jay's blog
Java8中HashMap的put值时处理hash冲突

putVal方法

put方法接受两个参数,分别是key和value。key用于计算value存放位置。具体逻辑如下:

  • 首先根据hash方法得到key的hash值,这个值是key的hashcode^key的hashcode右移16位的结果
  • 然后调用putVal方法。putVal方法是最终操作table的方法,不管是put还是putIfAbsent或者是putAll,最终都由putVal完成将值存放到table里。
    • 如果table为空或者table的长度为0,通过resize方法初始化
    • 接下来是存放值的操作。通过hashcode按位与table的length-1,得到数据存放的桶。如果桶里为空,则构造一个Node存放到table中前面通过按位与所得到的索引位置。说明一下:Node实际上是Map.Entry的一个实现。在1.8中Map.Entry在HashMap里有2个实现类。Node是其中之一,它是一个单向链表结构。另一个是TreeNode,TreeNode是红黑树结构。如果hash冲突的元素在2-8个那么则使用Node这个数据结构来存放元素,如果超过8则使用TreeNode来存放。
    • hash冲突分3种情况处理:
      • 第一种是重复设置,使用同一个key去设置多次值也被视为是hash冲突的。判断是否为同一个key的方式:要么同一个对象,也就能够用==判断,要么是key使用equals方法判断返回true。这种情况直接覆盖原来的数据。
      • 第二种情况,就是冲突元素已经大于8个,将数据存到前面提到的TreeNode中
      • 第三种情况,有冲突,但不大于8个时。存放到单向链表Node中。遍历单向链表:用了一个死循环,退出条件为当前桶的最后一个元素的next为null或者找到了重复的key。当最后一个元素为null时则认为当前put的元素是一个新的元素,将他放到链表的末端。当key重复了什么也不做,跳出循环后再处理。跳出循环后如果检测到变量e不为null,则认为当前这个key是重复的,然后重新赋值

putVal方法的代码如下:

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)//如果table为null或者table的长度为0
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)//没有hash冲突,构造一个Node并存放到对应的位置
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k)))) //p代表当前桶的第一关元素。此处判断的是地一个元素的key是否与新存入的相同,如果相同就将p赋值给e,待后面重新赋值
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//将元素put到TreeNode
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {//如果循环到最后一个都没有相同的key,则将存入的节点追加到链表最后一个元素的next
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);//转换为TreeNode
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) //链表中已经存在相同的key,处理手法与上面的第一个if类似,e的赋值操作由for循环后的if完成
                        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;
    }

测试代码如下

public class Pojo {
    public int hashCode() {
        return 0xff;
    }
    public static void main(String... args){
	    Map<Pojo,Integer> map = new HashMap<Pojo,Integer>();
	    for (int i = 0; i< 10 ;) {
	        Pojo p = new Pojo();
	        map.put(p,++i);
	    }
	}
}

构造参数loadFactor与initialCapacity

  • loadFactor 默认0.75。
  • initialCapacity 默认16。tableSizeFor这个方法会根据实际传入的值进行再一次校验。用|=进行1,2,4,8,16的右移位后再确定threshold大小,如果小于0,则为1,如果大于1«30则为1«30,否则为initialCapacity+1
<dependency>
     <groupId>org.databene</groupId>
     <artifactId>contiperf</artifactId>
     <version>2.3.4</version>
     <scope>test</scope>
</dependency>

最后修改于 2015-05-23