zoukankan      html  css  js  c++  java
  • WeakHashMap 源码分析

    WeakHashMap

    WeakHashMap 能解决什么问题?什么时候使用 WeakHashMap?

    1)WeakHashMap 是基于弱引用键实现 Map 接口的哈希表。当内存紧张,并且键只被 WeakHashMap 使用时,垃圾回收器会按需回收键值对。
    2)WeakHashMap 支持 null 键和 null 值。
    3)WeakHashMap 是线程不同步的,可以通过 {@link Collections#synchronizedMap Collections.synchronizedMap} 方法获取线程同步的 Map。
    

    如何使用 WeakHashMap?

    1)可以使用 WeakHashMap 实现热点分代缓存,当内存紧张,并且键只被 WeakHashMap 使用时,垃圾回收器会按需回收键值对。
    

    使用 WeakHashMap 有什么风险?

    1)WeakHashMap 读写数据时,都会同步锁住引用队列来删除无效的节点,当 JVM 内存不够而频繁执行垃圾回收时,同步删除操作比较影响性能。
    

    WeakHashMap 核心操作的实现原理?

    • 创建实例
        /**
         * 默认初始容量值为 16
         */
        private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
        /**
         * 最大的容量值,即 bucket 的个数
         */
        private static final int MAXIMUM_CAPACITY = 1 << 30;
    
        /**
         * 默认的加载因子
         */
        private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
        /**
         * 底层存储键值对的 table
         */
        Entry<K,V>[] table;
    
        /**
         * 已有键值对总数
         */
        private int size;
    
        /**
         * 下一次扩容的阈值
         */
        private int threshold;
    
        /**
         * 加载因子
         */
        private final float loadFactor;
    
        /**
         * 当弱引用关联的值需要被 GC 垃圾回收时,该弱引用就会被加入到其关联的引用队列中,
         * queue 中的对象都是 WeakHashMap 中需要被回收的节点。
         */
        private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
    
        /**
         * 结构化修改的次数,用于实现 Fast-Fail
         */
        int modCount;
    
        /**
         * WeakHashMap 的键值对继承了 WeakReference 类,可以实现按需回收
         */
        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;
            }
        }
    
        /**
         * 创建一个容量为 16,加载因子为 0.75 的空 WeakHashMap 实例
         */
        public WeakHashMap() {
            this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
        }
    
        /**
         * 创建一个容量为大于等于 initialCapacity 的最小的 2 的幂,
         * 加载因子为 0.75 的空 WeakHashMap 实例
         */
        public WeakHashMap(int initialCapacity) {
            this(initialCapacity, DEFAULT_LOAD_FACTOR);
        }
    
        /**
         * 创建一个容量为大于等于 initialCapacity 的最小的 2 的幂,
         * 加载因子为 loadFactor 的空 WeakHashMap 实例
         */
        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);
            }
            // 获取大于等于 initialCapacity 的最小的 2 的幂
            int capacity = 1;
            while (capacity < initialCapacity) {
                capacity <<= 1;
            }
            // 初始化 table
            table = newTable(capacity);
            // 写入加载因子
            this.loadFactor = loadFactor;
            // 写入扩容阈值
            threshold = (int)(capacity * loadFactor);
        }
    
    • 添加键值对
        /**
         * 添加新的键值对
         */
        @Override
        public V put(K key, V value) {
            // mask 键
            final Object k = WeakHashMap.maskNull(key);
            // 计算哈希值
            final int h = hash(k);
            // 去除无效的键值对,并返回 table
            final Entry<K,V>[] tab = getTable();
            // 基于键的哈希值计算目标索引
            final int i = WeakHashMap.indexFor(h, tab.length);
            // 读取指定的 bucket,并遍历单向链表的所有元素
            for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
                /**
                 * 当前节点的哈希值和目标哈希值一致,
                 * 并且目标键和弱引用键关联的键值相等
                 */
                if (h == e.hash && WeakHashMap.eq(k, e.get())) {
                    final V oldValue = e.value;
                    // 如果新值和旧值不相等,则替换旧值
                    if (value != oldValue) {
                        e.value = value;
                    }
                    // 返回旧值
                    return oldValue;
                }
            }
            modCount++;
            // 读取 bucket 首节点
            final Entry<K,V> e = tab[i];
            // 创建新的节点作为 bucket 的首节点,并将原来的单向链表链接在其后
            tab[i] = new Entry<>(k, value, queue, h, e);
            // 递增元素总个数,如果超出阈值
            if (++size >= threshold) {
                // 进行双倍扩容
                resize(tab.length * 2);
            }
            // 新增节点返回 null
            return null;
        }
    
        /**
         * 删除 WeakHashMap 中的无效节点,并返回 table
         */
        private Entry<K,V>[] getTable() {
            expungeStaleEntries();
            return table;
        }
    
        /**
         * 从 table 中删除过时的节点
         */
        private void expungeStaleEntries() {
            // 同步处理弱引用队列中的所有元素
            for (Object x; (x = queue.poll()) != null; ) {
                synchronized (queue) {
                    @SuppressWarnings("unchecked")
                    final
                    Entry<K,V> e = (Entry<K,V>) x;
                    // 计算哈希值
                    final int i = WeakHashMap.indexFor(e.hash, table.length);
                    // 读取 bucket 的首节点
                    Entry<K,V> prev = table[i];
                    // 暂存前置节点
                    Entry<K,V> p = prev;
                    while (p != null) {
                        // 读取下一个节点
                        final Entry<K,V> next = p.next;
                        // 链表中的当前节点就是引用队列中弹出的节点
                        if (p == e) {
                            // 当前处理节点是 bucket 首节点
                            if (prev == e) {
                                // 更新 bucket 首节点为后置节点
                                table[i] = next;
                            } else {
                                // 前置节点的后置节点更新为处理节点的后置节点
                                prev.next = next;
                            }
                            // 将节点值置空
                            e.value = null; // Help GC
                            // 递减元素个数
                            size--;
                            break;
                        }
                        // 否则处理下一个节点
                        prev = p;
                        p = next;
                    }
                }
            }
        }
    
        void resize(int newCapacity) {
            // 读取旧 table
            final Entry<K,V>[] oldTable = getTable();
            // 读取旧容量
            final int oldCapacity = oldTable.length;
            // 旧容量达到最大容量
            if (oldCapacity == MAXIMUM_CAPACITY) {
                // 只更新扩容阈值为 Integer.MAX_VALUE
                threshold = Integer.MAX_VALUE;
                return;
            }
            // 创建新 table
            final Entry<K,V>[] newTable = newTable(newCapacity);
            // 迁移旧 table 中的元素到新 table 中     
            transfer(oldTable, newTable);
            table = newTable;
            /*
             * 总元素个数 >= 扩容阈值的二分之一
             */
            if (size >= threshold / 2) {
                // 计算新的阈值
                threshold = (int)(newCapacity * loadFactor);
            } else {
                // 去除无效的节点并将元素迁移回旧 table 中
                expungeStaleEntries();
                transfer(newTable, oldTable);
                table = oldTable;
            }
        }
    
        /**
         * 从 src table 迁移所有的节点到 dest table
         */
        private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
            // 顺序处理 src table 中的每个 bucket
            for (int j = 0; j < src.length; ++j) {
                // 读取首节点
                Entry<K,V> e = src[j];
                // 将其置空
                src[j] = null;
                // 首节点不为 null,表示当前 bucket 不为空
                while (e != null) {
                    // 读取下一个节点
                    final Entry<K,V> next = e.next;
                    // 读取当前节点的键
                    final Object key = e.get();
                    // 键为 null,表示已经被回收,则删除该节点
                    if (key == null) {
                        e.next = null;  // Help GC
                        e.value = null; //  "   "
                        size--;
                    } else {
                        // 计算键在新 bucket 中的索引
                        final int i = WeakHashMap.indexFor(e.hash, dest.length);
                        // 当前节点的 next 指向新 bucket 的首节点
                        e.next = dest[i];
                        /**
                         * 新 bucket 的首节点更新为当前节点,
                         * 每次添加节点,新节点都作为 bucket 的首节点加入到单向链表中
                         */
                        dest[i] = e;
                    }
                    // 递归处理单向链表的下一个节点
                    e = next;
                }
            }
        }
    
    • 读取值
        /**
         * 根据键读取值
         */
        @Override
        public V get(Object key) {
            final Object k = WeakHashMap.maskNull(key);
            final int h = hash(k);
            final Entry<K,V>[] tab = getTable();
            final int index = WeakHashMap.indexFor(h, tab.length);
            // 读取 bucket 首节点
            Entry<K,V> e = tab[index];
            while (e != null) {
                // 当前节点键和目标键相等
                if (e.hash == h && WeakHashMap.eq(k, e.get())) {
                    // 读取值
                    return e.value;
                }
                e = e.next;
            }
            // 键不存在返回 null
            return null;
        }
    
    • 读取元素个数
        /**
         * 去除无效的节点,并返回粗略的元素总数
         */
        @Override
        public int size() {
            if (size == 0) {
                return 0;
            }
            expungeStaleEntries();
            return size;
        }
    
    • 是否为空
        /**
         * WeakHashMap 是否为空
         */
        @Override
        public boolean isEmpty() {
            return size() == 0;
        }
    
  • 相关阅读:
    面向对象深入:继承01——子类
    面向对象的基础知识——小结
    IP地址配置
    二、RPM包管理-rpm命令管理
    一、软件包管理简介
    关机重启命令
    网络命令
    权限管理命令
    字符截取命令
    shell-正则表达式(二)
  • 原文地址:https://www.cnblogs.com/zhuxudong/p/10017013.html
Copyright © 2011-2022 走看看