JDK容器与并发—Map—WeakHashMap

概述

      基于弱引用键实现的HashMap,非线程安全。

1)当key所引用的对象只有弱引用时,在GC回收该对象后,会自动删除所关联的Entry;

2)其行为部分依赖GC;

3)value是强引用的。确保value没有直接或间接引用key,否则会阻止key引用对象的回收。间接引用:value1强引用key2的对象,其所关联的value2强引用key1的对象。

4)迭代器fail-fast。

数据结构

      与HashMap唯一的区别就是:WeakHashMap会将key封装到WeakReference,且关联ReferenceQueue,实现key的弱引用

Entry[] table;

/**
* Reference queue for cleared WeakEntries
*/
private final ReferenceQueue queue = new ReferenceQueue<>();

private static class Entry extends WeakReference implements Map.Entry {
	V value;
	int hash;
	Entry next;

	/**
	 * Creates new entry.
	 */
	Entry(Object key, V value,
		  ReferenceQueue queue,
		  int hash, Entry next) {
		super(key, queue);
		this.value = value;
		this.hash  = hash;
		this.next  = next;
	}
} 
  

构造器

      与HashMap的区别是:table、threshold会在容器中完成初始化,没有提供init()扩展接口;HashMap会在第一个键值对put时完成table初始化:

// 带初始容量、负载因子构造
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);
	// table、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);
}

// 带初始容量构造
public WeakHashMap(int initialCapacity) {
	this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

// 无参构造
public WeakHashMap() {
	this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

// 带Map构造
public WeakHashMap(Map m) {
	this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
			DEFAULT_INITIAL_CAPACITY),
		 DEFAULT_LOAD_FACTOR);
	putAll(m);
}

增删改查

清除WeakHashMap中无效键值对

      在增删改查的操作中,首先做的第一件事情就是调用getTable,从WeakHashMap中删除key引用的对象已回收的关联键值对:

/**
 * Returns the table after first expunging stale entries.
 */
private Entry[] getTable() {
	expungeStaleEntries();
	return table;
}

// 从WeakHashMap中删除key引用的对象已回收的关联键值对
private void expungeStaleEntries() {
	for (Object x; (x = queue.poll()) != null; ) {
		synchronized (queue) {
			@SuppressWarnings("unchecked")
				Entry e = (Entry) x;
			int i = indexFor(e.hash, table.length);

			Entry prev = table[i];
			Entry p = prev;
			while (p != null) {
				Entry 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
                    // 由于expungeStaleEntries的调用不会引起modCount的变化,
					// HashIterator的遍历感知不到其操作,所以next不能null
					e.value = null; // Help GC
					size--;
					break;
				}
				prev = p;
				p = next;
			}
		}
	}
}

容量调整策略

      与HashMap基本一样,只是在扩展过程中,需要考虑WeakHashMap的特性:

1)转移前首先从WeakHashMap中删除key引用的对象已回收的关联键值对,转移过程中已经回收的也不转移;

2)转移完成后,对于size低于threshold / 2的情况进行处理:容量不扩展,重新转移回来。

void resize(int newCapacity) {
	Entry[] oldTable = getTable();// 从WeakHashMap中删除key引用的对象已回收的关联键值对:
	int oldCapacity = oldTable.length;
	if (oldCapacity == MAXIMUM_CAPACITY) {
		threshold = Integer.MAX_VALUE;
		return;
	}

	Entry[] newTable = newTable(newCapacity);
	boolean oldAltHashing = useAltHashing;
	useAltHashing |= sun.misc.VM.isBooted() &&
			(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
	boolean rehash = oldAltHashing ^ useAltHashing;
	transfer(oldTable, newTable, rehash);
	table = newTable;

	// 如果因为getTable、transfer去掉的关联键值对比较多,
	// size低于threshold / 2,则采用oldTable,将newTable中的键值对重新转移过来
	// 这种情况比较少,但对于WeakHashMap来说很有用,可以避免没有必要的扩展
	if (size >= threshold / 2) {
		threshold = (int)(newCapacity * loadFactor);
	} else {
		expungeStaleEntries(); // 转移前再次清除
		transfer(newTable, oldTable, false);
		table = oldTable;
	}
}

private void transfer(Entry[] src, Entry[] dest, boolean rehash) {
	for (int j = 0; j < src.length; ++j) {
		Entry e = src[j];
		src[j] = null;
		while (e != null) {
			Entry next = e.next;
			Object key = e.get();
			if (key == null) { // 对于“key所引用的对象已回收”的关联键值对不转移
				e.next = null;  // Help GC
				e.value = null; //  "   "
				size--;
			} else {
				if (rehash) {
					e.hash = hash(key);
				}
				int i = indexFor(e.hash, dest.length);
				e.next = dest[i];
				dest[i] = e;
			}
			e = next;
		}
	}
}

      增删改查的操作过程与HashMap一样。

迭代器

      与HashMap基本一样,除了因为 若引用特性而采用的两个强引用:nextKey、currentKey,分别用于next()能获取到、正常使用时可以获取到:

    private abstract class HashIterator implements Iterator {
        private int index;
        private Entry entry = null;
        private Entry lastReturned = null;
        private int expectedModCount = modCount;

        /**
         * Strong reference needed to avoid disappearance of key
         * between hasNext and next
         */
        private Object nextKey = null;

        /**
         * Strong reference needed to avoid disappearance of key
         * between nextEntry() and any use of the entry
         */
        private Object currentKey = null;

        HashIterator() {
            index = isEmpty() ? 0 : table.length;
        }

        public boolean hasNext() {
            Entry[] t = table;

            while (nextKey == null) {
                Entry e = entry;
                int i = index;
                while (e == null && i > 0)
                    e = t[--i];
                entry = e;
                index = i;
                if (e == null) {
                    currentKey = null;
                    return false;
                }
                nextKey = e.get(); // hold on to key in strong ref
                if (nextKey == null)
                    entry = entry.next;
            }
            return true;
        }

        /** The common parts of next() across different types of iterators */
        protected Entry nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (nextKey == null && !hasNext())
                throw new NoSuchElementException();

            lastReturned = entry;
            entry = entry.next;
            currentKey = nextKey;
            nextKey = null;
            return lastReturned;
        }

        public void remove() {
            if (lastReturned == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

            WeakHashMap.this.remove(currentKey);
            expectedModCount = modCount;
            lastReturned = null;
            currentKey = null;
        }

    }

特性

      WeakHashMap的键值对的key为弱引用,其与HashMap的区别就是对key的这个特性的特殊处理。

你可能感兴趣的:(JDK容器与并发)