Java 不';通过entrySet()进行迭代会创建太多的Map.Entry实例吗?

Java 不';通过entrySet()进行迭代会创建太多的Map.Entry实例吗?,java,performance,Java,Performance,我不确定是HashMap还是TreeMap本身存储Map.Entry。也就是说,当调用entrySet().iterator().next()时,它可能会返回动态创建的Map.Entry实例 就我个人而言,我认为这种形式可能更好: class Entry { Object key; Object value; } interface InplaceIterator { boolean next(); } Entry entryBuf = new Entry(); In

我不确定是
HashMap
还是
TreeMap
本身存储
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...
}
因此,可以避免创建条目

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

或者,是否有人已经编写了另一个集合框架来启用就地迭代?

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

例如,
EnumMap.entrySet().iterator()
(这里是OpenJDK的版本,1.6.0\u 20)的实现只是将迭代器对象本身用作
next()
方法返回的条目对象:

/**
 * 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");
    }
}
/**
*因为我们不使用条目对象,所以我们使用迭代器本身作为条目。
*/
私有类EntryInterator扩展了EnumMapIterator
实现Map.Entry
{
publicmap.Entry next(){
如果(!hasNext())
抛出新的NoTouchElementException();
lastReturnedIndex=index++;
归还这个;
}
公共K getKey(){
checkLastReturnedIndexForEntryUse();
return-keyUniverse[lastReturnedIndex];
}
public V getValue(){
checkLastReturnedIndexForEntryUse();
返回unmaskNull(VAL[lastReturnedIndex]);
}
公共V设置值(V值){
checkLastReturnedIndexForEntryUse();
V oldValue=unmaskNull(VAL[lastReturnedIndex]);
VAL[lastReturnedIndex]=maskNull(值);
返回旧值;
}
//等于,哈希代码,toString
私有void checkLastReturnedIndexForEntryUse()的{
如果(最后返回的索引<0)
抛出新的非法状态异常(“条目已删除”);
}
}
这是可能的,因为国家(我强调):

映射条目(键值对)。
Map.entrySet
方法返回映射的集合视图, 其元素属于此类。获取地图条目引用的唯一方法是从 此集合视图的迭代器这些
Map.Entry
对象仅在持续时间内有效 迭代的
;更正式地说,如果备份映射具有 在迭代器返回条目后进行了修改,但通过setValue操作除外 在地图条目上

如果要同时使用所有条目,则必须使用
map.entrySet().toArray()
,这可能会创建 条目的不可变副本


下面是关于默认映射的更多观察(都在OpenJDK 1.6.020中,可以在Ubuntu的
openjdk6源代码
包中找到):

  • 通用映射
    HashMap
    TreeMap
    (以及传统的
    哈希表
    )已经在使用一些 类似于
    条目
    对象作为其内部结构(表或树)的一部分,因此它们可以简化 对象实现Map.Entry并返回它们。它们不是由迭代器动态创建的

    这同样适用于
    WeakHashMap
    (在强引用中具有
    条目
    对象不会避免 如果我理解正确的话,垃圾收集是关键——但只要你不在服务器上调用
    next()
    迭代器,迭代器持有当前条目中的键)

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

  • ConcurrentSkipListMap
    使用的节点对象不实现任何内容,因此其迭代器返回
    newAbstractMap.SimpleImmutableEntry(n.key,v)。这意味着您不能使用它们的
    setValue()
    方法, 如课堂文档中所述:

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

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


Google Collection的ArrayListMultimap相当高效,并且不是资源密集型

创建多重映射

private Multimap<Integer, String> store = ArrayListMultimap.create();
private Multimap store=ArrayListMultimap.create();
迭代多重映射

for (Map.Entry<Integer, String> entry: store.entries()) {}
for(Map.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));
}
List keys=new ArrayList(store.keySet());
用于(长键:键){
ArrayList stored_strings=新的ArrayList(store.get(key));
}

通常,小型、寿命短的对象几乎是免费的。考虑<代码> F1 < /代码>和<代码> F2 < /代码>

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; }
}
这是您描述的问题的一个实际测试用例-每次迭代重用相同的对象,而不是每次迭代创建一个新对象。在这两种情况下,一些数据保存在对象中,并传递到调用si
    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);