容器--WeakHashMap
一、概述
WeakHashMap是Map的一种,根据其类的命令可以知道,它结合了WeakReference和HashMap的两种特点,从而构造出了一种Key可以自动回收的Map。
前面我们已经介绍了WeakReference的特点及实现原理,以及HashMap的实现原理,所以我们本文重点介绍WeakReference的在这类Map中的使用,以及其和原来的HashMap有什么不一样的地方。
二、实现原理分析
还是按之前的方式,我们从几个方面去分析Map的具体实现。
1. 初始化
WeakHashMap和普通的HashMap的初始化方式类似,可以指定初始容量和加载因子,若不指定则使用默认值,也可以用一个现有的Map来填充,如下:

第一个构造函数的实现方式如下:
public WeakHashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Initial Capacity: "+
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load factor: "+
loadFactor);
int capacity = 1;
//找到一个最合适的大小
while (capacity < initialCapacity)
capacity <<= 1;
table = newTable(capacity);
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
}
从上面的实现来看没有什么特别的,就是根据参数来计算了实际的容量和阈值。
2. 添加元素
和前几篇一样,我们还是来看下put的实现:
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;
}
这段代码大体上看来,和HashMap的实现是差不多的,为了更好的便于对于,我们把HashMap里的相关实现也贴出来:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);//table中的位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//entry相同的条件 , hash相同 , key的引用相同,或者equals()
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//新增
addEntry(hash, key, value, i);
return null;
}
接下来,我们先总结一下有哪些主要的区别,然后再详细分析WeakHashMap为什么要这样做。
通过对比代码,我们得知主要的区别如下:
1)WeakHashMap没有空表判断:这个很好理解,因为初始化时就已经创建了Entry数组,所以没必要判断空表
2)对key进行了maskNull封装:由于这个实现比较简单就不贴代码了,前面我们也介绍过maskNull的用法,主要用在那些原生的null表示不存在,但又需要支持null值的场合下,也就是说,用一个特殊的“null”,来代表对于空指针key的支持。因为WeakHashMap中的key是弱引用构造的,作为弱引用的引用对象,其自身是不能为null的。
3)没有直接使用table,而是使用了getTable(): 这个下面详细解释
4)使用了eq()来判断,且使用e.get()来获取key: 这个也好理解,弱引用对象就是通过get()方法来获取其所引用的对象,这里的key就是其引用对象。
5)在创建一个新的Entry时,多了一个queue的参数:这个queue的类型为ReferenceQueue类型,前面我们介绍过,这个是用于存储引用目标已经被回收的那些弱引用。
经过上面的分析,我们发现其它的都比较好懂,就是不清楚getTable()都做了些什么事,下面看一下其源码实现:
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
}
/**
* 根据英文的解释,移除陈旧的数据
* 这个方法的具体实际其实比较简单,就是将遍历队列中的每一个元素,这个元素就是一个entry,
* 在内部数组中找到它,并将其移除,移除比较简单,就是将值置为空.
*
* 那么通过这个反推,queue里面存储的就是所有失效的key了.
* Expunges stale entries from the table.
*/
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;//it's a entry
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;
}
}
}
}
上面的代码是一个双重循环,看似复杂,但如果了解了queue的定义,我们理解起来也就方便了。前面提到queue里存储的是一些弱引用实例,它们共同的特点是其引用目标已经被垃圾回收器回收。
在这个大前提下,这段代码做了以下几件事:
1)依次取出queue中的所有元素进行处理直到queue为空
2)每个出队的元素都是map中的一个entity,所以可以根据其hash值找到对应的存储位置。
3)判断entity的位置,根据其是否为散列表的表头来决定怎么将其从列表中移除了,当然,由于其key已经被回收,所以只需将其value置为null即可。
4)处理完毕后,表示存储中少了一个entity,size-1
所以这个方法就是完成了对于WeakHashMap的自动回收元素的处理,如果不这样处理则仍然有内存泄露的风险,另外大小也就不准确了。这个方法是典型的对于弱引用失效队列的监控和处理,值得学习。
3. 删除
删除的方法如下:
public V remove(Object key) {
Object k = maskNull(key);
int h = hash(k);
Entry<K,V>[] tab = getTable();
int i = indexFor(h, tab.length);
Entry<K,V> prev = tab[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
if (h == e.hash && eq(k, e.get())) {
modCount++;
size--;
if (prev == e)
tab[i] = next;
else
prev.next = next;
return e.value;
}
prev = e;
e = next;
}
return null;
}
可见删除方法的逻辑也跟之前的HashMap差不多,惟一变化的就是在table的获取上使用了getTable(), 而这个方法我们前面已经介绍了。
如果有兴趣,还可以再看下其它的处理方法,基本上所有的操作都会先执行getTable(),来对自动失效的key进行相应的清理。在此就不一一分析。
另外我们可以看到Entity实际上就是一个弱引用对象,其引用的目标为key, 代码截图如下:

