0
点赞
收藏
分享

微信扫一扫

HashMap的结构与构造函数

快乐与微笑的淘气 2022-04-27 阅读 57
java

原文链接:

通俗易懂Hashmap源码解析_java阳开发之路的博客-CSDN博客_hashmap源码

https://www.csdn.net/tags/MtTaEgxsNTYxMDAyLWJsb2cO0O0O.html

·

浅析HashMap的结构

        哈希表是一种以键 - 值(key-value)存储数据的结构,我们只要输入待查找的值的key,就可以找到其对应的值即 Value。

        HashMap的设计十分精巧,本文不作深入分析,只简要描述HashMap的原理。

        HashMap的结构是一个entry数组,每一个entry节点在数组中的位置由entry节点中的key来决定,用一个哈希函数把 key 换算成一个确定的位置,然后把 value 放在数组的这个位置

        了解哈希数组的同学都知道,【不同元素的哈希映射有可能是相同的,意味着两个完全不同的元素,是有可能会映射到相同的位置上】。

        HashMap的做法是:将新元素放在已有元素的尾端。因此,entry节点的类型实际上是一个链表结构

如图所示:

            

结论:

  •         当 链表的长度 >= 7 ,且entry数组长度 < 64 时,此时会优先选择扩容,(不转红黑树)
  •         只有当 链表的长度 >= 7,且entry数组的长度 >= 64 时,才会将链表转为红黑树
  •         当该entry节点的长度小于等于6了,则将它的红黑树转回为链表结构;

 ·

·

Hashmap源码-3个初始化方法:

1)链表与红黑树互相转化的阀值: 

    //将链表转化为红黑树的阀值
    static final int TREEIFY_THRESHOLD = 8;

    //将红黑树转回为链表的阀值
    static final int UNTREEIFY_THRESHOLD = 6;

    //将链表转为红黑树的最小entry数组的长度为 64
    //当链表的长度 >= 7,且entry数组的长度>= 64时,才会将链表转为红黑树。
    static final int MIN_TREEIFY_CAPACITY = 64;

2) Map的默认初始化容量以及容量极限:

    //HashMap默认的初始容量大小--16,容量必须是2的幂
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    //HashMap的容量极限,为2的30次幂;
    static final int MAXIMUM_CAPACITY = 1 << 30;

3) 默认负载因子、实际元素数量:

    //负载因子的默认大小,
    //元素的数量/容量得到的实际负载数值与负载因子进行对比,来决定容量的大小以及是否扩容;
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    //HashMap的实际元素数量
    transient int size;

4)table——Entry数组;


    //Node是Map.Entry接口的实现类,可以将这个table理解为是一个entry数组;
    //每一个Node即entry,本质都是一个单向链表
    transient Node<K,V>[] table;

 5)Map已经修改的次数、下一次扩容的大小、存储负载因子的常量:

    //HashMap已在结构上修改的次数 结构修改是指更改 HashMap 中的映射数量或以其他方式修改其内部结构(例如,重新散列)的那些
    transient int modCount;

    //下一次HashMap扩容的阀值大小,如果尚未扩容,则该字段保存初始entry数组的容量,或用零表示
    int threshold;

    //存储负载因子的常量,初始化的时候将默认的负载因子赋值给它;
    final float loadFactor;

什么是Map已经修改的次数?

6)构造函数1——默认的无参构造函数:

        将默认的负载因子0.75f 传给 存储负载因子的常量;

    //默认的构造函数
    public HashMap() {
        //将默认的负载因子0.75f传给存储负载因子的常量
        this.loadFactor = DEFAULT_LOAD_FACTOR; 
    }

7)构造函数2——带指定容量参数的构造函数

        将默认的负载因子0.75f当做负载因子常量,连同用户传入的容量参数,一起传给两个参数都带的构造函数,并调用之。

    //带指定容量参数的构造函数
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

8)构造函数3——带指定容量大小和负载因子大小参数的构造函数

//带指定容量大小和负载因子大小参数的构造函数
public HashMap(int initialCapacity, float loadFactor) {
	//指定的容量大小不可以小于0,否则将抛出IllegalArgumentException异常
	if (initialCapacity < 0)
		throw new IllegalArgumentException("Illegal initial capacity: " +
										   initialCapacity);
	 //判定指定的容量大小是否大于HashMap的容量极限
	if (initialCapacity > MAXIMUM_CAPACITY)
		initialCapacity = MAXIMUM_CAPACITY;
	 //指定的负载因子不可以小于0或为Null,否则将抛出IllegalArgumentException异常
	if (loadFactor <= 0 || Float.isNaN(loadFactor))
		throw new IllegalArgumentException("Illegal load factor: " +
										   loadFactor);
	//传入用户指定的负载因子 
	this.loadFactor = loadFactor;
	
    //Map扩容的阀值大小
	//tableSizeFor方法用于查找到距离容量initialCapacity最近的2次幂值;
	this.threshold = tableSizeFor(initialCapacity);
}

9)tableSizeFor()方法: 

        在刚才的构造函数中,我们看到了tableSizeFor()这一个方法,将容量参数传进去,返回结果赋值给threshold 。

看源码:

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

        可以看出,其实代码很简单,就是对容量cap进行了几次无符号右移的操作,最后返回。

那么这个方法的目的和作用是什么呢? 

        这个方法的作用是:用于查找距离容量cap最近的2次幂值,比如:

  •         cap是5~7,那么该方法的返回结果就是8;
  •         cap是9~15,那么该方法的返回结果就是16;
  •         cap是17~31,那么该方法的返回结果就是32;
  •         以此类推...

 10)构造函数4——传入一个Map集合的构造函数:

//传入一个Map集合,将Map集合中元素Map.Entry全部添加进HashMap实例中
public HashMap(Map<? extends K, ? extends V> m) {
	this.loadFactor = DEFAULT_LOAD_FACTOR;
	//此构造方法主要实现了Map.putAll()
	putMapEntries(m, false);
}

 ·

举报

相关推荐

0 条评论