zoukankan      html  css  js  c++  java
  • 容器--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由于其弱引用的特点,使得其非常适合用于做缓存的存储结构,这样当缓存中的数据不再使用之后,垃圾回收器可以自动回收,从而实现不需要人工干预且能自动释放内存的效果。

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

  • 相关阅读:
    数字证书学习笔记
    在微服务中使用领域事件
    用Gradle构建Spring Boot项目
    七言 朱雀
    作为分享者
    Spring Framework学习要点摘抄
    Servlet 3.0/3.1 中的异步处理
    Java集合学习笔记
    Java垃圾回收学习笔记
    你究竟有多了解Spring?
  • 原文地址:https://www.cnblogs.com/macs524/p/5844207.html
Copyright © 2011-2022 走看看