簡體   English   中英

字符串 IdentityHashMap 與 HashMap 性能

[英]String IdentityHashMap vs HashMap performance

Identity HashMap 是 Java 中的特殊實現,它比較對象引用而不是equals()並且還使用identityHashCode()代替hashCode() 此外,它使用linear-probe hash table而不是Entry list

Map<String, String> map = new HashMap<>(); 
Map<String, String> iMap = new IdentityHashMap<>();

這是否意味着如果調整正確,對於String鍵, IdentifyHashMap通常會更快?

看這個例子:

public class Dictionary {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("/usr/share/dict/words"));

        String line;
        ArrayList<String> list = new ArrayList<String>();

        while ((line = br.readLine()) != null) {
            list.add(line);
        }
        System.out.println("list.size() = " + list.size());
        Map<String, Integer> iMap = new IdentityHashMap<>(list.size());
        Map<String, Integer> hashMap = new HashMap<>(list.size());

        long iMapTime = 0, hashMapTime = 0;

        long time;
        for (int i = 0; i < list.size(); i++) {
            time = System.currentTimeMillis();
            iMap.put(list.get(i), i);
            time = System.currentTimeMillis() - time;
            iMapTime += time;
            time = System.currentTimeMillis();
            hashMap.put(list.get(i), i);
            time = System.currentTimeMillis() - time;
            hashMapTime += time;
        }

        System.out.println("iMapTime = " + iMapTime + " hashMapTime = " + hashMapTime);
    }

}

嘗試了非常基本的性能檢查。 我正在閱讀字典單詞(235K)並推入這兩個地圖。 它打印:

list.size() = 235886
iMapTime = 101 hashMapTime = 617 

我認為這是一個很好的改進,可以忽略,除非我在這里做錯了。

您將在 IdentityHashMap 上看到明顯更快的性能,但是這需要付出巨大的代價。

您必須絕對確保永遠不會將具有相同值但不同身份的對象添加到地圖中。

現在和未來都很難保證這一點,而且很多人做出了錯誤的假設。

例如

String t1 = "test";
String t2 = "test";

t1==t2將返回真。

String t1 = "test";
String t2 = new String("test");

t1==t2將返回 false。

總的來說,我的建議是,除非您絕對需要提高性能並確切地知道自己在做什么並嚴重鎖定和評論對類的訪問,否則通過使用 IdentityHashMap,您將面臨難以追蹤錯誤的巨大風險未來。

IdentityHashMap<String,?>工作?

要使IdentityHashMap<String,?>為任意字符串工作,您必須將您put()的鍵和傳遞給get()潛在鍵都設置為String.intern() get() (或使用等效機制。)

注意:與@m3th0dman 的回答中所述不同,您不需要intern()值。

無論哪種方式,實習字符串最終都需要在某種已經實習字符串的哈希表中查找它。 因此,除非您出於某種其他原因不得不實習字符串(因此已經支付了費用),否則您不會從中獲得多少實際的性能提升。

那么為什么測試表明你可以呢?

您的測試不切實際的地方在於,您保留與put()一起使用的鍵的確切列表,並按列表順序逐一遍歷它們。 注意(同樣可以通過將元素插入LinkedHashMap並在其條目集上簡單地調用iterator()來實現。

那么IdentityHashMap什么意義呢?

在某些情況下,可以保證(或實際上保證)對象標識與equals()相同。 想象一下,例如嘗試實現您自己的ThreadLocal類,您可能會編寫如下內容:

public final class ThreadLocal<T> {
   private final IdentityHashMap<Thread,T> valueMap;
   ...
   public T get() {
       return valueMap.get( Thread.currentThread() );
   }
}

因為你知道線程除了身份之外沒有平等的概念。 如果您的地圖鍵是枚舉值等,情況也是如此。

從技術上講,您可以執行以下操作以確保您擁有相同的字符串表示實例:

public class StringIdentityHashMap extends IdentityHashMap<String, String>
{
    @Override
    public String put(String key, String value)
    {
        return super.put(key.intern(), value.intern());
    }

    @Override
    public void putAll(Map<? extends String, ? extends String> m)
    {
        m.entrySet().forEach(entry -> put(entry.getKey().intern(), entry.getValue().intern()));
    }

    @Override 
    public String get(Object key)
    {
        if (!(key instanceof String)) {
            throw new IllegalArgumentException();
        }
        return super.get(((String) key).intern());
    }

    //implement the rest of the methods in the same way
}

但這對您沒有多大幫助,因為intern()調用equals()以確保給定的String存在於 String 池中,因此您最終會獲得典型HashMap的性能。

但是,這只會幫助您改善內存而不是 CPU。 沒有辦法獲得更好的 CPU 使用率並確保您的程序是正確的(不可能使用一些可能會改變的 JVM 內部知識),因為字符串可以在字符串池中或不在字符串池中,並且您無法知道它們是否在沒有(不隱式)調用equals()

有趣的是,IdentityHashMap 可能會更慢。 我使用 Class 對象作為鍵,並且使用 HashMap 比 IdentityHashMap 提高了約 50% 的性能。

IdentityHashMap 和 HashMap 在內部是不同的,所以如果你的鍵的 equals() 方法真的很快,HashMap 似乎更好。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM