概要

这一章,我们对WeakHashMap进行学习。
我们先对WeakHashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用WeakHashMap。
第1部分 WeakHashMap介绍
第2部分 WeakHashMap数据结构
第3部分 WeakHashMap源码解析(基于JDK1.6.0_45)
第4部分 WeakHashMap遍历方式
第5部分 WeakHashMap示例

转载请注明出处:http://www.cnblogs.com/skywang12345/admin/EditPosts.aspx?postid=3311092

基本介绍

WeakHashMap简介

WeakHashMap 继承于AbstractMap,实现了Map接口。
HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null
不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。
这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。实现步骤是:
(01) 新建WeakHashMap,将“键值对”添加到WeakHashMap中。
实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
(02) 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
(03) 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对
这就是“弱键”如何被自动从WeakHashMap中删除的步骤了。

和HashMap一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap。

WeakHashMap的构造函数

WeakHashMap共有4个构造函数,如下:

  1. // 默认构造函数。
  2. WeakHashMap()
  3. // 指定“容量大小”的构造函数
  4. WeakHashMap(int capacity)
  5. // 指定“容量大小”和“加载因子”的构造函数
  6. WeakHashMap(int capacity, float loadFactor)
  7. // 包含“子Map”的构造函数
  8. WeakHashMap(Map<? extends K, ? extends V> map)

WeakHashMap的API

  1. void clear()
  2. Object clone()
  3. boolean containsKey(Object key)
  4. boolean containsValue(Object value)
  5. Set<Entry<K, V>> entrySet()
  6. V get(Object key)
  7. boolean isEmpty()
  8. Set<K> keySet()
  9. V put(K key, V value)
  10. void putAll(Map<? extends K, ? extends V> map)
  11. V remove(Object key)
  12. int size()
  13. Collection<V> values()

数据结构

WeakHashMap的继承关系如下

  1. java.lang.Object
  2. java.util.AbstractMap<K, V>
  3. java.util.WeakHashMap<K, V>
  4. public class WeakHashMap<K,V>
  5. extends AbstractMap<K,V>
  6. implements Map<K,V> {}

WeakHashMap与Map关系如下图:

WeakHashMap详细介绍(源码解析)和使用示例 - 图1

从图中可以看出:
(01) WeakHashMap继承于AbstractMap,并且实现了Map接口。
(02) WeakHashMap是哈希表,但是它的键是”弱键”。WeakHashMap中保护几个重要的成员变量:table, size, threshold, loadFactor, modCount, queue。
  table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的”key-value键值对”都是存储在Entry数组中的。
  size是Hashtable的大小,它是Hashtable保存的键值对的数量。
  threshold是Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值=”容量*加载因子”。
  loadFactor就是加载因子。
  modCount是用来实现fail-fast机制的
  queue保存的是“已被GC清除”的“弱引用的键”。

源码解析(基于JDK1.6.0_45)

下面对WeakHashMap的源码进行说明

  1. package java.util;
  2. import java.lang.ref.WeakReference;
  3. import java.lang.ref.ReferenceQueue;
  4. public class WeakHashMap<K,V>
  5. extends AbstractMap<K,V>
  6. implements Map<K,V> {
  7. // 默认的初始容量是16,必须是2的幂。
  8. private static final int DEFAULT_INITIAL_CAPACITY = 16;
  9. // 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
  10. private static final int MAXIMUM_CAPACITY = 1 << 30;
  11. // 默认加载因子
  12. private static final float DEFAULT_LOAD_FACTOR = 0.75f;
  13. // 存储数据的Entry数组,长度是2的幂。
  14. // WeakHashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表
  15. private Entry[] table;
  16. // WeakHashMap的大小,它是WeakHashMap保存的键值对的数量
  17. private int size;
  18. // WeakHashMap的阈值,用于判断是否需要调整WeakHashMap的容量(threshold = 容量*加载因子)
  19. private int threshold;
  20. // 加载因子实际大小
  21. private final float loadFactor;
  22. // queue保存的是“已被GC清除”的“弱引用的键”。
  23. // 弱引用和ReferenceQueue 是联合使用的:如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中
  24. private final ReferenceQueue<K> queue = new ReferenceQueue<K>();
  25. // WeakHashMap被改变的次数
  26. private volatile int modCount;
  27. // 指定“容量大小”和“加载因子”的构造函数
  28. public WeakHashMap(int initialCapacity, float loadFactor) {
  29. if (initialCapacity < 0)
  30. throw new IllegalArgumentException("Illegal Initial Capacity: "+
  31. initialCapacity);
  32. // WeakHashMap的最大容量只能是MAXIMUM_CAPACITY
  33. if (initialCapacity > MAXIMUM_CAPACITY)
  34. initialCapacity = MAXIMUM_CAPACITY;
  35. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  36. throw new IllegalArgumentException("Illegal Load factor: "+
  37. loadFactor);
  38. // 找出“大于initialCapacity”的最小的2的幂
  39. int capacity = 1;
  40. while (capacity < initialCapacity)
  41. capacity <<= 1;
  42. // 创建Entry数组,用来保存数据
  43. table = new Entry[capacity];
  44. // 设置“加载因子”
  45. this.loadFactor = loadFactor;
  46. // 设置“WeakHashMap阈值”,当WeakHashMap中存储数据的数量达到threshold时,就需要将WeakHashMap的容量加倍。
  47. threshold = (int)(capacity * loadFactor);
  48. }
  49. // 指定“容量大小”的构造函数
  50. public WeakHashMap(int initialCapacity) {
  51. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  52. }
  53. // 默认构造函数。
  54. public WeakHashMap() {
  55. this.loadFactor = DEFAULT_LOAD_FACTOR;
  56. threshold = (int)(DEFAULT_INITIAL_CAPACITY);
  57. table = new Entry[DEFAULT_INITIAL_CAPACITY];
  58. }
  59. // 包含“子Map”的构造函数
  60. public WeakHashMap(Map<? extends K, ? extends V> m) {
  61. this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, 16),
  62. DEFAULT_LOAD_FACTOR);
  63. // 将m中的全部元素逐个添加到WeakHashMap中
  64. putAll(m);
  65. }
  66. // 键为null的mask值。
  67. // 因为WeakReference中允许“null的key”,若直接插入“null的key”,将其当作弱引用时,会被删除。
  68. // 因此,这里对于“key为null”的清空,都统一替换为“key为NULL_KEY”,“NULL_KEY”是“静态的final常量”。
  69. private static final Object NULL_KEY = new Object();
  70. // 对“null的key”进行特殊处理
  71. private static Object maskNull(Object key) {
  72. return (key == null ? NULL_KEY : key);
  73. }
  74. // 还原对“null的key”的特殊处理
  75. private static <K> K unmaskNull(Object key) {
  76. return (K) (key == NULL_KEY ? null : key);
  77. }
  78. // 判断“x”和“y”是否相等
  79. static boolean eq(Object x, Object y) {
  80. return x == y || x.equals(y);
  81. }
  82. // 返回索引值
  83. // h & (length-1)保证返回值的小于length
  84. static int indexFor(int h, int length) {
  85. return h & (length-1);
  86. }
  87. // 清空table中无用键值对。原理如下:
  88. // (01) 当WeakHashMap中某个“弱引用的key”由于没有再被引用而被GC收回时,
  89. // 被回收的“该弱引用key”也被会被添加到"ReferenceQueue(queue)"中。
  90. // (02) 当我们执行expungeStaleEntries时,
  91. // 就遍历"ReferenceQueue(queue)"中的所有key
  92. // 然后就在“WeakReference的table”中删除与“ReferenceQueue(queue)中key”对应的键值对
  93. private void expungeStaleEntries() {
  94. Entry<K,V> e;
  95. while ( (e = (Entry<K,V>) queue.poll()) != null) {
  96. int h = e.hash;
  97. int i = indexFor(h, table.length);
  98. Entry<K,V> prev = table[i];
  99. Entry<K,V> p = prev;
  100. while (p != null) {
  101. Entry<K,V> next = p.next;
  102. if (p == e) {
  103. if (prev == e)
  104. table[i] = next;
  105. else
  106. prev.next = next;
  107. e.next = null; // Help GC
  108. e.value = null; // " "
  109. size--;
  110. break;
  111. }
  112. prev = p;
  113. p = next;
  114. }
  115. }
  116. }
  117. // 获取WeakHashMap的table(存放键值对的数组)
  118. private Entry[] getTable() {
  119. // 删除table中“已被GC回收的key对应的键值对”
  120. expungeStaleEntries();
  121. return table;
  122. }
  123. // 获取WeakHashMap的实际大小
  124. public int size() {
  125. if (size == 0)
  126. return 0;
  127. // 删除table中“已被GC回收的key对应的键值对”
  128. expungeStaleEntries();
  129. return size;
  130. }
  131. public boolean isEmpty() {
  132. return size() == 0;
  133. }
  134. // 获取key对应的value
  135. public V get(Object key) {
  136. Object k = maskNull(key);
  137. // 获取key的hash值。
  138. int h = HashMap.hash(k.hashCode());
  139. Entry[] tab = getTable();
  140. int index = indexFor(h, tab.length);
  141. Entry<K,V> e = tab[index];
  142. // 在“该hash值对应的链表”上查找“键值等于key”的元素
  143. while (e != null) {
  144. if (e.hash == h && eq(k, e.get()))
  145. return e.value;
  146. e = e.next;
  147. }
  148. return null;
  149. }
  150. // WeakHashMap是否包含key
  151. public boolean containsKey(Object key) {
  152. return getEntry(key) != null;
  153. }
  154. // 返回“键为key”的键值对
  155. Entry<K,V> getEntry(Object key) {
  156. Object k = maskNull(key);
  157. int h = HashMap.hash(k.hashCode());
  158. Entry[] tab = getTable();
  159. int index = indexFor(h, tab.length);
  160. Entry<K,V> e = tab[index];
  161. while (e != null && !(e.hash == h && eq(k, e.get())))
  162. e = e.next;
  163. return e;
  164. }
  165. // 将“key-value”添加到WeakHashMap中
  166. public V put(K key, V value) {
  167. K k = (K) maskNull(key);
  168. int h = HashMap.hash(k.hashCode());
  169. Entry[] tab = getTable();
  170. int i = indexFor(h, tab.length);
  171. for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
  172. // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!
  173. if (h == e.hash && eq(k, e.get())) {
  174. V oldValue = e.value;
  175. if (value != oldValue)
  176. e.value = value;
  177. return oldValue;
  178. }
  179. }
  180. // 若“该key”对应的键值对不存在于WeakHashMap中,则将“key-value”添加到table中
  181. modCount++;
  182. Entry<K,V> e = tab[i];
  183. tab[i] = new Entry<K,V>(k, value, queue, h, e);
  184. if (++size >= threshold)
  185. resize(tab.length * 2);
  186. return null;
  187. }
  188. // 重新调整WeakHashMap的大小,newCapacity是调整后的单位
  189. void resize(int newCapacity) {
  190. Entry[] oldTable = getTable();
  191. int oldCapacity = oldTable.length;
  192. if (oldCapacity == MAXIMUM_CAPACITY) {
  193. threshold = Integer.MAX_VALUE;
  194. return;
  195. }
  196. // 新建一个newTable,将“旧的table”的全部元素添加到“新的newTable”中,
  197. // 然后,将“新的newTable”赋值给“旧的table”。
  198. Entry[] newTable = new Entry[newCapacity];
  199. transfer(oldTable, newTable);
  200. table = newTable;
  201. if (size >= threshold / 2) {
  202. threshold = (int)(newCapacity * loadFactor);
  203. } else {
  204. // 删除table中“已被GC回收的key对应的键值对”
  205. expungeStaleEntries();
  206. transfer(newTable, oldTable);
  207. table = oldTable;
  208. }
  209. }
  210. // 将WeakHashMap中的全部元素都添加到newTable中
  211. private void transfer(Entry[] src, Entry[] dest) {
  212. for (int j = 0; j < src.length; ++j) {
  213. Entry<K,V> e = src[j];
  214. src[j] = null;
  215. while (e != null) {
  216. Entry<K,V> next = e.next;
  217. Object key = e.get();
  218. if (key == null) {
  219. e.next = null; // Help GC
  220. e.value = null; // " "
  221. size--;
  222. } else {
  223. int i = indexFor(e.hash, dest.length);
  224. e.next = dest[i];
  225. dest[i] = e;
  226. }
  227. e = next;
  228. }
  229. }
  230. }
  231. // 将"m"的全部元素都添加到WeakHashMap中
  232. public void putAll(Map<? extends K, ? extends V> m) {
  233. int numKeysToBeAdded = m.size();
  234. if (numKeysToBeAdded == 0)
  235. return;
  236. // 计算容量是否足够,
  237. // 若“当前实际容量 < 需要的容量”,则将容量x2。
  238. if (numKeysToBeAdded > threshold) {
  239. int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
  240. if (targetCapacity > MAXIMUM_CAPACITY)
  241. targetCapacity = MAXIMUM_CAPACITY;
  242. int newCapacity = table.length;
  243. while (newCapacity < targetCapacity)
  244. newCapacity <<= 1;
  245. if (newCapacity > table.length)
  246. resize(newCapacity);
  247. }
  248. // 将“m”中的元素逐个添加到WeakHashMap中。
  249. for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
  250. put(e.getKey(), e.getValue());
  251. }
  252. // 删除“键为key”元素
  253. public V remove(Object key) {
  254. Object k = maskNull(key);
  255. // 获取哈希值。
  256. int h = HashMap.hash(k.hashCode());
  257. Entry[] tab = getTable();
  258. int i = indexFor(h, tab.length);
  259. Entry<K,V> prev = tab[i];
  260. Entry<K,V> e = prev;
  261. // 删除链表中“键为key”的元素
  262. // 本质是“删除单向链表中的节点”
  263. while (e != null) {
  264. Entry<K,V> next = e.next;
  265. if (h == e.hash && eq(k, e.get())) {
  266. modCount++;
  267. size--;
  268. if (prev == e)
  269. tab[i] = next;
  270. else
  271. prev.next = next;
  272. return e.value;
  273. }
  274. prev = e;
  275. e = next;
  276. }
  277. return null;
  278. }
  279. // 删除“键值对”
  280. Entry<K,V> removeMapping(Object o) {
  281. if (!(o instanceof Map.Entry))
  282. return null;
  283. Entry[] tab = getTable();
  284. Map.Entry entry = (Map.Entry)o;
  285. Object k = maskNull(entry.getKey());
  286. int h = HashMap.hash(k.hashCode());
  287. int i = indexFor(h, tab.length);
  288. Entry<K,V> prev = tab[i];
  289. Entry<K,V> e = prev;
  290. // 删除链表中的“键值对e”
  291. // 本质是“删除单向链表中的节点”
  292. while (e != null) {
  293. Entry<K,V> next = e.next;
  294. if (h == e.hash && e.equals(entry)) {
  295. modCount++;
  296. size--;
  297. if (prev == e)
  298. tab[i] = next;
  299. else
  300. prev.next = next;
  301. return e;
  302. }
  303. prev = e;
  304. e = next;
  305. }
  306. return null;
  307. }
  308. // 清空WeakHashMap,将所有的元素设为null
  309. public void clear() {
  310. while (queue.poll() != null)
  311. ;
  312. modCount++;
  313. Entry[] tab = table;
  314. for (int i = 0; i < tab.length; ++i)
  315. tab[i] = null;
  316. size = 0;
  317. while (queue.poll() != null)
  318. ;
  319. }
  320. // 是否包含“值为value”的元素
  321. public boolean containsValue(Object value) {
  322. // 若“value为null”,则调用containsNullValue()查找
  323. if (value==null)
  324. return containsNullValue();
  325. // 若“value不为null”,则查找WeakHashMap中是否有值为value的节点。
  326. Entry[] tab = getTable();
  327. for (int i = tab.length ; i-- > 0 ;)
  328. for (Entry e = tab[i] ; e != null ; e = e.next)
  329. if (value.equals(e.value))
  330. return true;
  331. return false;
  332. }
  333. // 是否包含null值
  334. private boolean containsNullValue() {
  335. Entry[] tab = getTable();
  336. for (int i = tab.length ; i-- > 0 ;)
  337. for (Entry e = tab[i] ; e != null ; e = e.next)
  338. if (e.value==null)
  339. return true;
  340. return false;
  341. }
  342. // Entry是单向链表。
  343. // 它是 “WeakHashMap链式存储法”对应的链表。
  344. // 它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数
  345. private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V> {
  346. private V value;
  347. private final int hash;
  348. // 指向下一个节点
  349. private Entry<K,V> next;
  350. // 构造函数。
  351. Entry(K key, V value,
  352. ReferenceQueue<K> queue,
  353. int hash, Entry<K,V> next) {
  354. super(key, queue);
  355. this.value = value;
  356. this.hash = hash;
  357. this.next = next;
  358. }
  359. public K getKey() {
  360. return WeakHashMap.<K>unmaskNull(get());
  361. }
  362. public V getValue() {
  363. return value;
  364. }
  365. public V setValue(V newValue) {
  366. V oldValue = value;
  367. value = newValue;
  368. return oldValue;
  369. }
  370. // 判断两个Entry是否相等
  371. // 若两个Entry的“key”和“value”都相等,则返回true。
  372. // 否则,返回false
  373. public boolean equals(Object o) {
  374. if (!(o instanceof Map.Entry))
  375. return false;
  376. Map.Entry e = (Map.Entry)o;
  377. Object k1 = getKey();
  378. Object k2 = e.getKey();
  379. if (k1 == k2 || (k1 != null && k1.equals(k2))) {
  380. Object v1 = getValue();
  381. Object v2 = e.getValue();
  382. if (v1 == v2 || (v1 != null && v1.equals(v2)))
  383. return true;
  384. }
  385. return false;
  386. }
  387. // 实现hashCode()
  388. public int hashCode() {
  389. Object k = getKey();
  390. Object v = getValue();
  391. return ((k==null ? 0 : k.hashCode()) ^
  392. (v==null ? 0 : v.hashCode()));
  393. }
  394. public String toString() {
  395. return getKey() + "=" + getValue();
  396. }
  397. }
  398. // HashIterator是WeakHashMap迭代器的抽象出来的父类,实现了公共了函数。
  399. // 它包含“key迭代器(KeyIterator)”、“Value迭代器(ValueIterator)”和“Entry迭代器(EntryIterator)”3个子类。
  400. private abstract class HashIterator<T> implements Iterator<T> {
  401. // 当前索引
  402. int index;
  403. // 当前元素
  404. Entry<K,V> entry = null;
  405. // 上一次返回元素
  406. Entry<K,V> lastReturned = null;
  407. // expectedModCount用于实现fast-fail机制。
  408. int expectedModCount = modCount;
  409. // 下一个键(强引用)
  410. Object nextKey = null;
  411. // 当前键(强引用)
  412. Object currentKey = null;
  413. // 构造函数
  414. HashIterator() {
  415. index = (size() != 0 ? table.length : 0);
  416. }
  417. // 是否存在下一个元素
  418. public boolean hasNext() {
  419. Entry[] t = table;
  420. // 一个Entry就是一个单向链表
  421. // 若该Entry的下一个节点不为空,就将next指向下一个节点;
  422. // 否则,将next指向下一个链表(也是下一个Entry)的不为null的节点。
  423. while (nextKey == null) {
  424. Entry<K,V> e = entry;
  425. int i = index;
  426. while (e == null && i > 0)
  427. e = t[--i];
  428. entry = e;
  429. index = i;
  430. if (e == null) {
  431. currentKey = null;
  432. return false;
  433. }
  434. nextKey = e.get(); // hold on to key in strong ref
  435. if (nextKey == null)
  436. entry = entry.next;
  437. }
  438. return true;
  439. }
  440. // 获取下一个元素
  441. protected Entry<K,V> nextEntry() {
  442. if (modCount != expectedModCount)
  443. throw new ConcurrentModificationException();
  444. if (nextKey == null && !hasNext())
  445. throw new NoSuchElementException();
  446. lastReturned = entry;
  447. entry = entry.next;
  448. currentKey = nextKey;
  449. nextKey = null;
  450. return lastReturned;
  451. }
  452. // 删除当前元素
  453. public void remove() {
  454. if (lastReturned == null)
  455. throw new IllegalStateException();
  456. if (modCount != expectedModCount)
  457. throw new ConcurrentModificationException();
  458. WeakHashMap.this.remove(currentKey);
  459. expectedModCount = modCount;
  460. lastReturned = null;
  461. currentKey = null;
  462. }
  463. }
  464. // value的迭代器
  465. private class ValueIterator extends HashIterator<V> {
  466. public V next() {
  467. return nextEntry().value;
  468. }
  469. }
  470. // key的迭代器
  471. private class KeyIterator extends HashIterator<K> {
  472. public K next() {
  473. return nextEntry().getKey();
  474. }
  475. }
  476. // Entry的迭代器
  477. private class EntryIterator extends HashIterator<Map.Entry<K,V>> {
  478. public Map.Entry<K,V> next() {
  479. return nextEntry();
  480. }
  481. }
  482. // WeakHashMap的Entry对应的集合
  483. private transient Set<Map.Entry<K,V>> entrySet = null;
  484. // 返回“key的集合”,实际上返回一个“KeySet对象”
  485. public Set<K> keySet() {
  486. Set<K> ks = keySet;
  487. return (ks != null ? ks : (keySet = new KeySet()));
  488. }
  489. // Key对应的集合
  490. // KeySet继承于AbstractSet,说明该集合中没有重复的Key。
  491. private class KeySet extends AbstractSet<K> {
  492. public Iterator<K> iterator() {
  493. return new KeyIterator();
  494. }
  495. public int size() {
  496. return WeakHashMap.this.size();
  497. }
  498. public boolean contains(Object o) {
  499. return containsKey(o);
  500. }
  501. public boolean remove(Object o) {
  502. if (containsKey(o)) {
  503. WeakHashMap.this.remove(o);
  504. return true;
  505. }
  506. else
  507. return false;
  508. }
  509. public void clear() {
  510. WeakHashMap.this.clear();
  511. }
  512. }
  513. // 返回“value集合”,实际上返回的是一个Values对象
  514. public Collection<V> values() {
  515. Collection<V> vs = values;
  516. return (vs != null ? vs : (values = new Values()));
  517. }
  518. // “value集合”
  519. // Values继承于AbstractCollection,不同于“KeySet继承于AbstractSet”,
  520. // Values中的元素能够重复。因为不同的key可以指向相同的value。
  521. private class Values extends AbstractCollection<V> {
  522. public Iterator<V> iterator() {
  523. return new ValueIterator();
  524. }
  525. public int size() {
  526. return WeakHashMap.this.size();
  527. }
  528. public boolean contains(Object o) {
  529. return containsValue(o);
  530. }
  531. public void clear() {
  532. WeakHashMap.this.clear();
  533. }
  534. }
  535. // 返回“WeakHashMap的Entry集合”
  536. // 它实际是返回一个EntrySet对象
  537. public Set<Map.Entry<K,V>> entrySet() {
  538. Set<Map.Entry<K,V>> es = entrySet;
  539. return es != null ? es : (entrySet = new EntrySet());
  540. }
  541. // EntrySet对应的集合
  542. // EntrySet继承于AbstractSet,说明该集合中没有重复的EntrySet。
  543. private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
  544. public Iterator<Map.Entry<K,V>> iterator() {
  545. return new EntryIterator();
  546. }
  547. // 是否包含“值(o)”
  548. public boolean contains(Object o) {
  549. if (!(o instanceof Map.Entry))
  550. return false;
  551. Map.Entry e = (Map.Entry)o;
  552. Object k = e.getKey();
  553. Entry candidate = getEntry(e.getKey());
  554. return candidate != null && candidate.equals(e);
  555. }
  556. // 删除“值(o)”
  557. public boolean remove(Object o) {
  558. return removeMapping(o) != null;
  559. }
  560. // 返回WeakHashMap的大小
  561. public int size() {
  562. return WeakHashMap.this.size();
  563. }
  564. // 清空WeakHashMap
  565. public void clear() {
  566. WeakHashMap.this.clear();
  567. }
  568. // 拷贝函数。将WeakHashMap中的全部元素都拷贝到List中
  569. private List<Map.Entry<K,V>> deepCopy() {
  570. List<Map.Entry<K,V>> list = new ArrayList<Map.Entry<K,V>>(size());
  571. for (Map.Entry<K,V> e : this)
  572. list.add(new AbstractMap.SimpleEntry<K,V>(e));
  573. return list;
  574. }
  575. // 返回Entry对应的Object[]数组
  576. public Object[] toArray() {
  577. return deepCopy().toArray();
  578. }
  579. // 返回Entry对应的T[]数组(T[]我们新建数组时,定义的数组类型)
  580. public <T> T[] toArray(T[] a) {
  581. return deepCopy().toArray(a);
  582. }
  583. }
  584. }

