HashMap底层原理 (一)基本结构和hash算法

HashMap数据结构:

在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,而JDK1.8中,HashMap采用位桶+链表+红黑树实现。1.8中档列表中的元素超过hashMap的TREEIFY_THRESHOLD字段所确认的值时,链表转换成为红黑树。而当红黑树的元素小于UNTREEIFY_THRESHOLD字段所设置的值时,重新转换成为链表
注:

  • TREEIFY_THRESHOLD默认值为8
  • UNTREEIFY_THRESHOLD默认值为6

我们以JDK1.8来做为分析源码,那么hashMap的数据结构如图所示:
在这里插入图片描述
根据hashmap的源码内部定义类Node

//node的部分源码  hashMap的每个元素都是已Node对象来存储
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;//通过hashmao扰乱过后的hash值
        final K key;//传入的key
        V value; //传入的值
        Node<K,V> next; //下一个值
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
    }

HashMap的构造函数

 	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;
        //将传入的指定数组长度转换成最近高位的2的次方长度
        this.threshold = tableSizeFor(initialCapacity);
    }
    /**
    * initialCapacity指定的数组长度
    */
  	public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    
 	public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; //就只是设置扩容因子为默认的扩容因子
    }
    /** 将指定传入的数转换成最近的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;
    }

通过上面的构造函数可以看出.hashMap的桶位长度一定为2的次方数据,至于为什么2的次方数,通过put中的计算桶位的公式(n - 1) & hash 来说明

//分别取n=6和n=7和n=8来看    hash假定为3   通过hash已4,3和2来举例  具体2进制运算过程这里不做演算
		System.out.println("======== n=6 hash 分别为1,2,3 =========");
		System.out.println((6 - 1) & 1);//结果为1
		System.out.println((6 - 1) & 2);//结果为0
		System.out.println((6 - 1) & 3);//结果为1
		System.out.println("======== n=7 hash 分别为1,2,3 =========");
		System.out.println((7 - 1) & 1);//结果为0
		System.out.println((7 - 1) & 2);//结果为2
		System.out.println((7 - 1) & 3);//结果为2
		System.out.println("======== n=8 hash 分别为1,2,3 =========");
		System.out.println((8 - 1) & 1);//结果为1
		System.out.println((8 - 1) & 2);//结果为2
		System.out.println((8 - 1) & 3);//结果为3

通过上列可以确认用2的次方数结合计算公式可以最小程度的避免hash碰撞。

HashMap的hash方法

在put方法中

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

先分析下hash方法

	//hashmap源码
	static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
	
	//修改后 为了看的更清楚
	static final int hash(Object key) {
        int h;
        h = key.hashCode();
        int y = h>>>16;
        int z = h ^ y
        return (key == null) ? 0 : z;
    }
    //Object 中的hashCode
   public native int hashCode();

逐句分析
h=key.hashCode();根据Object的hashCode方法 ,所有的重写或者至直接使用都是会返回一个int值,也就是会返回一个32位的2进制数,
y=h>>>16,将h右移16位 比如
h:1010 1001 0010 0010 1101 0101 0010 1101 >>>16后变为 0000 0000 0000 0000 1010 1001 0010 0010
z=h^y, 将高位数也加入进来一起做运算,进一步降低hash碰撞。

posted @ 2021-05-26 16:15  rr完美'诺言  阅读(5)  评论(0编辑  收藏  举报  来源