jdk8源码5---集合4---HashMap

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wxy540843763/article/details/80640625

本来是想写HashSet,但是看了HashSet的成员变量,你就懂了、、、、、

一、签名

public class HashMap<K,V> extends AbstractMap<K,V>

    implements Map<K,V>, Cloneable, Serializable {

    HashMap通常作为桶式哈希表,当桶变得很大的时候就转化为树节点。一般达到过量数据的时机比较少。所以在桶式哈希表中会尽量推迟树形节点的检测。

    树形哈希(所有节点都是树节点),以哈希值排序,但如果都是同类型并且该类型实现了比较器就以比较器的结果为准。TreeNode是一般节点的两倍。只有当哈希表节点数达到一定数量才使用。

    通常第一个节点作为树的根节点,当根节点移除时才更换。

    不论哈希列表还是树形哈希,分割还是非树形,都保证相对的访问遍历顺序。

二、成员变量

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;  // 初始大小16

static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量

static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载系数(装载因子)

主要控制空间利用率和冲突。装载因子越大空间利用率更高,冲突可能也会变大,反之则相反。

static final int TREEIFY_THRESHOLD = 8; //由链表转换成树的阈值、

static final int UNTREEIFY_THRESHOLD = 6; // 由树转换成链表的阈值

static final int MIN_TREEIFY_CAPACITY = 64; //转换树形后表格最小容量,至少是treeify_threshold的四倍。

static class Node<K,V> implements Map.Entry<K,V> { //基本的哈希容器节点。

        final int hash;    // 不可变的hash值,由关键字key得来。

        final K key;  // 关键字不可变

        V value;

        Node<K,V> next;

 

        Node(int hash, K key, V value, Node<K,V> next) {

            this.hash = hash;

            this.key = key;

            this.value = value;

            this.next = next;

        }

 

        public final K getKey()        { return key; }

        public final V getValue()      { return value; }

        public final String toString() { return key + "=" + value; }

 

        public final int hashCode() {  // 异或运算。

            return Objects.hashCode(key) ^ Objects.hashCode(value);

        }

 

        public final V setValue(V newValue) {

            V oldValue = value;

            value = newValue;

            return oldValue;

        }

 

        public final boolean equals(Object o) {

            if (o == this)

                return true;

            if (o instanceof Map.Entry) {

                Map.Entry<?,?> e = (Map.Entry<?,?>)o;

                if (Objects.equals(key, e.getKey()) &&

                    Objects.equals(value, e.getValue()))

                    return true;

            }

            return false;

        }

    }

transient Node<K,V>[] table;  // 不被序列化的节点。Node类型数组,第一次使用的时候初始化,必要时重新分配空间。长度总是2的次幂。

static class Node<K,V> implements Map.Entry<K,V> {

        final int hash;

        final K key;

        V value;

        Node<K,V> next;

 

        Node(int hash, K key, V value, Node<K,V> next) {

            this.hash = hash;

            this.key = key;

            this.value = value;

            this.next = next;

        }

 

        public final K getKey()        { return key; }

        public final V getValue()      { return value; }

        public final String toString() { return key + "=" + value; }

 

        public final int hashCode() {

            return Objects.hashCode(key) ^ Objects.hashCode(value);

        }

 

        public final V setValue(V newValue) {

            V oldValue = value;

            value = newValue;

            return oldValue;

        }

 

        public final boolean equals(Object o) {

            if (o == this)

                return true;

            if (o instanceof Map.Entry) {

                Map.Entry<?,?> e = (Map.Entry<?,?>)o;

                if (Objects.equals(key, e.getKey()) &&

                    Objects.equals(value, e.getValue()))

                    return true;

            }

            return false;

        }

    }

transient Set<Map.Entry<K,V>> entrySet; // 缓存所有的EntrySet()

transient int size; // 当前map中的数据量

transient int modCount; // map结构的修改次数。实现了fast-fial策略。

int threshold; // 下次重新分配空间resize()时,table数组的大小。

Final float loadFactor;  // hash表的负载因子。

三、静态工具函数。

static final int hash(Object key) {

        int h;

        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

    }  // 学习一下编码风格(标红处)。

计算key的hash值,并且将高位的hash值移到低位。因为使用的掩码是2的n次幂,高于掩码的位组成的哈希集合总是冲突,所以要把高位移到低位。

 

static Class<?> comparableClassFor(Object x) {

        if (x instanceof Comparable) {

            Class<?> c; Type[] ts, as; Type t; ParameterizedType p;

            if ((c = x.getClass()) == String.class) // bypass checks

                return c;

            if ((ts = c.getGenericInterfaces()) != null) {

                for (int i = 0; i < ts.length; ++i) {

                    if (((t = ts[i]) instanceof ParameterizedType) &&

                        ((p = (ParameterizedType)t).getRawType() ==

                         Comparable.class) &&

                        (as = p.getActualTypeArguments()) != null &&

                        as.length == 1 && as[0] == c) // type arg is c

                        return c;

                }

            }

        }

        return null;

    }

与x进行比较,如果x是可比较类型,返回x的类型,否则返回null。

 

static int compareComparables(Class<?> kc, Object k, Object x) {

        return (x == null || x.getClass() != kc ? 0 :

                ((Comparable)k).compareTo(x));

    }

如果x和k可以比较,返回k和x的比较结果,否则返回0.

 

static final int tableSizeFor(int cap) {

        int n = cap - 1;

        n |= n >>> 1;

        n |= n >>> 2;

        n |= n >>> 4;

        n |= n >>> 8;

        n |= n >>> 16;

        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

    }

对于给定的目标容器返回一个2的次幂容量(返回大于cap的最小的2次幂)感觉这个地方很深奥。

百度一张图片吧:

   

四、构造方法

public HashMap(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);

        this.loadFactor = loadFactor;

        this.threshold = tableSizeFor(initialCapacity);

    }

