不通过entrySet()迭代创建太多的Map.Entry实例吗?

时间:2011-03-28 07:25:48

标签: java performance

我不确定HashMapTreeMap是否存储Map.Entry。也就是说,当调用Map.Entry时,它可能会返回动态创建的entrySet().iterator().next()实例。

就个人而言,我认为这种形式可能更好:

class Entry {
    Object key;
    Object value;
}

interface InplaceIterator {
    boolean next();
}

Entry entryBuf = new Entry();
InplaceIterator it = map.entrySet().inplaceIterator(entryBuf);
while (it.next()) {
    // do with entryBuf...
}

因此,避免了Entry的创建。

我不知道Java Compiler是如何工作的,Java Compiler会通过分析数据流并获得Map.Entry可以安全重用的知识来优化Map.Entry的创建吗?

或者,有人已经编写了另一个集合框架来启用内部迭代吗?

3 个答案:

答案 0 :(得分:12)

您所描述的内容(拥有迭代器本地Map.Entry对象并将其重用于所有next()返回值)是一种可能的Map实现,我认为一些特殊用途的地图正在使用它。

例如,EnumMap.entrySet().iterator()(这里是OpenJDK的版本,1.6.0_20)的实现只是使用迭代器对象本身作为next()方法返回的Entry对象:

/**
 * Since we don't use Entry objects, we use the Iterator itself as entry.
 */
private class EntryIterator extends EnumMapIterator<Map.Entry<K,V>>
    implements Map.Entry<K,V>
{
    public Map.Entry<K,V> next() {
        if (!hasNext())
            throw new NoSuchElementException();
        lastReturnedIndex = index++;
        return this;
    }

    public K getKey() {
        checkLastReturnedIndexForEntryUse();
        return keyUniverse[lastReturnedIndex];
    }

    public V getValue() {
        checkLastReturnedIndexForEntryUse();
        return unmaskNull(vals[lastReturnedIndex]);
    }

    public V setValue(V value) {
        checkLastReturnedIndexForEntryUse();
        V oldValue = unmaskNull(vals[lastReturnedIndex]);
        vals[lastReturnedIndex] = maskNull(value);
        return oldValue;
    }

    // equals, hashCode, toString

    private void checkLastReturnedIndexForEntryUse() {
        if (lastReturnedIndex < 0)
            throw new IllegalStateException("Entry was removed");
    }
}

这是可能的,因为Map.Entry specification状态(我强调):

  

地图条目(键值对)。 Map.entrySet方法返回地图的集合视图,   其元素属于这一类。获取对地图条目的引用的唯一方法是   这个集合视图的迭代器。 这些Map.Entry个对象仅在持续时间内有效   迭代次数;更正式地说,如果支持映射具有,则映射条目的行为是不确定的   在迭代器返回条目后被修改,除非通过setValue操作   在地图上。

如果您想要一次性输入所有条目,则必须使用可能会创建的map.entrySet().toArray() 条目的不可变副本。


这里有一些关于默认地图的更多观察结果(全部在Ubuntu的openjdk6-source包中的OpenJDK 1.6.0_20中):

  • 通用地图HashMapTreeMap(以及遗产Hashtable)已在使用某些地图 一种Entry对象作为其内部结构(表或树)的一部分,所以他们简单地让这些 对象实现Map.Entry并返回它们。它们不是由Iterator动态创建的。

    同样适用于WeakHashMap(强引用中有Entry个对象无法避免 如果我理解的话,它是垃圾收集的关键 - 但只要你不打电话给next() 迭代器,迭代器在当前条目中保存密钥。

  • IdentityHashMap内部使用简单的Object[], 使用交替键和值,因此这里也没有输入对象,因此也可以重用迭代器作为条目。

  • ConcurrentSkipListMap正在使用没有实现任何内容的Node对象,因此它的迭代器返回 new AbstractMap.SimpleImmutableEntry<K,V>(n.key, v);。这意味着您无法使用他们的setValue()方法, 如课程文档中所述:

      

    此类中的方法返回的所有Map.Entry对及其视图表示映射的快照   在他们被生产的时候。它们不支持Entry.setValue方法。 (但请注意它   可以使用putputIfAbsentreplace更改关联地图中的映射,具体取决于   确切地说你需要哪种效果。)

  • ConcurrentHashMap内部使用类似于HashMap的HashEntry类,但这不是 实施任何事情此外,还有一个内部类WriteThroughEntry(扩展 AbstractMap.SimpleEntry),其setValue()方法委托给地图的put方法。迭代器 返回此WriteThroughEntry类的新对象。

答案 1 :(得分:1)

通常,小而短暂的物体几乎是免费的。考虑f1f2

static Entry f1(int i){ return new Entry(i); }

static Entry entry = new Entry(0);
static Entry f2(int i){ entry.i=i; return entry; }

static class Entry
{
    Entry(int i){ this.i=i; }
    int i;
    int get(){ return i; }
}

这是您描述的问题的实际测试案例 - 每次迭代重复使用相同的对象,而不是每次迭代创建一个新对象。在这两种情况下,一些数据都保存在对象中,并传送到呼叫站点进行读取。

让我们分析它,检索十亿个条目,并以三种不同的方式读取存储在每个条目中的数据

    int r = 0;
    for(int i=0; i<1000000000; i++)
    {
    test0:  r += i;
    test1:  r += f1(i).get();
    test2:  r += f2(i).get();
    } 
    print(r);

我得到的数字是test2test0一样快; test1test2慢一个每次迭代一个cpu周期。 (我猜不同的是几个机器指令,并且CPU在一个周期内将它们管道化)

如果您仍然不相信,请完全实施您提出的“高效”解决方案,将其与可能的“浪费”实施进行比较,并亲自看看它们之间的区别。你会惊讶的。

答案 2 :(得分:0)

Google Collection的ArrayListMultimap效率非常高,并且不会占用大量资源,http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/collect/ArrayListMultimap.html

创建Multimap

private Multimap<Integer, String> store = ArrayListMultimap.create();

迭代Multimap

for (Map.Entry<Integer, String> entry: store.entries()) {}

如果你宁愿避免Map.Entry,那么提取密钥集并从那里开始:

List<Integer> keys = new ArrayList<Integer>(store.keySet());
for(Long key : keys){
     ArrayList<String> stored_strings = new ArrayList<String>(store.get(key));
}