簡體   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