<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在之前的文章裡,我們聊到了 Java 標準庫中 HashMap 與 LinkedHashMap 的實現原理。HashMap 是一個標準的雜湊表資料結構,而 LinkedHashMap 是在 HashMap 的基礎上實現的雜湊連結串列。
今天,我們來討論 WeakHashMap,其中的 “Weak” 是指什麼,與前兩者的使用場景有何不同?我們就圍繞這些問題展開。
學習路線圖:
其實,WeakHashMap 與 HashMap 和 LinkedHashMap 的資料結構大同小異,所以我們先回顧後者的實現原理。
HashMap 是基於分離連結串列法解決雜湊衝突的動態雜湊表。
雜湊表示意圖
HashMap 實現示意圖
LinkedHashMap 是繼承於 HashMap 實現的雜湊連結串列。
LinkedHashMap 示意圖
FIFO 與 LRU 策略
WeakHashMap 中的 “Weak” 指鍵 Key 是弱參照,也叫弱鍵。弱參照是 Java 四大參照型別之一,一共有四種參照型別,分別是強參照、軟參照、弱參照和虛參照。我將它們的區別概括為 3 個維度:
維度 1 - 物件可達性狀態的區別: 強參照指向的物件是強可達的,只有強可達的物件才會認為是存活的物件,才能保證在垃圾收集的過程中不會被回收;
維度 2 - 垃圾回收策略的區別: 不同的參照型別的回收激程序度不同,
維度 3 - 感知垃圾回收時機: 當參照物件關聯的實際物件被垃圾回收時,參照物件會進入關聯的參照佇列,程式可以通過觀察參照佇列的方式,感知物件被垃圾回收的時機。
感知垃圾回收示意圖
提示: 關於 “Java 四種參照型別” 的區別,在小彭的 Java 專欄中深入討論過 《吊打面試官:說一下 Java 的四種參照型別》,去看看。
WeakHashMap 是使用弱鍵的動態雜湊表,用於實現 “自動清理” 的記憶體快取。
WeakHashMap 示意圖
WeakHashMap 與 HashMap 都是基於分離連結串列法解決雜湊衝突的動態雜湊表,兩者的主要區別在鍵 Key 的參照型別上:
HashMap 會持有鍵 Key 的強參照,除非手動移除,否則鍵值對會長期存在於雜湊表中。而 WeakHashMap 只持有鍵 Key 的弱參照,當 Key 物件不再被外部持有強參照時,鍵值對會被自動被清理。
WeakHashMap 與 LinkedHashMap 都有自動清理的能力,兩者的主要區別在於淘汰資料的策略上:
LinkedHashMap 會按照 FIFO 或 LRU 的策略 “嘗試” 淘汰資料,需要開發者重寫 removeEldestEntry()
方法實現是否刪除最早節點的判斷邏輯。而 WeakHashMap 會按照 Key 物件的可達性淘汰資料,當 Key 物件不再被持有強參照時,會自動清理無效資料。
WeakHashMap 的 Key 使用弱參照,也就是以 Key 作為清理資料的判斷錨點,當 Key 變得不可達時會自動清理資料。此時,如果使用多個 equals
相等的 Key 物件存取鍵值對,就會出現第 1 個 Key 物件不可達導致鍵值對被回收,而第 2 個 Key 查詢鍵值對為 null 的問題。這說明 equals
相等的 Key 物件在 HashMap 等雜湊表中是等價的,但是在 WeakHashMap 雜湊表中是不等價的。
因此,如果 Key 型別沒有重寫 equals 方法,那麼 WeakHashMap 就表現良好,否則會存在歧義。例如下面這個 Demo 中,首先建立了指向 image_url1
的圖片 Key1,再重建了同樣指向 image_url1
的圖片 Key2。在 HashMap 中,Key1 和 Key2 等價,但在 WeakHashMap 中,Key1 和 Key2 不等價。
Demo
class ImageKey { private String url; ImageKey(String url) { this.url = url; } public boolean equals(Object obj) { return (obj instanceOf ImageKey) && Objects.equals(((ImageKey)obj).url, this.url); } } WeakHashMap<ImageKey, Bitmap> map = new WeakHashMap<>(); ImageKey key1 = new ImageKey("image_url1"); ImageKey key2 = new ImageKey("image_url2"); // key1 equalsTo key3 ImageKey key3 = new ImageKey("image_url1"); map.put(key1, bitmap1); map.put(key2, bitmap2); System.out.println(map.get(key1)); // 輸出 bitmap1 System.out.println(map.get(key2)); // 輸出 bitmap2 System.out.println(map.get(key3)); // 輸出 bitmap1 // 使 key1 不可達,key3 保持 key1 = null; // 說明重建 Key 與原始 Key 不等價 System.out.println(map.get(key1)); // 輸出 null System.out.println(map.get(key2)); // 輸出 bitmap2 System.out.println(map.get(key3)); // 輸出 null
預設的 Object#equals
是判斷兩個變數是否指向同一個物件:
Object.java
public boolean equals(Object obj) { return (this == obj); }
不管是 Key 還是 Value 使用弱參照都可以實現自動清理,至於使用哪一種方法各有優缺點,適用場景也不同。
Key 弱參照: 以 Key 作為清理資料的判斷錨點,當 Key 不可達時清理資料。優點是容器外不需要持有 Value 的強參照,缺點是重建的 Key 與原始 Key 不等價,重建 Key 無法阻止資料被清理;
Value 弱參照: 以 Value 作為清理資料的判斷錨點,當 Value 不可達時清理資料。優點是重建 Key 與與原始 Key 等價,缺點是容器外需要持有 Value 的強參照。
型別 | 優點 | 缺點 | 場景 |
---|---|---|---|
Key 弱參照 | 外部不需要持有 Value 的強參照,使用更簡單 | 重建 Key 不等價 | 未重寫 equals |
Value 弱參照 | 重建 Key 等價 | 外部需要持有 Value 的強參照 | 重寫 equals |
舉例 1: 在 Android Glide 圖片框架的多級快取中,因為圖片的 EngineKey 是可重建的,存在多個 EngineKey 物件指向同一個圖片 Bitmap,所以 Glide 最頂層的活動快取採用的是 Value 弱參照。
EngineKey.java
class EngineKey implements Key { // 重寫 equals @Override public boolean equals(Object o) { if (o instanceof EngineKey) { EngineKey other = (EngineKey) o; return model.equals(other.model) && signature.equals(other.signature) && height == other.height && width == other.width && transformations.equals(other.transformations) && resourceClass.equals(other.resourceClass) && transcodeClass.equals(other.transcodeClass) && options.equals(other.options); } return false; } }
舉例 2: 在 ThreadLocal 的 ThreadLocalMap 執行緒本地儲存中,因為 ThreadLocal 沒有重寫 equals,不存在多個 ThreadLocal 物件指向同一個鍵值對的情況,所以 ThreadLocal 採用的是 Key 弱參照。
ThreadLocal.java
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
這一節,我們來分析 WeakHashMap 中主要流程的原始碼。
事實上,WeakHashMap 就是照著 Java 7 版本的 HashMap 依葫蘆畫瓢的,沒有樹化的邏輯。考慮到我們已經對 HashMap 做過詳細分析,所以我們沒有必要重複分析 WeakHashMap 的每個細節,而是把重心放在 WeakHashMap 與 HashMap 不同的地方。
先用一個表格整理 WeakHashMap 的屬性:
版本 | 資料結構 | 節點實現類 | 屬性 |
---|---|---|---|
Java 7 HashMap | 陣列 + 連結串列 | Entry(單連結串列) | 1、table(陣列) 2、size(尺寸) 3、threshold(擴容閾值) 4、loadFactor(裝載因子上限) 5、modCount(修改計數) 6、預設陣列容量 16 7、最大陣列容量 2^30 8、預設負載因子 0.75 |
WeakHashMap | 陣列 + 連結串列 | Entry(單連結串列,弱參照的子型別) | 9、queue(參照佇列) |
WeakHashMap.java
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> { // 預設陣列容量 private static final int DEFAULT_INITIAL_CAPACITY = 16; // 陣列最大容量:2^30(高位 0100,低位都是 0) private static final int MAXIMUM_CAPACITY = 1 << 30; // 預設裝載因子上限:0.75 private static final float DEFAULT_LOAD_FACTOR = 0.75f; // 底層陣列 Entry<K,V>[] table; // 鍵值對數量 private int size; // 擴容閾值(容量 * 裝載因子) private int threshold; // 裝載因子上限 private final float loadFactor; // 參照佇列 private final ReferenceQueue<Object> queue = new ReferenceQueue<>(); // 修改計數 int modCount; // 連結串列節點(一個 Entry 等於一個鍵值對) private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { // 與 HashMap 和 LinkedHashMap 相比,少了 key 的強參照 // final K key; // Value(強參照) V value; // 雜湊值 final int hash; Entry<K,V> next; Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) { super(key /*注意:只有 Key 是弱參照*/, queue); this.value = value; this.hash = hash; this.next = next; } } }
WeakHashMap 與 HashMap 的屬性幾乎相同,主要區別有 2 個:
WeakHashMap#Entry
節點繼承於 WeakReference
,表面看是 WeakHashMap 持有了 Entry 的強參照,其實不是。注意看 Entry 的構造方法,WeakReference 關聯的實際物件是 Key。 所以,WeakHashMap 依然持有 Entry 和 Value 的強參照,僅持有 Key 的弱參照。參照關係示意圖
不出意外的話又有小朋友出來舉手提問了
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45