至此,对于WeakHashMap的实现原理便一目了然了。
三、总结
WeakHashMap由于其弱引用的特点,使得其非常适合用于做缓存的存储结构,这样当缓存中的数据不再使用之后,垃圾回收器可以自动回收,从而实现不需要人工干预且能自动释放内存的效果。
同时,这也是一个学习如何使用弱引用的很好的例子。
容器--WeakHashMap的更多相关文章
- Java容器解析系列(13) WeakHashMap详解
关于WeakHashMap其实没有太多可说的,其与HashMap大致相同,区别就在于: 对每个key的引用方式为弱引用; 关于java4种引用方式,参考java Reference 网上很多说 弱引用 ...
- 【Java心得总结七】Java容器下——Map
我将容器类库自己平时编程及看书的感受总结成了三篇博文,前两篇分别是:[Java心得总结五]Java容器上——容器初探和[Java心得总结六]Java容器中——Collection,第一篇从宏观整体的角 ...
- [Think In Java]基础拾遗3 - 容器、I/O、NIO、序列化
目录 第十一章 持有对象第十七章 容器深入研究第十八章 Java I/O系统 第十一章 持有对象 1. java容器概览 java容器的两种主要类型(它们之间的主要区别在于容器中每个“槽”保存的元素个 ...
- Java入门记(五):容器关系的梳理(下)——Map
注意:阅读本文及相关源码时,需要数据结构相关知识,包括:哈希表.链表.红黑树. Map是将键(key)映射到值(value)的对象.不同的映射不能包含相同的键:每个键最多只能映射到一个值.下图是常见M ...
- Java - 容器详解
一.ArrayList 长度可变数组,类似于c++ STL中的vector. 元素以线性方式连续存储,内部允许存放重复元素. 允许对元素进行随机的快速访问,但是向ArrayList中插入和删除元素的速 ...
- java容器(java编程思想第四版-读书笔记)
容器类库图 List(interface) 次序是List最重要的特点:它保证维护元素特定的顺序.List为Collection添加了许多方法,使得能够向List中间插入与移除元素.(这只推荐L ...
- Java容器之旅:容器基础知识总结
下图展示了Java容器类库的完备图,包括抽象类和遗留构件(不包括Queue的实现). 常用的容器用黑色粗线框表示,点线框表示接口,虚线框表示抽象类,实线框表示类,空心箭头表示实现关系.Produce表 ...
- JAVA的容器---List,Map,Set (转)
JAVA的容器---List,Map,Set Collection├List│├LinkedList│├ArrayList│└Vector│ └Stack└SetMap├Hashtable├HashM ...
- Java集合容器简介
Java集合容器主要有以下几类: 1,内置容器:数组 2,list容器:Vetor,Stack,ArrayList,LinkedList, CopyOnWriteArrayList(1.5),Attr ...
随机推荐
- Android开发学习之路-Service和Activity的通信
在很多时候,Service都不仅仅需要在后台运行,还需要和Activity进行通信,或者接受Activity的指挥,如何来实现,来看代码. 定义一个服务 // 创建一个服务,然后在onBind()中返 ...
- Android Studio导入System Library步骤
转载请注明出处:http://www.cnblogs.com/cnwutianhao/p/6242170.html 请尊重知识产权!!! 同步更新到CSDN:http://blog.csdn.net ...
- iOS--------坐标系统(UIView的frame、bounds跟center属性)
1.概要翻开ios官方开发文档,赫然发现上面对这三个属性的解释如下: frame:描述当前视图在其父视图中的位置和大小. bounds:描述当前视图在其自身坐标系统中的位置和大小. center:描述 ...
- rabbitMQ第四篇:远程调用
前言:前面我们讲解的都是本地服务器,现在如果需要远程计算机上运行一个函数,等待结果.这就是一个不同的故事了,这种模式通常被称为远程过程调用或者RPC. 本章教程我们使用RabbitMQ搭建一个RPC系 ...
- 截取js数组中某段值(slice)
// var a = [1,2,3]; // console.log(a.slice(1)); >>[2, 3] 从索引1开始截取. // console.log(a.slice(1,2) ...
- jQuery尺寸算法
我们默认都统一是采用offsetWidth或者offsetHeight取值了,但我们知道关于这2个尺寸的算法是这样的: offsetWidth = border-left-width + paddin ...
- 深入理解javascript函数系列第二篇——函数参数
× 目录 [1]arguments [2]内部属性 [3]函数重载[4]参数传递 前面的话 javascript函数的参数与大多数其他语言的函数的参数有所不同.函数不介意传递进来多少个参数,也不在乎传 ...
- SQL*Loader之CASE6
CASE6 1. SQL脚本 [oracle@node3 ulcase]$ cat ulcase6.sql set termout off rem host write sys$output &quo ...
- javaWeb之maven多数据库环境的配置信息
在使用maven构建的web项目里,不管采用的是什么orm框架,数据库写死了必然不是最灵活的方式.所以通过maven 的buid方式可以动态的分配数据库信息 比如在jdbc.properties中,可 ...
- 互动教程,让你5分钟掌握 Flexbox 布局模式
Flexbox 布局模块目前是 W3C 候选标准,旨在提供一个更有效的方式进行布局,对齐和为容器里的项目分配空间,即使它们的大小是未知的或者动态的.这里分享一个基于 Knockout.js 构建的互动 ...