说明:WeakHashMap和HashMap都是通过”拉链法”实现的散列表。它们的源码绝大部分内容都一样,这里就只是对它们不同的部分就是说明。

WeakReference是“弱键”实现的哈希表。它这个“弱键”的目的就是:实现对“键值对”的动态回收。当“弱键”不再被使用到时,GC会回收它,WeakReference也会将“弱键”对应的键值对删除。
“弱键”是一个“弱引用(WeakReference)”,在Java中,WeakReference和ReferenceQueue 是联合使用的。在WeakHashMap中亦是如此:如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 接着,WeakHashMap会根据“引用队列”,来删除“WeakHashMap中已被GC回收的‘弱键’对应的键值对”。
另外,理解上面思想的重点是通过 expungeStaleEntries() 函数去理解。

遍历方式

4.1 遍历WeakHashMap的键值对

第一步:根据entrySet()获取WeakHashMap的“键值对”的Set集合。
第二步:通过Iterator迭代器遍历“第一步”得到的集合。

// 假设map是WeakHashMap对象
// map中的key是String类型,value是Integer类型
Integer integ = null;
Iterator iter = map.entrySet().iterator();
while(iter.hasNext()) {
    Map.Entry entry = (Map.Entry)iter.next();
    // 获取key
    key = (String)entry.getKey();
        // 获取value
    integ = (Integer)entry.getValue();
}

