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

    简介

    基于哈希表的Map接口的实现,带有弱键。当集合中的键没有引用时,将会被垃圾回收器删除。

    类继承关系

    在这里插入图片描述

    属性

    	/**
         * 默认初始容量-必须为2的幂。
         */
        private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
        /**
         * 最大容量
         */
        private static final int MAXIMUM_CAPACITY = 1 << 30;
    
        /**
         * 默认加载因子
         */
        private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
        /**
         * hash槽
         */
        Entry<K,V>[] table;
    
        /**
         * 键值对的数量
         */
        private int size;
    
        /**
         * 下次扩容的阈值
         */
        private int threshold;
    
        /**
         * 加载因子
         */
        private final float loadFactor;
    
        /**
         * 已清除虚键的队列
         */
        private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
        /**
         * 用来表示null键
         */
        private static final Object NULL_KEY = new Object();
    

    内部类

    // 继承了WeakReference 说明这个键是个虚键
    // 并且实现了Entry节点 可以作为单链表
    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;
    
        /**
         * 内部节点值得注意的是这个构造方法
         * super(key, queue)调用父类构造 传入key和引用队列
         * 当GC回收了key的时候 会将这个Entry放到队列里面 就像是个回调
         * 这边发现队列有数据 就去清理一下(清理节点,key已经回收了)
         */
        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;
        }
    }
    

    构造

    //构造一个空map,拥有默认的初始容量16,和加载因子0.75
    public WeakHashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    public WeakHashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    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);
        //确定初始容量 寻找大于传入容量的最近 2的幂
        //这里循环判断是否大于初始容量 不大于 在翻一倍 直到找到那个数
        //注意这里算法和HashMap不一样 但是功能是一样的
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;
        table = newTable(capacity);
        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
    }
    

    hash

    final int hash(Object k) {
        int h = k.hashCode();
        // 此函数可确保在每个位位置仅
        //常数倍相差的hashCode具有
        //有限的冲突次数(默认负载因子约为8)
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    

    插入put

    public V put(K key, V value) {
        Object k = maskNull(key);//如果key=null 使用默认全局nullKey表示
        int h = hash(k);
        Entry<K,V>[] tab = getTable();//获得table 这个函数里面先清除了一遍GC后的数据
        int i = indexFor(h, tab.length);//h & (length-1) 定位hash槽
    
        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;
    }
    private static Object maskNull(Object key) {
       return (key == null) ? NULL_KEY : key;
    }
    

    获取get

    public V get(Object key) {
        Object k = maskNull(key);
        int h = hash(k);//计算hash码
        Entry<K,V>[] tab = getTable();//获取表
        int index = indexFor(h, tab.length);//定位 和插入规则一样
        Entry<K,V> e = tab[index];//找到所在hash槽 时间复杂度O(1)
        while (e != null) {
            if (e.hash == h && eq(k, e.get()))
                return e.value;
            e = e.next;
        }
        return null;
    }
    

    移除remove

    public V remove(Object key) {
    	//老规矩 计算hash码 获取tab表 定位hash槽
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int i = indexFor(h, tab.length);
        Entry<K,V> prev = tab[i];//第一个节点
        Entry<K,V> e = prev;
    
        while (e != null) {
            Entry<K,V> next = e.next;
            if (h == e.hash && eq(k, e.get())) {//找到节点后
                modCount++;
                size--;
                //如果要删除的节点是第一个 那么直接把下一个节点放到hash槽就行了
                //否则将第一个节点指向下一个节点
                if (prev == e)
                    tab[i] = next;
                else
                    prev.next = next;
                return e.value;
            }
            prev = e;
            e = next;
        }
    
        return null;
    }
    

    扩容resize

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

    移除被GC的节点expungeStaleEntries

    private void expungeStaleEntries() {
        for (Object x; (x = queue.poll()) != null; ) {
            synchronized (queue) {
                @SuppressWarnings("unchecked")
                    Entry<K,V> e = (Entry<K,V>) x;
                int i = indexFor(e.hash, table.length);
    
                Entry<K,V> prev = table[i];
                Entry<K,V> p = prev;
                while (p != null) {
                    Entry<K,V> 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
                        e.value = null; // Help GC
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }
    
  • 相关阅读:
    LinkedListQueue
    LinkedListStack
    redis学习之——Redis事务(transactions)
    redis学习之——持久化RDB 和AOF
    redis学习之——redis.conf配置(基本)文件学习
    评估算法的核心指标
    Vector类
    List接口与ArrayList、LinkedList实现类
    Collection接口
    枚举类
  • 原文地址:https://www.cnblogs.com/paper-man/p/13284620.html
Copyright © 2011-2022 走看看