java集合的底层原理(Map的底层原理(WeakHashMap) 四)

「爱情、让人受尽委屈。」 2023-02-17 03:15 133阅读 0赞

一、概念相关

  1. WeakHashMap ,从名字上看, 直接翻译就知道,这是一种弱的HashMap,那这个弱字表面看起来有点不好理解,其实这是跟对象引用相关的,先熟悉了对象引用原理,再来理解这个弱字就很容易了,如不明白对象引用原理,大家可以看看这篇博文 J[ava中的对象、对象引用及对象引用分类][ava]

弄明白了对象引用原理,就明白我们的WeakHashMap是基于弱引用的,也就是说只要垃圾回收机制一开启,就直接开始了扫荡,看见了就清除。

那我们为什么需要 WeakHashMap 呢?

WeakHashMap正是由于使用的是弱引用,因此它的对象可能被随时回收。更直观的说,当使用 WeakHashMap 时,即使没有删除任何元素,它的尺寸、get方法也可能不一样。比如:

(1)调用两次size()方法返回不同的值;第一次为10,第二次就为8了。

(2)两次调用isEmpty()方法,第一次返回false,第二次返回true;

(3)两次调用containsKey()方法,第一次返回true,第二次返回false;

(4)两次调用get()方法,第一次返回一个value,第二次返回null;

咋一想,这种飘忽不定的东西好像没什么用,试想一下,你准备使用WeakHashMap保存一些数据,写着写着都没了,那还保存个啥呀。

不过有一种场景,最喜欢这种飘忽不定、一言不合就删除的东西。那就是缓存。在缓存场景下,由于内存是有限的,不能缓存所有对象,因此就需要一定的删除机制,淘汰掉一些对象。

二、工作原理

1、WeakHashMap为什么具有弱引用的特点:随时被回收对象

这个问题就比较简单了,我们的目的主要是验证。WeakHashMap是基于弱引用的,肯定就具有了弱引用的性质。我们去他的部分源码中看一下:

  1. public class WeakHashMap<K,V>
  2. extends AbstractMap<K,V>
  3. implements Map<K,V> {
  4. /**
  5. * The default initial capacity -- MUST be a power of two.
  6. */
  7. private static final int DEFAULT_INITIAL_CAPACITY = 16;
  8. /**
  9. * The maximum capacity, used if a higher value is implicitly specified
  10. * by either of the constructors with arguments.
  11. * MUST be a power of two <= 1<<30.
  12. */
  13. private static final int MAXIMUM_CAPACITY = 1 << 30;
  14. /**
  15. * The load factor used when none specified in constructor.
  16. */
  17. private static final float DEFAULT_LOAD_FACTOR = 0.75f;
  18. /**
  19. * Reference queue for cleared WeakEntries
  20. */
  21. private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
  22. /**
  23. * The table, resized as necessary. Length MUST Always be a power of two.
  24. */
  25. Entry<K,V>[] table;
  26. public WeakHashMap(int initialCapacity, float loadFactor) {
  27. if (initialCapacity < 0)
  28. throw new IllegalArgumentException("Illegal Initial Capacity: "+
  29. initialCapacity);
  30. if (initialCapacity > MAXIMUM_CAPACITY)
  31. initialCapacity = MAXIMUM_CAPACITY;
  32. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  33. throw new IllegalArgumentException("Illegal Load factor: "+
  34. loadFactor);
  35. int capacity = 1;
  36. while (capacity < initialCapacity)
  37. capacity <<= 1;
  38. table = newTable(capacity);
  39. this.loadFactor = loadFactor;
  40. threshold = (int)(capacity * loadFactor);
  41. }
  42. private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
  43. V value;
  44. final int hash;
  45. Entry<K,V> next;
  46. /**
  47. * Creates new entry.
  48. */
  49. Entry(Object key, V value,
  50. ReferenceQueue<Object> queue,
  51. int hash, Entry<K,V> next) {
  52. super(key, queue);
  53. this.value = value;
  54. this.hash = hash;
  55. this.next = next;
  56. }
  57. }

从这里我们可以看到其内部的Entry继承了WeakReference,也就是弱引用,所以就具有了弱引用的特点。不过还要注意一点,那就是ReferenceQueue,他的作用是GC会清理掉对象之后,引用对象会被放到ReferenceQueue中。

2、WeakHashMap中的Entry被GC后,WeakHashMap是如何将其移除的?

意思是某一个Entry突然被垃圾回收了,这之后WeakHashMap肯定就不能保留这个Entry了,那他是如何将其移除的呢?

WeakHashMap内部有一个expungeStaleEntries函数,在这个函数内部实现移除其内部不用的entry从而达到的自动释放内存的目的。因此我们每次访问WeakHashMap的时候,都会调用这个expungeStaleEntries函数清理一遍。这也就是为什么前两次调用WeakHashMap的size()方法有可能不一样的原因。我们可以看看是如何实现的:

  1. private void expungeStaleEntries() {
  2. for (Object x; (x = queue.poll()) != null; ) {
  3. synchronized (queue) {
  4. @SuppressWarnings("unchecked")
  5. Entry<K,V> e = (Entry<K,V>) x;
  6. int i = indexFor(e.hash, table.length);
  7. Entry<K,V> prev = table[i];
  8. Entry<K,V> p = prev;
  9. while (p != null) {
  10. Entry<K,V> next = p.next;
  11. if (p == e) {
  12. if (prev == e)
  13. table[i] = next;
  14. else
  15. prev.next = next;
  16. // Must not null out e.next;
  17. // stale entries may be in use by a HashIterator
  18. e.value = null; // Help GC
  19. size--;
  20. break;
  21. }
  22. prev = p;
  23. p = next;
  24. }
  25. }
  26. }
  27. }

首先GC每次清理掉一个对象之后,引用对象会被放到ReferenceQueue中。然后遍历这个queue进行删除即可。

当然。WeakHashMap的增删改查操作都会直接或者间接的调用expungeStaleEntries()方法,达到及时清除过期entry的目的。

三、WeakHashMap的使用

1、缓存中使用

  1. Car car = new Car(22000,"silver");
  2. WeakReference<Car> weakCar = new WeakReference<Car>(car);
  3. HashMapWeakHashMap的区别也在于此,HashMapkey是对实际对象的强引用。

  弱引用(WeakReference)的特性是:当gc线程发现某个对象只有弱引用指向它,那么就会将其销毁并回收内存。WeakReference也会被加入到引用队列queue中。

2、不合理的使用

不要使用基础类型作为WeakHashMap的key

缓存的使用案例太多了,这里举一个WeakHashMap使用不规范的例子。

  1. Map<Object, Object> objectMap = new WeakHashMap<Object, Object>();
  2. for (int i = 0; i < 1000; i++) {
  3. objectMap.put(i, new Object());
  4. System.gc();
  5. System.out.println("Map size :" + objectMap.size());
  6. }

我们期待得到1和0的输出,但是实际上看到的是许多128!有趣的是,并非所有的东西都是GC感兴趣的。在上面的情景中,int i会被转化为封装类型Integer,但是Integer保留了-128到127的缓存,因此哪些Key <= 127的Entry将不会进行自动回收。所以不要使用原始类型(及其对应的包装类型)。

发表评论

表情:
评论列表 (有 0 条评论,133人围观)

还没有评论,来说两句吧...

相关阅读