JDK1.8 Hashmap源码解析

    • 一、基础知识
        • 1、注释
        • 2、内部结构
        • 3、补充
    • 二、常用方法
        • 1、字段
        • 2、计算hash
        • 3、存入值:
        • 4、查找node:
    • 三、扩容
    • 四、 jdk1.8新加的方法

一、基础知识

1、注释

  • 允许空值和空键
  • 无序:不保证map中的顺序,不保证顺序一直不变;
  • 两个重要因素:初始大小和负载因子(初始大小默认16,负载因子默认为0.75);当已存储的数量 > 容量 * 负载因子,hashmap自动增大为原来大小的两倍,重新散列(rehash,消耗大)。负载因子越高,空间消耗越小,查询map中元素消耗时间越多。当需要一个较大空间时,最好给一个大的初始容量,避免rehash。
  • 基本操作 (get and put) 恒定时间性能:O(log n);
  • 使用相同的键存储值会直接降低hashtable的效率。为了减轻这样的情况,会对key进行比较,确保key的重复性低,但是这样也会降低性能。
  • hashmap不同步,需要自行在外部同步。一般是将map封装在一个对象中,然后对这个对象进行同步;也可以如下确保同步:
	Map m = Collections.synchronizedMap(new HashMap(...));

大致相当于 hashtable, 只是hashmap不同步, 并允许空值;

  • 当hashmap过大时(链表长度大于8)会转为红黑树,支持更快的查询,树节点的大小是常规节点的两倍。

2、内部结构

这是hashmap的内部结构,用数组加链表的形式,先使用散列,把节点分布到数组的每个位置,发生冲突时,使用链表解决
JDK1.8 Hashmap源码解析_第1张图片
这里散列的大小为2^n,事实上这并不是一个很好的选择,碰撞概率会增大。一般情况下,散列的大小最好取2的n次方-1(素数)。hashmap这样做是为了之后运算(位运算)方便,同时在hash时选择更好的hash函数,以抵消2的n次方带来的不便。

这是每一个node
JDK1.8 Hashmap源码解析_第2张图片
当链表长度大于8时,每一个node都会变成treeNode,形成红黑树。

3、补充

  • 要求map中存储的对象有hashcode()equals()方法,且有不变性,所以使用IntegerString更好,它们都是final,不会变,而且有hashcode()和equals()方法。
  • fail-fast机制:map中有一个modcount,用于存储版本号,每次对map进行结构上的修改,modcount就会+1;修改时,检查版本号,如果期待的版本号和当前版本号不同,则直接抛出异常,不再进行后续步骤。问题在于fail-fast并不保证每次都能检查出异常,所以并不能依赖它,hashmap依旧是线程不安全的。
  • 序列化的时候,先写入大小,负载因子等参数,再写入每一个节点,读取时按相同顺序。

二、常用方法

1、字段

hashmap中的字段如下,可以在初始化时进行设置,如果不设置,则按照默认的处理

	//大小为2^n,首次使用时初始化,有时长度可以为0
	transient Node<K,V>[] table;
	//缓存节点,AbstractMap字段在keySet() and values()中使用
	transient Set<Entry<K,V>> entrySet;
	//map中存储节点数量
	transient int size;
	//版本号,结构修改时增加,fail-fast机制
	transient int modCount;
	//大于该值,rehash
	int threshold;
	//负载因子
	final float loadFactor;

默认配置

	//默认初始化容量
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
	//最大容量
	static final int MAXIMUM_CAPACITY = 1 << 30;
	//负载因子
	static final float DEFAULT_LOAD_FACTOR = 0.75f;
	//链表长度大于该值,转为红黑树
	static final int TREEIFY_THRESHOLD = 8;
	//最多可存储数量:CAPACITY * LOAD_FACTOR。大于该值,rehash。
	static final int UNTREEIFY_THRESHOLD = 6;
	//变成树的最小容量
	static final int MIN_TREEIFY_CAPACITY = 64;

2、计算hash

先得到key的hashcode,然后让高16位和低16位异或,结果就是hash,
index = (n - 1) & hash,也就是hash对表大小取余。

	/*计算hash
	由于map的大小为2^n,更容易出现碰撞,所以需要高位与低位异或,减少碰撞
	*/
	static final int hash(Object key) {
	    int h;
	    // >>>:无符号右移16位
	    //高位与低位异或
	    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
	}

3、存入值:

afterNodeInsertion,afterNodeAccess这些是linkhashmap会做的事情,此处不讨论

	/** 存入值
	* @param onlyIfAbsent if true, don't change existing value
	* @param evict if false, the table is in creation mode.
	* @return previous value, or null if none
	*
	*/
	//todo
	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;//table的长度
	   //该节点应该存入的位置为空,新建节点,存入
	   if ((p = tab[i = (n - 1) & hash]) == null)
	       tab[i] = newNode(hash, key, value, null);
	   //不为空,p指向链表或红黑树
	   else {
	       Node<K,V> e; K k;
	       //判断第一个节点,如果第一个节点就是要存储的节点,将p的值给e
	       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;
	               }
	               //如果e是要存储的节点,停止
	               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);
	           //返回旧值
	           return oldValue;
	       }
	   }
	   ++modCount;
	   if (++size > threshold)
	       resize();
	   afterNodeInsertion(evict);
	   return null;
	}

jdk1.7加入节点用的是头插法,所以tab[index] = 最新加入的节点,因为认为最新加入的节点用到的可能性会更大。
jdk1.8采用尾插

4、查找node:

	final Node<K,V> getNode(int hash, Object key) {
	   Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
	   
	   //table为null,table长度为0,index = (n - 1) & hash,对应位置为null
	   if ((tab = table) != null && (n = tab.length) > 0 &&
	           (first = tab[(n - 1) & hash]) != null) {
	       //first = table[index],此位置存的是一串链表
	       //first存的是第一个节点
	       //先判断第一个节点(first)是不是
	       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;
	}

经过这两个方法其他方法基本都大同小异,先得到index对应的链表/树,根据不同的情况进行处理。是树,交给树处理,是链表遍历,自行处理。
JDK1.8 Hashmap源码解析_第3张图片

三、扩容

	//扩容
	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) {
	            threshold = Integer.MAX_VALUE;//最多可存储值,设为最大(原来是容量*负载因子)
	            return oldTab;
	        }
	        //扩容两倍
	        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 { //初始化
	        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) {
	            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 {
	                            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;
	}

jdk1.7:使用头插法,扩容时,链表会逆置
JDK1.8 Hashmap源码解析_第4张图片
链表逆置可以避免尾部遍历,但存在一个很严重的问题:多线程的时候会导致死循环!!!
若线程1与线程2同时扩容,此时线程1与线程2都指向node1,且next节点均为node2。
线程1执行线程2休眠,线程1扩容完成后链表逆置原本的1->2->3变为3->2->1
线程2唤醒开始执行扩容,此时线程2指向node1,且next节点为node2,但node2的next节点又为node1,出现死循环

jdk1.8:采用尾插法,不会出现链表逆置
JDK1.8 Hashmap源码解析_第5张图片
除此之外,jdk1.8的改进:在1.7扩容时,需要重新rehash,1.8不用。

	if ((e.hash & oldCap) == 0){
		loTail...
	} else {
		hiTail...
	}
	
	if (loTail != null) {
	    loTail.next = null;
	    newTab[j] = loHead;
	}
	if (hiTail != null) {
	    hiTail.next = null;
	    newTab[j + oldCap] = hiHead;
	}

扩容,就是给原来的容量乘2,也就是把原来容量oldCap左移一位,这时2^n的好处就表现出来了。
index = (n - 1) & hash,如果这一位是0,则index = index,否则,index = index + oldCap。
这样很快就能得到新的index而且避免了rehash(rehash是一个消耗比较大的方法,避免它,可以提高性能)。

四、 jdk1.8新加的方法

	/** 
	 * 添加一个节点,若该节点已存在,不改变原来的值
	 * */
	@Override
	public V putIfAbsent(K key, V value) {
	    return putVal(hash(key), key, value, true, true);
	}
	
	/**
	 * 有值,设置新值,返回新值
	 * 无值,头插
	 * */
	//TODO afterNodeInsertion mappingFunction.apply treeifyBin afterNodeInsertion
	@Override
	public V computeIfAbsent(K key,
	                         Function<? super K, ? extends V> mappingFunction) {
	    if (mappingFunction == null)
	        throw new NullPointerException();
	    int hash = hash(key);
	    Node<K,V>[] tab; Node<K,V> first; int n, i;
	    int binCount = 0;
	    TreeNode<K,V> t = null;
	    Node<K,V> old = null;
	    //设置大小resize
	    if (size > threshold || (tab = table) == null ||
	            (n = tab.length) == 0)
	        n = (tab = resize()).length;
	    //获取旧节点
	    if ((first = tab[i = (n - 1) & hash]) != null) {
	        if (first instanceof TreeNode)
	            old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
	        else {
	            Node<K,V> e = first; K k;
	            do {
	                if (e.hash == hash &&
	                        ((k = e.key) == key || (key != null && key.equals(k)))) {
	                    old = e;
	                    break;
	                }
	                ++binCount;
	            } while ((e = e.next) != null);
	        }
	        //旧的值和节点都存在,返回旧的值
	        V oldValue;
	        if (old != null && (oldValue = old.value) != null) {
	            afterNodeAccess(old);
	            return oldValue;
	        }
	    }
	    V v = mappingFunction.apply(key);
	    //新值不存在
	    if (v == null) {
	        return null;
	    } else if (old != null) {//新值存在且旧节点存在
	        old.value = v;//设置新值
	        afterNodeAccess(old);
	        return v;
	    }
	    //按照红黑树处理
	    else if (t != null)
	        t.putTreeVal(this, tab, hash, key, v);
	    else {//头插法给链表加入一个节点
	        tab[i] = newNode(hash, key, v, first);
	        if (binCount >= TREEIFY_THRESHOLD - 1)
	            treeifyBin(tab, hash);
	    }
	    ++modCount;
	    ++size;
	    afterNodeInsertion(true);
	    return v;
	}
	
	/**
	 * 新值存在则设置新值,不存在 则删除节点
	 * */
	public V computeIfPresent(K key,
	                          BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
	    if (remappingFunction == null)
	        throw new NullPointerException();
	    Node<K,V> e; V oldValue;
	    int hash = hash(key);
	    if ((e = getNode(hash, key)) != null &&
	            (oldValue = e.value) != null) {
	        V v = remappingFunction.apply(key, oldValue);
	        if (v != null) {
	            e.value = v;
	            afterNodeAccess(e);
	            return v;
	        }
	        else
	            removeNode(hash, key, null, false, true);
	    }
	    return null;
	}
	
	@Override
	public V compute(K key,
	                 BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
	    if (remappingFunction == null)
	        throw new NullPointerException();
	    int hash = hash(key);
	    Node<K,V>[] tab; Node<K,V> first; int n, i;
	    int binCount = 0;
	    TreeNode<K,V> t = null;
	    Node<K,V> old = null;
	    if (size > threshold || (tab = table) == null ||
	            (n = tab.length) == 0)
	        n = (tab = resize()).length;
	    //获取旧节点
	    if ((first = tab[i = (n - 1) & hash]) != null) {
	        if (first instanceof TreeNode)
	            old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
	        else {
	            Node<K,V> e = first; K k;
	            do {
	                if (e.hash == hash &&
	                        ((k = e.key) == key || (key != null && key.equals(k)))) {
	                    old = e;
	                    break;
	                }
	                ++binCount;
	            } while ((e = e.next) != null);
	        }
	    }
	    V oldValue = (old == null) ? null : old.value;
	    V v = remappingFunction.apply(key, oldValue);
	    if (old != null) {//有节点
	        if (v != null) {//新值存在
	            old.value = v;//设置新值
	            afterNodeAccess(old);
	        }
	        else//无值,删除节点
	            removeNode(hash, key, null, false, true);
	    }
	    //没有节点,但新值存在则添加节点
	    else if (v != null) {
	        //红黑树添加
	        if (t != null)
	            t.putTreeVal(this, tab, hash, key, v);
	        //链表添加
	        else {
	            tab[i] = newNode(hash, key, v, first);
	            if (binCount >= TREEIFY_THRESHOLD - 1)
	                treeifyBin(tab, hash);
	        }
	        ++modCount;
	        ++size;
	        afterNodeInsertion(true);
	    }
	    return v;
	}
	
	@Override
	public V merge(K key, V value,
	               BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
	    if (value == null)
	        throw new NullPointerException();
	    if (remappingFunction == null)
	        throw new NullPointerException();
	    int hash = hash(key);
	    Node<K,V>[] tab; Node<K,V> first; int n, i;
	    int binCount = 0;
	    TreeNode<K,V> t = null;
	    Node<K,V> old = null;
	    //设置大小resize
	    if (size > threshold || (tab = table) == null ||
	            (n = tab.length) == 0)
	        n = (tab = resize()).length;
	    //first为对应的链表/树
	    //得到需要的节点old
	    if ((first = tab[i = (n - 1) & hash]) != null) {
	        //红黑树
	        if (first instanceof TreeNode)
	            old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
	        else {//链表
	            Node<K,V> e = first; K k;
	            do {
	                if (e.hash == hash &&
	                        ((k = e.key) == key || (key != null && key.equals(k)))) {
	                    old = e;
	                    break;
	                }
	                ++binCount;
	            } while ((e = e.next) != null);
	        }
	    }
	    //节点存在
	    if (old != null) {
	        V v;
	        //根据旧值和value,得到设置的新值
	        if (old.value != null)
	            v = remappingFunction.apply(old.value, value);
	        else
	            v = value;
	        //新值存在
	        if (v != null) {
	            old.value = v;//设置新值
	            afterNodeAccess(old);
	        }
	        else//不存在则删除节点
	            removeNode(hash, key, null, false, true);
	        return v;
	    }
	    //新的值存在则添加节点
	    if (value != null) {
	        if (t != null)
	            t.putTreeVal(this, tab, hash, key, value);
	        else {
	            tab[i] = newNode(hash, key, value, first);
	            if (binCount >= TREEIFY_THRESHOLD - 1)
	                treeifyBin(tab, hash);
	        }
	        ++modCount;
	        ++size;
	        afterNodeInsertion(true);
	    }
	    return value;
	}

你可能感兴趣的:(笔记,java,链表,数据结构)