關於IdentityHashMap remove刪除不掉問題

IdentityHashMap 是一個Map的實現類,但是由於他有意違反Map的設計原則,所以不是Map的通用實現,與HashMap屬於並列關係,他有個很大的特點,就是key可以存放相同的元素。也可以存放key於value都爲null。

IdentityHashMap利用哈希表來實現Map接口,比較鍵(和值)時使用引用相等性代替對象相等性,也就是說使用 == 而不是使用equals,這是一個重要的細節點,需要牢牢記住。下面的bug就是因爲這個問題引起的。

 

  1. 比如對於要保存的key,k1和k2,當且僅當k1== k2的時候,IdentityHashMap纔會相等,而對於HashMap來說,相等的條件則是:(k1==null ? k2==null : k1.equals(k2))。
  2. IdentityHashMap不是Map的通用實現,它有意違反了Map的常規協定。並且IdentityHashMap允許key和value都爲null。
  3. 同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型數據裝箱的源碼)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章