我不确定HashMap
或TreeMap
是否存储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的创建吗?
或者,有人已经编写了另一个集合框架来启用内部迭代吗?
答案 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中):
通用地图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()
方法委托给地图的put
方法。迭代器
返回此WriteThroughEntry
类的新对象。
答案 1 :(得分:1)
通常,小而短暂的物体几乎是免费的。考虑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
一样快; test1
比test2
慢一个每次迭代一个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));
}