[英]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.