4.2 遍历WeakHashMap的键

第一步:根据keySet()获取WeakHashMap的“键”的Set集合。
第二步:通过Iterator迭代器遍历“第一步”得到的集合。

// 假设map是WeakHashMap对象
// map中的key是String类型,value是Integer类型
String key = null;
Integer integ = null;
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
        // 获取key
    key = (String)iter.next();
        // 根据key,获取value
    integ = (Integer)map.get(key);
}

4.3 遍历WeakHashMap的值

第一步:根据value()获取WeakHashMap的“值”的集合。
第二步:通过Iterator迭代器遍历“第一步”得到的集合。

// 假设map是WeakHashMap对象
// map中的key是String类型,value是Integer类型
Integer value = null;
Collection c = map.values();
Iterator iter= c.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}

WeakHashMap遍历测试程序如下

import java.util.Map;
import java.util.Random;
import java.util.Iterator;
import java.util.WeakHashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Collection;

/*
 * @desc 遍历WeakHashMap的测试程序。
 *   (01) 通过entrySet()去遍历key、value,参考实现函数:
 *        iteratorHashMapByEntryset()
 *   (02) 通过keySet()去遍历key、value,参考实现函数:
 *        iteratorHashMapByKeyset()
 *   (03) 通过values()去遍历value,参考实现函数:
 *        iteratorHashMapJustValues()
 *
 * @author skywang
 */
public class WeakHashMapIteratorTest {

    public static void main(String[] args) {
        int val = 0;
        String key = null;
        Integer value = null;
        Random r = new Random();
        WeakHashMap map = new WeakHashMap();

        for (int i=0; i<12; i++) {
            // 随机获取一个[0,100)之间的数字
            val = r.nextInt(100);

            key = String.valueOf(val);
            value = r.nextInt(5);
            // 添加到WeakHashMap中
            map.put(key, value);
            System.out.println(" key:"+key+" value:"+value);
        }
        // 通过entrySet()遍历WeakHashMap的key-value
        iteratorHashMapByEntryset(map) ;

        // 通过keySet()遍历WeakHashMap的key-value
        iteratorHashMapByKeyset(map) ;

        // 单单遍历WeakHashMap的value
        iteratorHashMapJustValues(map);        
    }

    /*
     * 通过entry set遍历WeakHashMap
     * 效率高!
     */
    private static void iteratorHashMapByEntryset(WeakHashMap map) {
        if (map == null)
            return ;

        System.out.println("\niterator WeakHashMap By entryset");
        String key = null;
        Integer integ = null;
        Iterator iter = map.entrySet().iterator();
        while(iter.hasNext()) {
            Map.Entry entry = (Map.Entry)iter.next();

            key = (String)entry.getKey();
            integ = (Integer)entry.getValue();
            System.out.println(key+" -- "+integ.intValue());
        }
    }