根据特定的初始化容量和负载因子的构造函数。

 

public HashMap(int initialCapacity) {

        this(initialCapacity, DEFAULT_LOAD_FACTOR);

    }

根据指定初始化容量和默认装载因子.75的构造函数

 

public HashMap() {

        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted

    }

构造一个默认大小和默认装载因子的HashMap。

 

public HashMap(Map<? extends K, ? extends V> m) {

        this.loadFactor = DEFAULT_LOAD_FACTOR;

        putMapEntries(m, false);

    }

 

五、成员方法

public V put(K key, V value) {

        return putVal(hash(key), key, value, false, true);

    }

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为空的话,就先初始化,扩容。

        if ((tab = table) == null || (n = tab.length) == 0)

            n = (tab = resize()).length;

        if ((p = tab[i = (n - 1) & hash]) == null) // 如果tab[i]为空,直接放入。

            tab[i] = newNode(hash, key, value, null);

        else { // 如果hash后的位置上的值不为空。后接链表。

            Node<K,V> e; K k;

            if (p.hash == hash &&

                ((k = p.key) == key || (key != null && key.equals(k)))) // key已经存在。

                e = p;

            else if (p instanceof TreeNode) // 如果该节点属于红黑树。(链表的长度>8)

                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

            else {// tab[i]后还是链表。

                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;

                    }

                    if (e.hash == hash &&

                        ((k = e.key) == key || (key != null && key.equals(k))))//如果key已经存在,就覆盖value。

                        break;

                    p = e;

                }

            }

 

//如果之前判断到key已经存在,就进行覆盖value

            if (e != null) { // existing mapping for key

                V oldValue = e.value;

                if (!onlyIfAbsent || oldValue == null)

                    e.value = value;

                afterNodeAccess(e);

                return oldValue;

            }

        }

        ++modCount;

        if (++size > threshold)

            resize();

        afterNodeInsertion(evict);

        return null;

    }

扩容机制:

