【问题标题】:Java Iterated HashTable vs ArrayList speedJava Iterated HashTable vs ArrayList 速度
【发布时间】:2012-01-20 16:37:03
【问题描述】:

我正在编写一个简单的 3D SW 渲染引擎。我有一个默认的 ArrayList<Object3D> 包含整个场景。现在,我希望能够按名称添加、删除和选择对象,就像 3D 编辑器一样(因为它比鼠标选择简单得多,但在家庭作业中看起来仍然不错:))。

所以,我首先想到的是使用Hashtable 作为场景名称和索引ArrayList。但是,后来我想我可以直接使用Hashtable 保存场景,然后使用迭代器进行渲染。

所以我想问一下,在 3D 引擎中,什么是速度优先?因为与选择对象相比,我每秒会循环多次场景。 ArrayListiterated Hashtable 快吗?谢谢。

【问题讨论】:

    标签: java performance arraylist hashtable


    【解决方案1】:

    首先,我建议您使用HashMap 而不是Hashtable,原因与ArrayListVector 更好的选择相同:由于无用的同步,开销更少。

    我的猜测是,迭代 ArrayList 会比迭代 Hashtable(或 HashMapentrySet() 方法返回的 Set 更快。但了解的唯一方法是剖析。

    显然,HashMap 对显示列表的更改(除了追加或删除最后一个元素)将比ArrayList 更快。

    编辑 所以我遵循了自己的建议并进行了基准测试。这是我使用的代码:

    import java.util.*;
    
    public class IterTest {
        static class Thing {
            Thing(String name) { this.name = name; }
            String name;
        }
    
        static class ArrayIterTest implements Runnable {
            private final ArrayList<Thing> list;
            ArrayIterTest(ArrayList<Thing> list) {
                this.list = list;
            }
            public void run() {
                int i = 0;
                for (Thing thing : list) {
                    ++i;
                }
            }
        }
    
        static class ArraySubscriptTest implements Runnable {
            private final ArrayList<Thing> list;
            ArraySubscriptTest(ArrayList<Thing> list) {
                this.list = list;
            }
            public void run() {
                int i = 0;
                int n = list.size();
                for (int j = 0; j < n; ++j) {
                    Thing thing = list.get(j);
                    ++i;
                }
            }
        }
    
        static class MapIterTest implements Runnable {
            private final Map<String, Thing> map;
            MapIterTest(Map<String, Thing> map) {
                this.map = map;
            }
            public void run() {
                int i = 0;
                Set<Map.Entry<String, Thing>> set = map.entrySet();
                for (Map.Entry<String, Thing> entry : set) {
                    ++i;
                }
            }
        }
    
        public static void main(String[] args) {
            final int ITERS = 10000;
            final Thing[] things = new Thing[1000];
            for (int i = 0; i < things.length; ++i) {
                things[i] = new Thing("thing " + i);
            }
            final ArrayList<Thing> arrayList = new ArrayList<Thing>();
            Collections.addAll(arrayList, things);
            final HashMap<String, Thing> hashMap = new HashMap<String, Thing>();
            for (Thing thing : things) {
                hashMap.put(thing.name, thing);
            }
            final ArrayIterTest t1 = new ArrayIterTest(arrayList);
            final ArraySubscriptTest t2 = new ArraySubscriptTest(arrayList);
            final MapIterTest t3 = new MapIterTest(hashMap);
            System.out.println("t1 time: " + time(t1, ITERS));
            System.out.println("t2 time: " + time(t2, ITERS));
            System.out.println("t3 time: " + time(t3, ITERS));
        }
    
        private static long time(Runnable runnable, int iters) {
            System.gc();
            long start = System.nanoTime();
            while (iters-- > 0) {
                runnable.run();
            }
            return System.nanoTime() - start;
        }
    }
    

    以下是典型运行的结果:

    t1 time: 41412897
    t2 time: 30580187
    t3 time: 146536728
    

    显然,使用 ArrayList 是 HashMap 的一大胜利(3-4 倍),至少对于我迭代 HashMap 的风格而言。我怀疑数组迭代器比数组下标慢的原因是需要创建所有迭代器对象然后进行垃圾收集。

    作为参考,这是在具有大量可用内存的 Intel 1.6GHz 四核 Windows 机器上使用 Java 1.6.0_26(64 位 JVM)完成的。

    【讨论】:

      【解决方案2】:

      我相当确定遍历ArrayList 会比遍历Hashtable 更快。不过,不确定差异有多大——在实际的内部逻辑中可能(拇指吮吸)2 倍,但这并不多。

      但请注意,除非您需要多线程同步,否则您应该使用HashMap 而不是Hashtable。那里有一些表现。

      【讨论】:

      • 对于大多数需要同步的应用程序,Hashtable 的方法级同步几乎总是错误的,你最终还是要进行更高级别的同步。我不会推荐 Hashtable 作为处理多线程同步的一种方式。
      • 哈希表在从不同线程随机更新表的情况下很有用。不是您必须要与它“同步”线程的地方,而是为了避免获得损坏的表,例如 HashMap 发生的情况。
      • 是的,这是我评论中“最”之外的一个案例。我只是认为应用程序需要在线程之间共享映射但除了确保映射不损坏所需的同步之外不需要同步是不常见的。
      • 当我维护 iSeries JVM 时,我们收到了很多人(实际上是银行等)的多次抱怨,他们从 HashMap 得到“损坏”错误,而事实证明他们正在执行多线程东西。
      • 从 HashMap 切换到 Hashtable 将消除损坏的数据结构作为不正确同步的症状,但我的猜测是某个地方出现了不同的症状。当(如果?)他们的应用程序开发人员最终弄清楚如何正确同步他们的线程时,使用 Hashtable 的需求可能就消失了。就像我说的,只是一个猜测。 :)
      【解决方案3】:

      实际上,我查看了当前的HashMap 实现(正如大家指出的那样,优先于Hashtable)。遍历值看起来就像是简单地遍历底层数组。

      因此,速度可能与迭代 ArrayList 相当,尽管可能需要一些时间跳过 HashMaps 底层数组中的间隙。

      总而言之,剖析为王。

      【讨论】:

      • 数组是桶数组。每个存储桶都包含一个必须遍历的条目链。无论如何,您都必须使用 LinkedHashMap 来保留顺序,并且 LinkedHashMap 使用条目链而不是数组进行迭代。
      【解决方案4】:

      A) 不要使用Hashtable,使用HashMapHashtable 已被非正式弃用

      B) 这取决于应用程序。在HashMap 中查找会更快,迭代可能与两者在内部使用数组相同。 (但HashMap 中的数组有间隙,因此这可能会给ArrayList 带来一点优势)。哦,如果要保持固定的迭代顺序,请使用LinkedHashMap(按插入排序)或TreeMap(按自然顺序排序)

      【讨论】:

        【解决方案5】:

        如前所述,最好使用HashMap。关于迭代,理论上,ArrayList 必须更快有两个原因。首先是数据结构更简单,访问时间更短。二是ArrayList可以通过索引进行迭代,而不需要创建Iterator对象,这样在大量使用的情况下,产生的垃圾更少,因此产生的gc也更少。 在实践中 - 您可能不会注意到差异,这取决于您要使用它的重量。

        【讨论】:

          【解决方案6】:

          如果您不需要检索同步,请使用 java.util.HashMap 而不是 java.util.Hashtable

          【讨论】:

          • 也许您可以详细说明一下这个答案?
          猜你喜欢
          • 2013-11-19
          • 2012-08-07
          • 1970-01-01
          • 1970-01-01
          • 2020-05-26
          • 2018-02-20
          • 2010-10-02
          • 1970-01-01
          • 2014-09-17
          相关资源
          最近更新 更多