    /*
     * 通过keyset来遍历WeakHashMap
     * 效率低!
     */
    private static void iteratorHashMapByKeyset(WeakHashMap map) {
        if (map == null)
            return ;

        System.out.println("\niterator WeakHashMap By keyset");
        String key = null;
        Integer integ = null;
        Iterator iter = map.keySet().iterator();
        while (iter.hasNext()) {
            key = (String)iter.next();
            integ = (Integer)map.get(key);
            System.out.println(key+" -- "+integ.intValue());
        }
    }


    /*
     * 遍历WeakHashMap的values
     */
    private static void iteratorHashMapJustValues(WeakHashMap map) {
        if (map == null)
            return ;

        Collection c = map.values();
        Iterator iter= c.iterator();
        while (iter.hasNext()) {
            System.out.println(iter.next());
       }
    }
}

WeakHashMap使用示例

下面通过实例来学习如何使用WeakHashMap

import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.Date;
import java.lang.ref.WeakReference;

/**
 * @desc WeakHashMap测试程序
 *
 * @author skywang
 * @email kuiwu-wang@163.com
 */
public class WeakHashMapTest {

    public static void main(String[] args) throws Exception {
        testWeakHashMapAPIs();
    }

    private static void testWeakHashMapAPIs() {
        // 初始化3个“弱键”
        String w1 = new String("one");
        String w2 = new String("two");
        String w3 = new String("three");
        // 新建WeakHashMap
        Map wmap = new WeakHashMap();

        // 添加键值对
        wmap.put(w1, "w1");
        wmap.put(w2, "w2");
        wmap.put(w3, "w3");

        // 打印出wmap
        System.out.printf("\nwmap:%s\n",wmap );

        // containsKey(Object key) :是否包含键key
        System.out.printf("contains key two : %s\n",wmap.containsKey("two"));
        System.out.printf("contains key five : %s\n",wmap.containsKey("five"));

        // containsValue(Object value) :是否包含值value
        System.out.printf("contains value 0 : %s\n",wmap.containsValue(new Integer(0)));

        // remove(Object key) : 删除键key对应的键值对
        wmap.remove("three");

        System.out.printf("wmap: %s\n",wmap );



        // ---- 测试 WeakHashMap 的自动回收特性 ----

        // 将w1设置null。
        // 这意味着“弱键”w1再没有被其它对象引用,调用gc时会回收WeakHashMap中与“w1”对应的键值对
        w1 = null;
        // 内存回收。这里,会回收WeakHashMap中与“w1”对应的键值对
        System.gc();

        // 遍历WeakHashMap
        Iterator iter = wmap.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry en = (Map.Entry)iter.next();
            System.out.printf("next : %s - %s\n",en.getKey(),en.getValue());
        }
        // 打印WeakHashMap的实际大小
        System.out.printf(" after gc WeakHashMap size:%s\n", wmap.size());
    }
}

