容器--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的实现:

 1 public V put(K key, V value) {
 2         Object k = maskNull(key);
 3         int h = hash(k);
 4         Entry<K,V>[] tab = getTable();
 5         int i = indexFor(h, tab.length);
 6
 7         for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
 8             if (h == e.hash && eq(k, e.get())) {
 9                 V oldValue = e.value;
10                 if (value != oldValue)
11                     e.value = value;
12                 return oldValue;
13             }
14         }
15
16         modCount++;
17         Entry<K,V> e = tab[i];
18         tab[i] = new Entry<>(k, value, queue, h, e);
19         if (++size >= threshold)
20             resize(tab.length * 2);
21         return null;
22     }  

这段代码大体上看来,和HashMap的实现是差不多的,为了更好的便于对于,我们把HashMap里的相关实现也贴出来:

 1 public V put(K key, V value) {
 2         if (table == EMPTY_TABLE) {
 3             inflateTable(threshold);
 4         }
 5         if (key == null)
 6             return putForNullKey(value);
 7         int hash = hash(key);
 8         int i = indexFor(hash, table.length);//table中的位置
 9         for (Entry<K,V> e = table[i]; e != null; e = e.next) {
10             Object k;
11
12             //entry相同的条件 , hash相同 , key的引用相同,或者equals()
13             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
14                 V oldValue = e.value;
15                 e.value = value;
16                 e.recordAccess(this);
17                 return oldValue;
18             }
19         }
20
21         modCount++;
22
23         //新增
24         addEntry(hash, key, value, i);
25         return null;
26     }

接下来,我们先总结一下有哪些主要的区别,然后再详细分析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()都做了些什么事,下面看一下其源码实现:

 1     private Entry<K,V>[] getTable() {
 2         expungeStaleEntries();
 3         return table;
 4     }
 5
 6
 7 /**
 8      * 根据英文的解释,移除陈旧的数据
 9      * 这个方法的具体实际其实比较简单,就是将遍历队列中的每一个元素,这个元素就是一个entry,
10      * 在内部数组中找到它,并将其移除,移除比较简单,就是将值置为空.
11      *
12      * 那么通过这个反推,queue里面存储的就是所有失效的key了.
13      * Expunges stale entries from the table.
14      */
15     private void expungeStaleEntries() {
16         for (Object x; (x = queue.poll()) != null; ) {
17             synchronized (queue) {
18                 @SuppressWarnings("unchecked")
19                 Entry<K,V> e = (Entry<K,V>) x;//it‘s a entry
20                 int i = indexFor(e.hash, table.length);
21
22                 Entry<K,V> prev = table[i];
23                 Entry<K,V> p = prev;
24                 while (p != null) {
25                     Entry<K,V> next = p.next;
26                     if (p == e) {
27                         if (prev == e)
28                             table[i] = next;
29                         else
30                             prev.next = next;
31                         // Must not null out e.next;
32                         // stale entries may be in use by a HashIterator
33                         e.value = null; // Help GC
34                         size--;
35                         break;
36                     }
37                     prev = p;
38                     p = next;
39                 }
40             }
41         }
42     }

上面的代码是一个双重循环,看似复杂,但如果了解了queue的定义,我们理解起来也就方便了。前面提到queue里存储的是一些弱引用实例,它们共同的特点是其引用目标已经被垃圾回收器回收。

在这个大前提下,这段代码做了以下几件事:

1)依次取出queue中的所有元素进行处理直到queue为空

2)每个出队的元素都是map中的一个entity,所以可以根据其hash值找到对应的存储位置。

3)判断entity的位置,根据其是否为散列表的表头来决定怎么将其从列表中移除了,当然,由于其key已经被回收,所以只需将其value置为null即可。

4)处理完毕后,表示存储中少了一个entity,size-1

所以这个方法就是完成了对于WeakHashMap的自动回收元素的处理,如果不这样处理则仍然有内存泄露的风险,另外大小也就不准确了。这个方法是典型的对于弱引用失效队列的监控和处理,值得学习。

3. 删除

删除的方法如下:

 1  public V remove(Object key) {
 2         Object k = maskNull(key);
 3         int h = hash(k);
 4         Entry<K,V>[] tab = getTable();
 5         int i = indexFor(h, tab.length);
 6         Entry<K,V> prev = tab[i];
 7         Entry<K,V> e = prev;
 8
 9         while (e != null) {
10             Entry<K,V> next = e.next;
11             if (h == e.hash && eq(k, e.get())) {
12                 modCount++;
13                 size--;
14                 if (prev == e)
15                     tab[i] = next;
16                 else
17                     prev.next = next;
18                 return e.value;
19             }
20             prev = e;
21             e = next;
22         }
23
24         return null;
25     }

可见删除方法的逻辑也跟之前的HashMap差不多,惟一变化的就是在table的获取上使用了getTable(), 而这个方法我们前面已经介绍了。

如果有兴趣,还可以再看下其它的处理方法,基本上所有的操作都会先执行getTable(),来对自动失效的key进行相应的清理。在此就不一一分析。