final Node<K,V>[] resize() {

        Node<K,V>[] oldTab = table;

        int oldCap = (oldTab == null) ? 0 : oldTab.length;

        int oldThr = threshold;

        int newCap, newThr = 0;

        if (oldCap > 0) {

            if (oldCap >= MAXIMUM_CAPACITY) { // 如果原来table中容量已经是最大

                threshold = Integer.MAX_VALUE;

                return oldTab;

            }

// 如果旧容量*2小于最大容量阈值,并且旧容量大于默认初始化容量。

            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&

                     oldCap >= DEFAULT_INITIAL_CAPACITY)

                newThr = oldThr << 1; // 新的容量阈值扩大两倍。

        }

        else if (oldThr > 0) // initial capacity was placed in threshold

            newCap = oldThr;

        else {               // zero initial threshold signifies using defaults

            newCap = DEFAULT_INITIAL_CAPACITY;

            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

        }

        if (newThr == 0) {

            float ft = (float)newCap * loadFactor;

            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?

                      (int)ft : Integer.MAX_VALUE);

        }

        threshold = newThr;

        @SuppressWarnings({"rawtypes","unchecked"})

            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];

        table = newTab;

        if (oldTab != null) {

            for (int j = 0; j < oldCap; ++j) { // 把每个table[i]都移动到新的newtable[i]中。

                Node<K,V> e;

                if ((e = oldTab[j]) != null) {

                    oldTab[j] = null;

                    if (e.next == null)

                        newTab[e.hash & (newCap - 1)] = e;

                    else if (e instanceof TreeNode)

                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);

                    else { // preserve order

                        Node<K,V> loHead = null, loTail = null;

                        Node<K,V> hiHead = null, hiTail = null;

                        Node<K,V> next;

                        do {

                            next = e.next;

                        // 原索引。

                            if ((e.hash & oldCap) == 0) {

                                if (loTail == null)

                                    loHead = e;

                                else

                                    loTail.next = e;

                                loTail = e;

                            }

                            else {  // 原索引+oldCap

                                if (hiTail == null)

                                    hiHead = e;

                                else

                                    hiTail.next = e;

                                hiTail = e;

                            }

                        } while ((e = next) != null);

                        if (loTail != null) {

                            loTail.next = null;

                            newTab[j] = loHead;

                        }

                        if (hiTail != null) {

                            hiTail.next = null;

                            newTab[j + oldCap] = hiHead;

                        }

                    }

                }

            }

        }

        return newTab;

    }

final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,

                                       int h, K k, V v) {

            Class<?> kc = null;

            boolean searched = false;

            TreeNode<K,V> root = (parent != null) ? root() : this;

            for (TreeNode<K,V> p = root;;) {

                int dir, ph; K pk;

                if ((ph = p.hash) > h)

                    dir = -1;

                else if (ph < h)

                    dir = 1;

                else if ((pk = p.key) == k || (k != null && k.equals(pk)))

                    return p;

                else if ((kc == null &&

                          (kc = comparableClassFor(k)) == null) ||

                         (dir = compareComparables(kc, k, pk)) == 0) {

                    if (!searched) {

                        TreeNode<K,V> q, ch;

                        searched = true;

                        if (((ch = p.left) != null &&

                             (q = ch.find(h, k, kc)) != null) ||

                            ((ch = p.right) != null &&

                             (q = ch.find(h, k, kc)) != null))

                            return q;

                    }

                    dir = tieBreakOrder(k, pk);

                }

 

                TreeNode<K,V> xp = p;

                if ((p = (dir <= 0) ? p.left : p.right) == null) {

                    Node<K,V> xpn = xp.next;

                    TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);

                    if (dir <= 0)

                        xp.left = x;

                    else

                        xp.right = x;

                    xp.next = x;

                    x.parent = x.prev = xp;

                    if (xpn != null)

                        ((TreeNode<K,V>)xpn).prev = x;

                    moveRootToFront(tab, balanceInsertion(root, x));

                    return null;

                }

            }

        }

分析:

    图片来源于网络,见注释。

 

 

 

public V remove(Object key) {

        Node<K,V> e;

        return (e = removeNode(hash(key), key, null, false, true)) == null ?

            null : e.value;

    }

static final int hash(Object key) {

        int h;

        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

    }

final Node<K,V> removeNode(int hash, Object key, Object value,

                               boolean matchValue, boolean movable) {

        Node<K,V>[] tab; Node<K,V> p; int n, index;

        if ((tab = table) != null && (n = tab.length) > 0 &&

            (p = tab[index = (n - 1) & hash]) != null) {

            Node<K,V> node = null, e; K k; V v;

            if (p.hash == hash &&

                ((k = p.key) == key || (key != null && key.equals(k))))

                node = p;

            else if ((e = p.next) != null) {

                if (p instanceof TreeNode)

                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);

                else {

                    do {

                        if (e.hash == hash &&

                            ((k = e.key) == key ||

                             (key != null && key.equals(k)))) {

                            node = e;

                            break;

                        }

                        p = e;

                    } while ((e = e.next) != null);

                }

            }

            if (node != null && (!matchValue || (v = node.value) == value ||

                                 (value != null && value.equals(v)))) {

                if (node instanceof TreeNode)

                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);

                else if (node == p)

                    tab[index] = node.next;

                else

                    p.next = node.next;

                ++modCount;

                --size;

                afterNodeRemoval(node);

                return node;

            }

        }

        return null;

    }

 

 

