繁体   English   中英

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

[英]Doesn't that iterate thru entrySet() create too many Map.Entry instances?

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

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

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的知识?

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

您所描述的内容(具有迭代器本地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规范声明(由我强调):

映射条目(键值对)。 Map.entrySet方法返回地图的集合视图,其元素属于此类。 获取对映射条目的引用的唯一方法是来自此collection-view的迭代器。 这些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()方法委托给map的put方法。 迭代器返回此WriteThroughEntry类的新对象。

通常,小的,短暂的物体几乎是免费的。 考虑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一样快; 每次迭代只有一个cpu周期, test1test2慢。 (我猜不同的是几个机器指令,CPU在一个周期内管道化)

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

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));
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM