zoukankan      html  css  js  c++  java
  • (Java 源码阅读) 春眠不觉晓,HashMap知多少

    Jdk1.8的优化(相比1.7)

    • 数组+链表改成了数组 + 链表/红黑树

    • 链表插入由头插法改为尾插法

    • 扩容时1.7对原数组中的元素重新hash定位,1.8是位置不变或者是索引+旧容量大小

    • 插入与扩容的顺序。1.8是先插入再扩容。

    线程安全的做法

    hashmap有数据覆盖的问题。不是线程安全。

    例子:putval 比如线程A符合判断条件if ((p = tab[i = (n - 1) & hash]) == null)

    获取table数组的索引下标 index 和链表的头结点,进入条件判断后正好挂起;而线程B也符合条件判断语句,并且获取的table数组的索引下标也是index和链表的头结点,B的数据会写入table[index]。

    之后A线程恢复,持有过期的链表的头结点,A的数据会写入table[index]中,覆盖B的数据。

    这是A恢复现场,赋值操作。还有重复扩容。

    putval的步骤:

    一、若数组为空,则通过resize()扩容 二、计算数组索引,若为空则直接插入 三、 否则说明索引对应的位置已有元素,分类讨论

    1. 若数组索引对应的键相同,则直接覆盖
    2. 若数组索引对应的节点是红黑树节点,则插入红黑树
    3. 否则是链表
    • 从头结点遍历链表,若链表中有键值相同的节点,则覆盖
    • 若到达链表末尾,则直接插入
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            //第一步:判断table是否为空,则调用resize()函数创建一个
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            //第二步:计算元素的储存位置index,如果为空则直接插入
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            //若不为空,说明要添加的位置上已经有元素,需要分类讨论
            else {
                Node<K,V> e; K k;
                //第一种情况:key值相同,直接覆盖
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)
                //第二种情况:判断是否是红黑树
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                //第三种情况:链表
                    for (int binCount = 0; ; ++binCount) {
                        // 如果是链表末尾,新增节点
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        // 如果链表中存在key,则覆盖
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    //若链表长度>=7,转红黑树
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;//放入值
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
    复制代码

    HashTable

    直接在操作方法上加上synchronized,锁住整个数组,粗粒度。

    public class Hashtable<K,V>
        extends Dictionary<K,V>
        implements Map<K,V>, Cloneable, java.io.Serializable {
    		......................................
    		public Hashtable(Map<? extends K, ? extends V> t) {
            this(Math.max(2*t.size(), 11), 0.75f);
            putAll(t);
        }
    
     
        public synchronized int size() {
            return count;
        }
    
        public synchronized boolean isEmpty() {
            return count == 0;
        }
    
        public synchronized Enumeration<K> keys() {
            return this.<K>getEnumeration(KEYS);
        }
    
        public synchronized Enumeration<V> elements() {
            return this.<V>getEnumeration(VALUES);
        }
    
        public synchronized boolean contains(Object value) {
            if (value == null) {
                throw new NullPointerException();
            }
    
            Entry<?,?> tab[] = table;
            for (int i = tab.length ; i-- > 0 ;) {
                for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
                    if (e.value.equals(value)) {
                        return true;
                    }
                }
            }
            return false;
        }
    		
        }
        .......................................
    }
    复制代码

    Collections.synchronizedMap

    内部定义一个对象锁mutex

    public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
            return new SynchronizedMap<>(m);
        }
    
        /**
         * @serial include
         */
        private static class SynchronizedMap<K,V>
            implements Map<K,V>, Serializable {
            private static final long serialVersionUID = 1978198479659022715L;
    
            private final Map<K,V> m;     // Backing Map
            final Object      mutex;        // Object on which to synchronize
    
            SynchronizedMap(Map<K,V> m) {
                this.m = Objects.requireNonNull(m);
                mutex = this;
            }
    
            SynchronizedMap(Map<K,V> m, Object mutex) {
                this.m = m;
                this.mutex = mutex;
            }
    
            public int size() {
                synchronized (mutex) {return m.size();}
            }
            public boolean isEmpty() {
                synchronized (mutex) {return m.isEmpty();}
            }
            public boolean containsKey(Object key) {
                synchronized (mutex) {return m.containsKey(key);}
            }
            public boolean containsValue(Object value) {
                synchronized (mutex) {return m.containsValue(value);}
            }
            public V get(Object key) {
                synchronized (mutex) {return m.get(key);}
            }
    
            public V put(K key, V value) {
                synchronized (mutex) {return m.put(key, value);}
            }
            public V remove(Object key) {
                synchronized (mutex) {return m.remove(key);}
            }
            public void putAll(Map<? extends K, ? extends V> map) {
                synchronized (mutex) {m.putAll(map);}
            }
            public void clear() {
                synchronized (mutex) {m.clear();}
            }
    
            private transient Set<K> keySet;
            private transient Set<Map.Entry<K,V>> entrySet;
            private transient Collection<V> values;
    
            public Set<K> keySet() {
                synchronized (mutex) {
                    if (keySet==null)
                        keySet = new SynchronizedSet<>(m.keySet(), mutex);
                    return keySet;
                }
            }
    
            public Set<Map.Entry<K,V>> entrySet() {
                synchronized (mutex) {
                    if (entrySet==null)
                        entrySet = new SynchronizedSet<>(m.entrySet(), mutex);
                    return entrySet;
                }
            }
    
            public Collection<V> values() {
                synchronized (mutex) {
                    if (values==null)
                        values = new SynchronizedCollection<>(m.values(), mutex);
                    return values;
                }
            }
    
            public boolean equals(Object o) {
                if (this == o)
                    return true;
                synchronized (mutex) {return m.equals(o);}
            }
            public int hashCode() {
                synchronized (mutex) {return m.hashCode();}
            }
            public String toString() {
                synchronized (mutex) {return m.toString();}
            }
    
            // Override default methods in Map
            @Override
            public V getOrDefault(Object k, V defaultValue) {
                synchronized (mutex) {return m.getOrDefault(k, defaultValue);}
            }
            @Override
            public void forEach(BiConsumer<? super K, ? super V> action) {
                synchronized (mutex) {m.forEach(action);}
            }
            @Override
            public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
                synchronized (mutex) {m.replaceAll(function);}
            }
            @Override
            public V putIfAbsent(K key, V value) {
                synchronized (mutex) {return m.putIfAbsent(key, value);}
            }
            @Override
            public boolean remove(Object key, Object value) {
                synchronized (mutex) {return m.remove(key, value);}
            }
            @Override
            public boolean replace(K key, V oldValue, V newValue) {
                synchronized (mutex) {return m.replace(key, oldValue, newValue);}
            }
            @Override
            public V replace(K key, V value) {
                synchronized (mutex) {return m.replace(key, value);}
            }
            ...........................
          
        }
    复制代码

    ConcurrentHashMap

    使用分段锁,降低锁的粒度。

    ConcurrentHashMap成员变量使用volatile 修饰

    使用CAS操作和synchronized结合实现赋值操作,多线程操作只会锁住当前操作索引的节点。

    public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
        implements ConcurrentMap<K,V>, Serializable {
        .............................................
        transient volatile Node<K,V>[] table;
    
        /**
         * The next table to use; non-null only while resizing.
         */
        private transient volatile Node<K,V>[] nextTable;
    
        /**
         * Base counter value, used mainly when there is no contention,
         * but also as a fallback during table initialization
         * races. Updated via CAS.
         */
        private transient volatile long baseCount;
    
        /**
         * Table initialization and resizing control.  When negative, the
         * table is being initialized or resized: -1 for initialization,
         * else -(1 + the number of active resizing threads).  Otherwise,
         * when table is null, holds the initial table size to use upon
         * creation, or 0 for default. After initialization, holds the
         * next element count value upon which to resize the table.
         */
        private transient volatile int sizeCtl;
    
        /**
         * The next table index (plus one) to split while resizing.
         */
        private transient volatile int transferIndex;
    
        /**
         * Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
         */
        private transient volatile int cellsBusy;
    
        /**
         * Table of counter cells. When non-null, size is a power of 2.
         */
        private transient volatile CounterCell[] counterCells;
        ...................................................
        }
    复制代码

    HashMap

    初始化

    JDK1.8版本的,内部使用数组 + 链表/红黑树。 如果自己传入初始大小k,初始化大小为大于k的2的整数次方,例如如果传10,大小为16。默认初始值是16,最大容量是2^31次方。

    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 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.
      */
     static final int MAXIMUM_CAPACITY = 1 << 30;
    复制代码

    哈希函数

    先拿到key的hashcode,然后让hashcode的前16位与后16位进行异或

    • 尽可能降低hash碰撞,越分散越好

    • 尽可能高效,这是高频操作,所以采用位运算

    不直接使用hashcode的原因是hashcode函数的返回类型是int型散列值。初始化数组只有16,容易出现哈希冲突

    static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
    复制代码

    Node

    final Node<K,V> getNode(int hash, Object key) {
            Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
                if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                if ((e = first.next) != null) {
                    if (first instanceof TreeNode)
                        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }
    复制代码

    get函数

    public V get(Object key) {
            Node<K,V> e;
            return (e = getNode(hash(key), key)) == null ? null : e.value;
        }
        
    /**
    * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
    复制代码

    数据插入原理

    判断数组是否为空,为空进行初始化;

    不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index; 查看 table[index] 是否存在数据,没有数据就构造一个Node节点存放在 table[index] 中; 存在数据,说明发生了hash冲突(存在二个节点key的hash值一样), 继续判断key是否相等,相等,用新的value替换原数据(onlyIfAbsent为false);

    如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中;

    如果不是树型节点,创建普通Node加入链表中;判断链表长度是否大于 8, 大于的话链表转换为红黑树;

    插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍。

    LinkedHashMap

    LinkedHashMap内部维护了一个单链表,有头尾节点。

    LinkedHashMap节点Entry内部除了继承HashMap的Node属性,before 和 after用于标识前置节点和后置节点

    实现按插入的顺序或访问顺序排序。

    public class LinkedHashMap<K,V>
        extends HashMap<K,V>
        implements Map<K,V>
    {
    
        /**
         * HashMap.Node subclass for normal LinkedHashMap entries.
         */
        static class Entry<K,V> extends HashMap.Node<K,V> {
            Entry<K,V> before, after;
            Entry(int hash, K key, V value, Node<K,V> next) {
                super(hash, key, value, next);
            }
        }
        .....................................................
    }
    复制代码

    TreeMap

    默认是自然排序。 key所属的类实现Comparable接口进行比较。

    public class TreeMap<K,V>
        extends AbstractMap<K,V>
        implements NavigableMap<K,V>, Cloneable, java.io.Serializable
    {
        /**
         * The comparator used to maintain order in this tree map, or
         * null if it uses the natural ordering of its keys.
         *
         * @serial
         */
        private final Comparator<? super K> comparator;
        ...........................................
        public TreeMap(Comparator<? super K> comparator) {
            this.comparator = comparator;
        }
    }
    复制代码

    总结

    HashMap是一个有趣的数据类型,设计的知识很多。本文只选取了几个重要的点进行分析,包括存储结构里的数据红黑树,哈希函数中的冲突避免和线程安全问题。另外HashMap在面试中出现的次数也非常多,几乎是后端开发岗的必问题目,值得结合源码深入研究。


    作者:AlexanderChen
    链接:https://juejin.im/post/5efac964e51d4534883803a2
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    联想 Vibe Shot(Z90-3) 免recovery 获取ROOT权限 救砖 VIBEUI V3.1_1625
    联想 Z5S(L78071)免解锁BL 免rec 保留数据 ROOT Magisk Xposed 救砖 ZUI 10.5.370
    联想 Z5(L78011) 免解锁BL 免rec 保留数据 ROOT Magisk Xposed 救砖 ZUI 10.5.254
    联想 S5 Pro(L78041)免解锁BL 免rec 保留数据 ROOT Magisk Xposed 救砖 ZUI 5.0.123
    第二阶段 冲刺八
    第二阶段 冲刺七
    第二阶段 冲刺六
    第二阶段 冲刺五
    代码大全阅读笔记03
    学习进度十二
  • 原文地址:https://www.cnblogs.com/Dplus/p/13214283.html
Copyright © 2011-2022 走看看