Map之hashmap源码分析

来源:互联网 发布:python编程求圆的面积 编辑:程序博客网 时间:2024/05/20 14:18

HashMap作为Java编程中最为常用的工具类之一,本文将从JDK1.8源码的层面对HashMap进行分析。

全限定类名:java.util.HashMap

一:HashMap概述

HashMap是实现了KEY-VALUE的非线程安全的数据结构。允许使用 null 值和 null 键。

二:HahsMap的内部是以何种形式存储数据的.

根据每个segment包含元素的具体数量以及MIN_TREEIFY_CAPACITY参数的限定共同决定使用链表还是红黑树。(在1.7以及之前只存在链表结构。关于红黑树的数据结构这边不予以详细介绍)

三:JDK1.8的HashMap与1.7中有何区别

JDK7与JDK8中HashMap实现的最大区别就是对于冲突的处理方法。由JDK7中的链表变为了JDK8中的链表、红黑树并存。

四:哪些优秀的内容本文没有提及的

红黑树的相关数据结构,实现原理,查询插入删除操作本文没有提及。
JDK8引入的函数式编程,包括default的拓展方法,map等函数的使用本文没有提及。
关于序列化和反序列化的具体内容,包括writeObject和readObject方法。

五:源码分析及实现

1.常量说明

1.1 DEFAULT_INITIAL_CAPACITY  默认初始化容量。容量必须为2的次方。原因在下面hash的时候会解释。默认的hashmap大小为16.

 /**     * The default initial capacity - MUST be a power of two.     */    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

1.2 MAXIMUM_CAPACITY 最大的容量大小2^30。

 /**     * 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;

1.3 DEFAULT_LOAD_FACTOR默认resize的因子。0.75,即实际数量超过总数*DEFAULT_LOAD_FACTOR的数量即会发生resize动作。

    /**     * The load factor used when none specified in constructor.     */    static final float DEFAULT_LOAD_FACTOR = 0.75f;

1.4 TREEIFY_THRESHOLD 树化阈值。当单个segment的容量超过阈值时,将链表转化为红黑树。

    /**     * The bin count threshold for using a tree rather than list for a     * bin.  Bins are converted to trees when adding an element to a     * bin with at least this many nodes. The value must be greater     * than 2 and should be at least 8 to mesh with assumptions in     * tree removal about conversion back to plain bins upon     * shrinkage.     */    static final int TREEIFY_THRESHOLD = 8;

1.5 UNTREEIFY_THRESHOLD 链表化阈值。当删除操作后单个segment的容量低于阈值时,将红黑树转化为链表。

    /**     * The bin count threshold for untreeifying a (split) bin during a     * resize operation. Should be less than TREEIFY_THRESHOLD, and at     * most 6 to mesh with shrinkage detection under removal.     */    static final int UNTREEIFY_THRESHOLD = 6;

1.6 MIN_TREEIFY_CAPACITY 最小树化容量。当桶中的bin被树化时最小的hash表容量,低于该容量时不会树化。

 /**     * The smallest table capacity for which bins may be treeified.     * (Otherwise the table is resized if too many nodes in a bin.)     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts     * between resizing and treeification thresholds.     */    static final int MIN_TREEIFY_CAPACITY = 64;

2.关键操作分析

2.1 如何分配到各个segment当中。

一致性算法一般包含mod或者是hash。HashMap是以hash操作作为散列依据。但是又与传统的hash存在着少许的优化。hash值是key的hashcode与其hashcode右移16位的异或结果。在put方法中,将取出的hash值与当前的hashmap容量-1进行与运算。得到的就是位桶的下标。那么为何需要使用key.hashCode() ^ h>>>16的方式来计算hash值呢。其实从微观的角度来看,这种方法与直接去key的哈希值返回在功能实现上没有差别。但是由于最终获取下表是对二进制数组最后几位的与操作。所以直接取hash值会丢失高位的数据,从而增大冲突引起的可能。由于hash值是32位的二进制数。将高位的16位于低位的16位进行异或操作,即可将高位的信息存储到低位。因此该函数也叫做扰乱函数。目的就是减少冲突出现的可能性。而官方给出的测试报告也验证了这一点。直接使用key的hash算法与扰乱函数的hash算法冲突概率相差10%左右。

static final int hash(Object key) {        int h;        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);    }
 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) {...
n = table.lengthi = (n - 1) & hash...}

2.2 如何获取下一个需要扩容的大小。即如何获取距离一个数字的最近的2的幂次方。

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

由于map的容量需要维持在2的幂次方,所以需要计算出下一个扩容的大小。具体算法如图所示,int n = cap-1;的目的是为了防止当前的大小正好为2的幂次。

二进制中一个大于0的数字必定有一位为1。假设一个数字为00001******。在进行第一次n|= n>>>1;后结果变为000011*****;即让最高位以及次一位的数字变为1.同理

下一步变为00001111****;即让后两位再变为一。以此类推直到数据变为000011111111.最后执行+1操作即得到了距离给定数字最近的二次幂。

2.3 如何根据key获取value。

源码:
public V get(Object key) {        Node<K,V> e;        return (e = getNode(hash(key), key)) == null ? null : e.value;    }
 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;    }

核心思想即通过传入的key,调用其hash值对segment的位置进行判断后。根据定位的segment对相应的链表/红黑树进行搜索。判断两个key的.equals()方法为真时,即取得
对应的value。

2.5 如何根据提交新的值。

源码:
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;        if ((tab = table) == null || (n = tab.length) == 0)     //(1)            n = (tab = resize()).length;//(2)        if ((p = tab[i = (n - 1) & hash]) == null)//(3)            tab[i] = newNode(hash, key, value, null);//(4)        else {            Node<K,V> e; K k;            if (p.hash == hash &&                ((k = p.key) == key || (key != null && key.equals(k))))//(5)                e = p;//(6)            else if (p instanceof TreeNode)                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //(7)            else {                for (int binCount = 0; ; ++binCount) {//(7)                    if ((e = p.next) == null) {                        p.next = newNode(hash, key, value, null);                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//(8)                            treeifyBin(tab, hash);                        break;                    }                    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;                if (!onlyIfAbsent || oldValue == null)                    e.value = value;                afterNodeAccess(e);//无用操作,为linkedhashmap服务                return oldValue;            }        }        ++modCount;//(9)        if (++size > threshold)//(10)            resize();        afterNodeInsertion(evict);//无用操作,为linkedhashmap服务        return null;    }
先判断当前的table是否为空(1),如果是则对table进行resize操作(2)。否则根据传入的key的hash算法进入到相应的segment(3)。如果segment对应的Node节点为空(4),
则创建新节点并赋值。如果不为空则判断当前节点是否为传入节点(5),若是则替换value值(6),否则循环遍历list或者红黑树查询是否存在相同的key(7),若是则替换,否则插入。
同时判断树化阈值以及最小树化阈值来判断是否resize或者是树化(8)。将操作计数+1(9)。判断是否需要扩容(10)。本段没有详细描述红黑树如何插入,查找节点。红黑树内容请
参照其他文章进行学习。

2.6 如何删除一个值

源码
    public V remove(Object key) {        Node<K,V> e;        return (e = removeNode(hash(key), key, null, false, true)) == null ?            null : e.value;    }

 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;    }
删除操作可以参照上面的插入操作。进行反向思路即可。