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
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    Linux中的DHCP服务
    写出发帖数最多的前几条记录
    nginx和php-fpm调用方式
    redi集群测试
    Redis 3.2.1集群搭建
    linux系统centOS7下搭建redis集群中ruby版本过低问题的解决方法
    nginx与php-fpm通信的两种方式
    Linux安装mongodb总结
    php高并发
    thinkphp5报错
  • 原文地址:https://www.cnblogs.com/Dplus/p/13214283.html
Copyright © 2011-2022 走看看