WeakHashMap

WeakHashMap

基于哈希表的Map接口实现,具有弱键。当WeakHashMap中的一个条目的键不再正常使用时,它将自动被删除。更准确地说,给定键的映射的存在不会阻止该键被垃圾收集器丢弃,也就是说,使其可完成、完成,然后再回收。当一个键被丢弃时,它的条目将有效地从映射中删除,因此这个类的行为与其他映射实现略有不同。

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

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

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

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

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

  1. 线程不安全
  2. key 作为虚引用
  3. value 作为强引用
  4. key 和 value 都可以为 空值; 但是比较有意思的,他会首先判断 是否 为空,如果为空,就会变成 new Object(); 就会创建 一个 Object 对象 作为 key, get 和 put 都是这种。
  5. 默认大小,加载因子 和 hashmap 类似,只是底层实现就是 entry 数组和单链表,并且 key 是 弱引用
  6. 前插法
public class WeakHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>

基于哈希表的Map接口实现,·具有弱键。当WeakHashMap中的一个条目的键不再正常使用时,它将自动被删除。更准确地说,给定键的映射的存在不会阻止该键被垃圾收集器丢弃,也就是说,使其可完成、完成,然后再回收。当一个键被丢弃时,它的条目将有效地从映射中删除,只是,因此这个类的行为与其他映射实现略有不同。支持空值和空键。该类具有与HashMap类相似的性能特征,具有相同的初始容量和负载因子的效率参数。与大多数集合类一样,这个类不是同步的。可以使用集合构造同步的WeakHashMap.synchronizedMap方法。这个类主要用于关键对象,这些对象的equals方法使用==操作符测试对象标识。一旦丢弃了这样的键,就永远不能重新创建它,因此不可能在稍后的某个时间在WeakHashMap中查找该键,却惊奇地发现其条目被删除了。该类可以很好地处理其equals方法不基于对象标识的关键对象,比如字符串实例。然而,对于这种可重新创建的键对象,自动删除键已被丢弃的WeakHashMap条目可能会令人困惑

WeakHashMap类的行为在一定程度上依赖于垃圾收集器的操作,因此几个熟悉的(但不是必需的)映射不变量对这个类无效。因为垃圾收集器可能在任何时候丢弃键,所以WeakHashMap的行为可能就像一个未知线程正在悄悄地删除条目。特别是,即使你同步WeakHashMap实例并调用其mutator方法,有可能大小方法返回较小值随着时间的推移,isEmpty方法返回false,然后真的,containsKey方法返回true,后来错误对于一个给定的键,get方法的返回值对于一个给定的关键但后来返回null,对于以前出现在映射中的键,put方法返回null,而remove方法返回false,以及对键集、值集合和条目集进行连续的检查,以产生依次更少的元素。WeakHashMap中的每个键对象都作为弱引用的引用间接存储。因此,只有在映射内外对键的弱引用被垃圾收集器清除后,键才会被自动删除。实现注意:WeakHashMap中的值对象由普通的强引用保存。因此,应该注意确保值对象不直接或间接地强引用它们自己的键,因为这将防止键被丢弃。注意,值对象可以通过WeakHashMap本身间接引用它的键;也就是说,一个值对象可以强引用其他一些键对象,而这些键对象的关联值对象又强引用第一个值对象的键。如果映射中的值不依赖于持有对它们的强引用的映射,一种处理方法是在插入之前将值本身包装在WeakReferences中,如:m。put(key, new WeakReference(value)),然后在每个get上展开包装。

返回的迭代器返回的集合的迭代器方法这门课的所有“集合视图方法”是快速失败:如果结构修改地图创建迭代器后,任何时候以任何方式除非通过迭代器的删除方法,迭代器将抛出ConcurrentModificationException。因此,在面对并发修改时,迭代器会快速而干净地失败,而不是在未来某个不确定的时间冒险做出任意的、不确定的行为。注意,迭代器的快速失败行为不能得到保证,因为一般来说,在存在非同步并发修改时不可能做出任何硬性保证。快速失败迭代器尽最大努力抛出ConcurrentModificationException。因此,如果要编写一个依赖于这个异常来保证其正确性的程序,那就错了:迭代器的快速失败行为应该仅用于检测错误。

/**
 * The default initial capacity -- MUST be a power of two.
 */
private static final int DEFAULT_INITIAL_CAPACITY = 16;

/**
 * The maximum capacity, used if a higher value is implicitly specified
 * by either of the constructors with arguments.
 * MUST be a power of two <= 1<<30.
 */
private static final int MAXIMUM_CAPACITY = 1 << 30;

/**
 * The load factor used when none specified in constructor.
 */
private static final float DEFAULT_LOAD_FACTOR = 0.75f;

/**
 * The table, resized as necessary. Length MUST Always be a power of two.
 */
Entry<K,V>[] table;

/**
 * The number of key-value mappings contained in this weak hash map.
 */
