[英]Doesn't that iterate thru entrySet() create too many Map.Entry instances?
我不确定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...
}
因此,避免了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中):
通用映射HashMap
和TreeMap
(以及遗留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
方法。 (但请注意,可以使用put
,putIfAbsent
或replace
更改关联映射中的映射,具体取决于您需要的确切效果。)
ConcurrentHashMap
内部使用类似于HashMap的HashEntry
类,但这并没有实现任何东西。 此外,还有一个内部类WriteThroughEntry
(扩展AbstractMap.SimpleEntry
),其setValue()
方法委托给map的put
方法。 迭代器返回此WriteThroughEntry
类的新对象。
通常,小的,短暂的物体几乎是免费的。 考虑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; }
}
这是您描述的问题的实际测试案例 - 每次迭代重用相同的对象,而不是每次迭代创建一个新对象。 在这两种情况下,一些数据都保存在对象中,并传送到呼叫站点进行读取。
让我们分析它,检索十亿个条目,并以三种不同的方式读取存储在每个条目中的数据
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);
我得到的数字是, test2
和test0
一样快; 每次迭代只有一个cpu周期, test1
比test2
慢。 (我猜不同的是几个机器指令,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.