运行结果

wmap:{three=w3, one=w1, two=w2}
contains key two : true
contains key five : false
contains value 0 : false
wmap: {one=w1, two=w2}
next : two - w2
 after gc WeakHashMap size:1

WeakHashMap和HashMap的区别

前面对HashMap的源码和WeakHashMap的源码分别进行了分析。在WeakHashMap源码分析博文中有对与HashMap区别的比较,但是不够具体系统。加上本人看了一些相关的博文,发现了一些好的例子来说明这两者的区别,因此,就有了这篇博文。

WeakHashMap和HashMap一样,WeakHashMap也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以为null。不过WeakHashMap的键是“弱键”(注:源码中Entry中的定义是这样的:private static class Entry extends WeakReference implements Map.Entry,即Entry实现了WeakReference类),当WeakHashMap某个键不再正常使用时,会被从WeakHashMap自动删除。更精确的说,对于一个给定的键,其映射的存在并不能阻止垃圾回收器对该键的丢弃,这就使该键称为被终止的,被终止,然后被回收,这样,这就可以认为该键值对应该被WeakHashMap删除。因此,WeakHashMap使用了弱引用作为内部数据的存储方案,,WeakHashMap可以作为简单缓存表的解决方案,当系统内存不足时,垃圾收集器会自动的清除没有在任何其他地方被引用的键值对。如果需要用一张很大的Map作为缓存表时,那么可以考虑使用WeakHashMap。

从源码的角度,我们来分析下上面这段话是如何来工作的??

在WeakHashMap实现中,借用了ReferenceQueue这个“监听器”来保存被GC回收的”弱键”,然后在每次使用WeakHashMap时,就在WeakHashMap中删除ReferenceQueue中保存的键值对。即WeakHashMap的实现是通过借用
ReferenceQueue这个“监听器”来优雅的实现自动删除那些引用不可达的key的。关于ReferenceQueue会在下篇博文中进行介绍

具体如下:

WeakHashMap是通过数组table保存Entry(键值对);每个Entry实际上就是一个链表来实现的。当某“弱键”不再被其它对象引用,就会被GC回收时,这个“弱键”也同时被添加到ReferenceQueue队列中。当下一步我们需要操作WeakHashMap时,会先同步table、queue,table中保存了全部的键值对,而queue中保存的是GC回收的键值对;同步他们,就是删除table中被GC回收的键值对。

源码中完成“删除”操作的函数代码如下:

    /**
     * Expunges stale entries from the table.
     *翻译:删除过时的条目,即将ReferenceQueue队列中的对象引用全部在table中给删除掉
     *思路:如何删除一个table的节点e,方法为:首先计算e的hash值,接着根据hash值找到其在table的位置,然后遍历链表即可。
     */
    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;
                }
            }
        }
    }

例子说明1:往一个WeakHashMap中添加大量的元素