private int size;

/**
 * The next size value at which to resize (capacity * load factor).
 */
private int threshold;

/**
 * The load factor for the hash table.
 */
private final float loadFactor;
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
    V value;
    final int hash;
    Entry<K,V> next;
    /**
     * Creates new entry.
     */
    Entry(Object key, V value,
          ReferenceQueue<Object> queue,
          int hash, Entry<K,V> next) {
        super(key, queue);
        this.value = value;
        this.hash  = hash;
        this.next  = next;
    }
}

get

public V get(Object key) {
    Object k = maskNull(key); // 首先获取 是否为 空
    int h = hash(k);
    Entry<K,V>[] tab = getTable();
    int index = indexFor(h, tab.length); // 整数 与 操作
    Entry<K,V> e = tab[index];
    while (e != null) {	//
        if (e.hash == h && eq(k, e.get()))
            return e.value;
        e = e.next;
    }
    return null;
}

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;
}

resize

首先增长容量,然后 transfer

void resize(int newCapacity) {
    Entry<K,V>[] oldTable = getTable();
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry<K,V>[] newTable = newTable(newCapacity);
    transfer(oldTable, newTable); // 
    table = newTable;

    /*
     * If ignoring null elements and processing ref queue caused massive
     * shrinkage, then restore old table.  This should be rare, but avoids
     * unbounded expansion of garbage-filled tables.
     */
    if (size >= threshold / 2) {
        threshold = (int)(newCapacity * loadFactor);
    } else {
        expungeStaleEntries();
        transfer(newTable, oldTable);
        table = oldTable;
    }
}

transfer

会将一些 key == null 的键值对 去掉

/** Transfers all entries from src to dest tables */
private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
    for (int j = 0; j < src.length; ++j) {
        Entry<K,V> e = src[j];
        src[j] = null;
        while (e != null) {
            Entry<K,V> next = e.next;
            Object key = e.get();
            if (key == null) {
                e.next = null;  // Help GC
                e.value = null; //  "   "
                size--;
            } else {
                int i = indexFor(e.hash, dest.length);
                e.next = dest[i];
                dest[i] = e;
            }
            e = next;
        }
    }
}

size

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

public int size() {
    if (size == 0)
        return 0;
    expungeStaleEntries();
    return size;
}

WeakHashMap 的使用

Integer

集合中只会存储 基本类型的封装类型,因此,在直接插入 基本类型的数据时候,会首先转变成相应的封装类型,并且,比较有意思的是, byte, short, int, long 类型对象的封装类型都有 相应的缓存,

每次插入 map.put(20, 20) 和 map.put(20, 20) 都会从缓存中获取 20 的封装对象;

也就是说 -128 - 127 之间的对象 integer 都是至少有一个 对象引用的,因此,下面的例子中,插入的 4 个数在垃圾回收时没有变化

WeakHashMap<Integer, Integer> weakHashMap = new WeakHashMap<>();

weakHashMap.put(20, 20);
weakHashMap.put(22, 22);
weakHashMap.put(24, 24);
weakHashMap.put(25, 25);

System.out.println(weakHashMap);
System.out.println(weakHashMap.size());
System.out.println(weakHashMap.keySet().size());

System.gc();

System.out.println(weakHashMap);
System.out.println(weakHashMap.size());
System.out.println(weakHashMap.keySet().size());
/*
{24=24, 25=25, 22=22, 20=20}
4
4
{24=24, 25=25, 22=22, 20=20}
4
4
*/

我们发现,只有 25 -24 键值对没有被删除,因为 存在于缓存中,而其他的对象都被垃圾回收

WeakHashMap<Integer, Integer> weakHashMap = new WeakHashMap<>();

weakHashMap.put(168, 20);
weakHashMap.put(695, 22);
weakHashMap.put(25, 24);
weakHashMap.put(36942, 25);

System.out.println(weakHashMap);
System.out.println(weakHashMap.size());
System.out.println(weakHashMap.keySet().size());

System.gc();

System.out.println(weakHashMap);
System.out.println(weakHashMap.size());
System.out.println(weakHashMap.keySet().size());
/*
{695=22, 25=24, 36942=25, 168=20}
4
4
{25=24}
1
1
*/

其他任意对象,如果,外部没有GC 可达,就会被回收

WeakHashMap<Dog, Integer> weakHashMap = new WeakHashMap<>();

weakHashMap.put(new Dog("CaoBourne1", 25), 30);
weakHashMap.put(new Dog("CaoBourne2", 25), 30);
weakHashMap.put(new Dog("CaoBourne3", 25), 30);
weakHashMap.put(new Dog("CaoBourne4", 25), 30);

System.out.println(weakHashMap);
System.out.println(weakHashMap.size());
System.out.println(weakHashMap.keySet().size());

System.gc();

System.out.println(weakHashMap);
System.out.println(weakHashMap.size());
System.out.println(weakHashMap.keySet().size());
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值