IdentityHashMap 是一個Map的實現類,但是由於他有意違反Map的設計原則,所以不是Map的通用實現,與HashMap屬於並列關係,他有個很大的特點,就是key可以存放相同的元素。也可以存放key於value都爲null。
IdentityHashMap利用哈希表來實現Map接口,比較鍵(和值)時使用引用相等性代替對象相等性,也就是說使用 ==
而不是使用equals,這是一個重要的細節點,需要牢牢記住。下面的bug就是因爲這個問題引起的。
- 比如對於要保存的key,k1和k2,當且僅當k1== k2的時候,IdentityHashMap纔會相等,而對於HashMap來說,相等的條件則是:(k1==null ? k2==null : k1.equals(k2))。
- IdentityHashMap不是Map的通用實現,它有意違反了Map的常規協定。並且IdentityHashMap允許key和value都爲null。
- 同HashMap,IdentityHashMap也是無序的,並且該類不是線程安全的,如果要使之線程安全,可以調用Collections.synchronizedMap(new IdentityHashMap(...))方法來實現。
bug代碼
IdentityHashMap<Integer,Integer> idn = new IdentityHashMap<>();
idn.put(128,1511);
idn.put(127,1511);
idn.remove(127);
idn.remove(128);
System.out.println(idn);
這段代碼的運行結果是:{128::1511}
也就是key爲128的沒有刪掉。
這是爲什麼呢?
原因其實很簡單,
看過jvm虛擬機的都應該知道,方法區中有一塊常量池(jdk8之前是存在方法區中的),也就是整型數據緩衝池,會緩衝-128至127這段整型數據。
所以上述代碼的執行流程爲:
idn.put();的時候,jvm先自動把128裝箱成一個Integer對象,存放到idn中,jvm看到127的時候,會先從緩衝池中,查找有沒有127的整型對象。如果沒有,在自動把127裝箱成一個Integer對象,存放到idn中,如果有,就直接用緩衝池中的Integer對象
當執行remove的時候,jvm看到127的時候,會先從緩衝池中,查找有沒有127的整型對象。如果沒有,在自動把127裝箱成一個Integer對象,存放到idn中,如果有,就直接用緩衝池中的Integer對象,所以找到了這個對象,堆地址跟put的時候是一樣的,所以能夠刪掉這個數據
當執行remove的時候,jvm看到128的時候,jvm又自動把128裝箱成一個Integer對象,這就又產生了一個對象,地址肯定與第一個裝箱的地址不同(類似於匿名對象,這個地址沒有人知道,因爲沒有棧引用,用完就沒人找的到了),所以remove的時候,發現不是相同地址,代表idn中沒有這個對象,所以就會返回null回來。
以上就是這段代碼的個人解讀
明白了原理,就可以很好解決這個bug了,上述分析中也提到過,主要原因就是它的匿名,沒有引用,所以我們加個引用就解決這個bug了。
IdentityHashMap<Integer,Integer> idn = new IdentityHashMap<>();
Integer a = new Integer(128);
//或者用 Integer.valueOf(128);
idn.put(a,1511);
idn.put(127,1511);
idn.remove(127);
idn.remove(a);
System.out.println(idn);
下面是 Integer.valueOf()的源碼(也就是int型數據裝箱的源碼)