六、遍历方法

这里总结一下Map的遍历方法吧,for,fori,iterator什么的统统不算哈。

就只有两种。一种是entry,一种是getKey。

    举个栗子:一对夫妻,你想找个那个妻子。。。。。。一种找到他老公,通过他老公去找,第二种是直接拿到他俩的结婚证找到妻子。

1. hashMap.keySet(); for set…………就是for循环的方式了。

2. hashMap.entrySet(); 

 

总结

hashMap根据键的hashCode值存储数据,大多数情况下可以直接定位它的值。大多数情况下可以直接定位它的值,因而具有很快的访问速度。但是遍历顺序却是不确定的。hashMap最多只允许一条记录的键为null,允许多条记录的值为null。hashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致,但是可以用Collections.synchronizedMap()使HashMap线程安全。

或者使用ConcurrentHashMap。

 

要搞清楚HashMap,首先要清楚HashMap是什么,即它的存储结构-字段,其次弄明白它能干什么,即它的功能实现-方法。

    Jdk8中,HashMap是数组+链表+红黑树实现的,如下图。

   

那么问题来了,数据底层存储的是什么呢?这样的存储方式有什么好处呢?

1.hashMap有一个很重要的字段,就是Node<K,V>.这个Node的实现,见上面的字段介绍。

途中每个黑点就是一个Node。

2.大家都知道HashMap的底层数据结构使哈希表,哈希表为解决冲突,可以采用开发地址法和链地址法等来解决问题,java中HashMap采用了链地址法。当数据被hash后,得到数组下标,把数据放在对应下标元素的链表上。

3.根据源码,threshold就是在此loadFactor和length对应下允许的最大元素数目,超过这个数目就要重新扩容,扩容后的HashMap容量就是之前容量的两倍。默认的loadFactor是0.75,是对空间和时间的平衡选择,。如果内存空间很多而对事件效率要求很高,可以降低loadFactor的值。想反,对内存空间比较紧张而对时间效率要求不高的时候,就可以增加loadFactor的值,这个值可以大于1.

4.为什么hashMap的重新扩容或者初始化大小是2的n次方呢?

    在HashMap中,哈希桶数组table的长度length大小必须为2的n次方(一定是合数),这是一种非常规的设计,常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小于合数,具体证明可以参考http://blog.csdn.net/liuqiyao_01/article/details/14475159,Hashtable初始化桶大小为11,就是桶大小设计为素数的应用(Hashtable扩容后不能保证还是素数)。HashMap采用这种非常规设计,主要是为了在取模和扩容时做优化,同时为了减少冲突,HashMap定位哈希桶索引位置时,也加入了高位参与运算的过程。

5.哈希算法:

    这里的Hash算法本质上就是三步,取key的hashCode值,,高位运算,取模运算。

对于任意的对象,只要hashCode()的值相同,那么计算所得的hash码也是相同的,我们把hash值对数组的长度取模,但是取模的消耗很大,hashMap采用h&(length-1)的方式。

    这个方法非常巧妙,它通过h & (table.length -1)来得到该对象的保存位,而HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。

6.扩容机制:

         我们使用的是2次幂的扩展(指长度扩为原来的2倍)所以,元素的位置要么是在原位置,要么是在原位置在移动2次幂的位置。

图a,表示扩容前的key1和key2两种key确定索引的位置,

图b,表示扩容后的key1和key2两种key确定索引位置的示例。

元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:

因此,我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,可以看看下图为16扩充为32的resize示意图:

这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。这一块就是JDK1.8新增的优化点。

1. 线程安全机制:

线程不安全,多线程环境中应尽量使用ConcurrentHashMap。

 

 

(1) 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。

(2) 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。

(3) HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。

(4) JDK1.8引入红黑树大程度优化了HashMap的性能。

(5) 还没升级JDK1.8的,现在开始升级吧。HashMap的性能提升仅仅是JDK1.8的冰山一角。

 

 

 

借鉴文章:http://www.importnew.com/20386.html  (通过这篇文章,知道了什么才是在学习)


猜你喜欢

转载自blog.csdn.net/wxy540843763/article/details/80640625