上面说的可能比较空,比如为什么可以作为缓冲表呀之类,可能看一个实际例子之后我们就可以更好的理解上面的两段话
第一段代码,就是HashMap的应用,往HashMap中存放一系列很大的数据。

    public class TestHashMap {

        public static void main(String[] args){
            Map<Integer,byte[]> hashMap = new HashMap<Integer,byte[]>();
            for(int i=0;i<100000;i++){
                hashMap.put(i, new byte[i]);
            }
        }
    }

第二段代码,就是WeakHashMap的应用,往WeakHashMap中存放与上例HashMap相同的数据。

    public class TestWeakHashMap {

        public static void main(String[] args){
            Map<Integer,byte[]> weakHashMap = new WeakHashMap<Integer,byte[]>();
            for(int i=0;i<100000;i++){
                weakHashMap.put(i, new byte[i]);
            }
        }
    }

运行上面的两段代码,发现,第一段代码是不能正常工作的,会抛“java.lang.OutOfMemoryError: Java heap space”,而第二段代码就可以正常工作。

以上就说明了,WeakHashMap当系统内存不足时,垃圾收集器会自动的清除没有在任何其他地方被引用的键值对,因此可以作为简单缓存表的解决方案。而HashMap就没有上述功能。

但是,如果WeakHashMap的key在系统内持有强引用,那么WeakHashMap就退化为了HashMap,所有的表项都不会被垃圾回收器回收。

例子说明2:一系列的WeakHashMap,往每个WeakHashMap中只添加一个大的数据

看如下的例子,例子的代码是,在for循环中每次都new一个WeakHashMap对象,且每个对象实例中只添加一个key和value都是大的数组对象。看会出现上面现象???

    public class TestWeakHashMap3 {

        public static void main(String[] args){
            List<WeakHashMap<Integer[][], Integer[][]>> maps = new ArrayList<WeakHashMap<Integer[][],Integer[][]>>();   
            int totalNum = 10000;
            for(int i=0;i<totalNum;i++){
                WeakHashMap<Integer[][], Integer[][]> w = new WeakHashMap<Integer[][], Integer[][]>();
                w.put(new Integer[1000][1000], new Integer[1000][1000]);
                maps.add(w);
                System.gc();//显示gc
                System.out.println(i);
            }
        }
    }

上面的运行结果如下:即由于空间不足报异常错误。

    /*
     * 运行结果:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
     *  at com.wrh.testhashmap.TestWeakHashMap3.main(TestWeakHashMap3.java:15)
     * */

而如下的代码确能够正常工作,这两段代码的区别在于下面这段代码中调用了WeakHashMap的size()方法。

    public class TestWeakHashMap5 {

        public static void main(String[] args){
            List<WeakHashMap<Integer[][], Integer[][]>> maps = new ArrayList<WeakHashMap<Integer[][],Integer[][]>>();   
            int totalNum = 10000;
            for(int i=0;i<totalNum;i++){
                WeakHashMap<Integer[][], Integer[][]> w = new WeakHashMap<Integer[][], Integer[][]>();
                w.put(new Integer[1000][1000], new Integer[1000][1000]);
                maps.add(w);
                System.gc();
                for(int j=0;j<i;j++){
                    System.out.println("第"+j+"个map的大小为:"+maps.get(j).size());
                }

            }
        }
    }

可能有人要问了,不是说WeakHashMap具有会自动进行垃圾回收,第一种情况为什么会报OOM异常了,第二种情况会正常工作呢????

首先要说明的是,第一段代码并不是没有执行GC,而是仅对WeakHashMap中的key中的Integer数组进行了回收,而value依然保持。我们先来看如下的例子:将value换成一个小的对象Object,就会证明这一点内容。

    public static void main(String[] args){
        List<WeakHashMap<Integer[][], Object>> maps = new ArrayList<WeakHashMap<Integer[][],Object>>(); 
        int totalNum = 10000;
        for(int i=0;i<totalNum;i++){
            WeakHashMap<Integer[][], Object> w = new WeakHashMap<Integer[][], Object>();
            w.put(new Integer[1000][1000], new Object());
            maps.add(w);
            System.gc();
            System.out.println(i);
        }
    }

上面的代码运行时没有任何问题的,这也就证明了key中的Integer数组确实被回收了,那为何key中的reference数据被GC,却没有触发WeakHashMap去做清理整个key的操作呢??

原因是在于:在进行put操作后,虽然GC将WeakReference的key中的Integer数组回收了,并将事件通过到了ReferenceQueue,但是后续却没有相应的动作去触发WeakHashMap来进行处理ReferenceQueue,所以WeakReference包装的key依然存在在WeakHashMap中,其对应的value也就依然存在。

但是在WeakHashMap中会删除那些已经被GC的键值对在源码中是通过调用expungeStaleEntries函数来完成的,而这个函数只在WeakHashMap的put、get、size()等方法中才进行了调用。因此,只有put、get、size()方法来可以触发WeakHashMap来进行处理ReferenceQueue。

以上也就是为什么上面的第二段代码中调用下WeakHashMap的size()方法之后就不会报异常能正常工作的原因。