做爲一個java開發者確定都知道且使用HashMap,但估計大部分人都不太知道WeakHashMap。從類定義上來看,它和普通的HashMap同樣,繼承了AbstractMap類和實現了Map接口,也就是說它有着與HashMap差很少的功能。那麼既然jdk已經提供了HashMap,爲何還要再提供一個WeakHashMap呢? 黑格爾曾經說過,存在必合理,接下來咱們來看下爲何有WeakHashMap。
先來想象一下你由於某種需求須要一個Cache,你確定會面臨一個問題,就是全部數據不可能都放到Cache裏,或者放到Cache裏性價比過低了。這個時候你可能很快就想到了各類Cache數據過時策略,目前也有一些優秀的包提供了功能豐富的Cache,好比Google的Guava Cache,它支持數據按期過時、LRU、LFU等策略,但它任然有可能會致使有用的數據被淘汰,沒用的數據遲遲不淘汰(若是策略使用得當的狀況下這都是小几率事件)。
若是我如今說有種機制,可讓你Cache裏不用的key數據自動清理掉,用的還留着,沒有誤殺也沒有漏殺你信不信!沒錯WeakHashMap就是能實現這種功能的東西,這也是它和普通的HashMap不一樣的地方——它有自清理的機制。 若是讓你實現一種自清理的HashMap,你怎麼作? 個人作法確定是想辦法先知道某個Key確定沒有在用了,而後清理到HashMap中對應的K-V。在JVM裏一個對象沒用了是指沒有任何其餘有用對象直接或者間接執行它,具體點就是在GC過程當中它是GCRoots不可達的。 Jvm提供了一種機制能讓咱們感知到一個對象是否已經變成了垃圾對象,這就是WeakReference,不瞭解WeakReference的能夠看下我上一篇介紹博客Java弱引用(WeakReferences)。
某個WeakReference對象所指向的對象若是被斷定爲垃圾對象,Jvm會將該WeakReference對象放到一個ReferenceQueue裏,咱們只要看下這個Queue裏的內容就知道某個對象還有沒有用了。 WeakHashMap就是這麼作的,因此這裏的Weak是指WeakReference。接下來讓咱們看下它的代碼,看它具體是怎麼實現的。
java
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static final int MAXIMUM_CAPACITY = 1 << 30;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;複製代碼
和普通HashMap同樣,WeakHashMap也有一些默認值,好比默認容量是16,最大容量2^30,使用超過75%它就會自動擴容。
git
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
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, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
/*
* 其餘代碼
*/
}複製代碼
它的Entry和普通HashMap的Entry最大的不一樣是它繼承了WeakReference,而後把Key作成了弱引用(注意只有Key沒有Value),而後傳入了一個ReferenceQueue,這就讓它能在某個key失去全部強引用的時候感知到。
github
public V put(K key, V value) {
Object k = maskNull(key);
int h = hash(k);
Entry<K,V>[] tab = getTable();
int i = indexFor(h, tab.length);
for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
if (h == e.hash && eq(k, e.get())) {
V oldValue = e.value;
if (value != oldValue)
e.value = value;
return oldValue;
}
}
modCount++;
Entry<K,V> e = tab[i];
tab[i] = new Entry<>(k, value, queue, h, e);
if (++size >= threshold)
resize(tab.length * 2);
return null;
}複製代碼
put方法也很簡單,用key的hashcode在tab中定位,而後判斷是不是已經存在的key,已經存在就替換舊值,不然就新建Entry,遇到多個不一樣的key有一樣的hashCode就採用開鏈的方式解決hash衝突。注意這裏和HashMap不太同樣的地方,HashMap會在鏈表太長的時候對鏈表作樹化,把單鏈錶轉換爲紅黑樹,防止極端狀況下hashcode衝突致使的性能問題,但在WeakHashMap中沒有樹化。
一樣,在size大於閾值的時候,WeakHashMap也對作resize的操做,也就是把tab擴大一倍。WeakHashMap中的resize比HashMap中的resize要簡單好懂些,但沒HashMap中的resize優雅。WeakHashMap中resize有另一個額外的操做,就是expungeStaleEntries(),就是對tab中的死對象作清理,稍後會詳細介紹。緩存
public V get(Object key) {
Object k = maskNull(key);
int h = hash(k);
Entry<K,V>[] tab = getTable();
int index = indexFor(h, tab.length);
Entry<K,V> e = tab[index];
while (e != null) {
if (e.hash == h && eq(k, e.get()))
return e.value;
e = e.next;
}
return null;
}複製代碼
get方法就沒什麼特別的了,由於Entry裏存了hash值和key的值,因此只要用indexFor定位到tab中的位置,而後遍歷一下單鏈表就知道了。
安全
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
while (p != null) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}複製代碼
expungeStaleEntries就是WeakHashMap的核心了,它承擔着Map中死對象的清理工做。原理就是依賴WeakReference和ReferenceQueue的特性。在每一個WeakHashMap都有個ReferenceQueue queue,在Entry初始化的時候也會將queue傳給WeakReference,這樣當某個能夠key失去全部強應用以後,其key對應的WeakReference對象會被放到queue裏,有了queue就知道須要清理哪些Entry裏。這裏也是整個WeakHashMap裏惟一加了同步的地方。 除了上文說的到resize中調用了expungeStaleEntries(),size()中也調用了這個清理方法。另外 getTable()也調了,這就意味着幾乎全部其餘方法都間接調用了清理。
多線程
除了上述幾個和HashMap不太同樣的地方外,WeakHashMap也提供了其餘HashMap全部的方法,好比像remove、clean、putAll、entrySet…… 功能上幾乎能夠徹底替代HashMap,但WeakHashMap也有一些本身的缺陷。
工具
關鍵修改方法沒有提供任何同步,多線程環境下確定會致使數據不一致的狀況,因此使用時須要多注意。源碼分析
HashMap在Jdk8作了好多優化,好比單鏈表在過長時會轉化爲紅黑樹,下降極端狀況下的操做複雜度。但WeakHashMap沒有相應的優化,有點像jdk8以前的HashMap版本。
性能
WeakHashMap構造方法中無法指定自定的ReferenceQueue,若是用戶想用ReferenceQueue作一些額外的清理工做的話就行不通了。若是即想用WeakHashMap的功能,也想用ReferenceQueue,貌似得本身實現一套新的WeakHashMap了。
優化
這裏列舉幾個我所知道的WeakHashMap的使用場景。
在阿里開源的Java診斷工具中使用了WeakHashMap作類-字節碼的緩存。
// 類-字節碼緩存
private final static Map<Class<?>/*Class*/, byte[]/*bytes of Class*/> classBytesCache
= new WeakHashMap<Class<?>, byte[]>();複製代碼
WeakHashMap這種自清理的機制,很是適合作緩存了。
ThreadLocal中用ThreadLocalMap存儲Thread對象,雖然ThreadLocalMap和WeakHashMap不是一個東西,但ThreadLocalMap也利用到了WeakReference的特性,功能用途很相似,因此我很好奇爲何ThreadLocalMap不直接用WeakHashMap呢!
版權聲明:本文爲博主原創文章,轉載請註明出處。 博客地址:xindoo.blog.csdn.net/