另外我们可以看到Entity实际上就是一个弱引用对象,其引用的目标为key, 代码截图如下:

至此,对于WeakHashMap的实现原理便一目了然了。

三、总结

  WeakHashMap由于其弱引用的特点,使得其非常适合用于做缓存的存储结构,这样当缓存中的数据不再使用之后,垃圾回收器可以自动回收,从而实现不需要人工干预且能自动释放内存的效果。

  同时,这也是一个学习如何使用弱引用的很好的例子。

时间: 2024-10-02 01:14:51

容器--WeakHashMap的相关文章

Thinking in Java:容器深入研究

1.虚线框表示Abstract类,图中大量的类的名字都是以Abstract开头的,它们仅仅是部分实现了特定接口的工具,因此创建时能够选择从Abstract继承. Collections中的实用方法:挑几个经常使用的: 1. reverse(List):逆转次序 2. rotate(List,int distance)全部元素向后移动distance个位置,将末尾元素循环到前面来(用了三次reverse) 3.sort(List,Comparator) 排序,可依照自己的Comparator 4.

java容器总结

容器大体分为两类:Collection和Map. 区别: 1 Collection中存储了一组对象,而Map存储键值对. 2 Collection的子类都实现了iterator这个方法,该方法能返回一个Iterator对象. Collection的具体实现包括List和Set. 区别: 1 List是有序的,元素可以重复的,Set是无序的,元素不可以重复. 2 Set的value最多只能存一个null. List的具体实现包括LinkedList,ArrayList,Vector和Stack.

[Think In Java]基础拾遗3 - 容器、I/O、NIO、序列化

目录 第十一章 持有对象第十七章 容器深入研究第十八章 Java I/O系统 第十一章 持有对象 1. java容器概览 java容器的两种主要类型(它们之间的主要区别在于容器中每个“槽”保存的元素个数):Collection和Map. (1)Collection是一个独立元素的序列,这些元素都服从一条或者多条规则.Collection概括了序列的概念——一种存放一组对象的方式. List:按照插入的顺序保存元素(ArrayList,LinkedList) Set:不能有重复元素(HashSet

持有对象:总结JAVA中的容器,迭代器

JAVA使用术语“Collection”来指代那些表示集合的对象,JAVA提供的接口很多,首先我们先来记住他们的层次结构: java集合框架的基本接口/类层次结构 java.util.Collection [I] +--java.util.List [I] +--java.util.ArrayList [C] +--java.util.LinkedList [C] +--java.util.Vector [C] +--java.util.Stack [C] +--java.util.Set [I

Java基础:容器

转载请注明出处:jiq?钦's technical Blog 一.Collection:存放独立元素 Collection中的接口都是可选操作,其实现类 并不一定实现了其所有接口,这是为了防止"接口爆炸".最常见的Unsupported Operation 都源自于背后由固定尺寸的数据结构支持的容器,比如使用ArrayList.asList将数组转换成List的时候,就会得到这样的容器.  (1)Collection之List List较之Collection增加了很多额外的接口.特别

容器深入研究 --- 理解Map

通常的: 映射表(也称关联数组)的基本思想是它维护的键-值(对)关联,因此你可以使用键来查找值. 标准的Java类库中包含了Map的几种实现,包括:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap. 它们都有同样的基本接口Map,但是行为特性各不相同,这表现在效率.键值对的保存及呈现次序.对象的保存周期.映射表如何在多线程程序中工作的判定"键"等价的策略方面.Map接口实现的数量应该让

Java容器-引用分类与部分Map用法

一.目录 1.引用分类 2.了解WeakHashMap.IdentityHashMap.EnumMap 3.同步控制与只读设置 二.代码实现 1.引用分类(面试) 强引用(StrongReference):引用指向对象,gc运行时,不回收. 弱引用(SoftReference):gc运行时可能回收(当jvm内存不足时). 软引用(WeakReference):当gc运行时,对象回收. 虚引用(phantomReference):类似于无银用,主要跟踪对象被回收的对象,不能单独使用,要与Refer

java常用容器(集合)的总结

一开始接触容器时,总感觉里面东西很多很难.学完总结一下,常用的无非以下几种,方法也无需全部记住,记住几个关键的即可,其中红色的方法务必记住. Collection                                           ├List                                      │   ├LinkedList                              │   ├ArrayList │   └Vector           

Java容器之旅:容器基础知识总结

下图展示了Java容器类库的完备图,包括抽象类和遗留构件(不包括Queue的实现). 常用的容器用黑色粗线框表示,点线框表示接口,虚线框表示抽象类,实线框表示类,空心箭头表示实现关系.Produce表示任意的Map对象可以生成Collection对象,任意的Collection对象可以生成Iterator对象. 最后以表格的形式总结List.Set.Map接口及各实现类的特性:   特性 实现类 实现类特性 对放置的元素的要求 List 线性.有序的存储容器,可通过索引访问元素get(n) Ar