当前位置:   article > 正文

HashMap的数据结构(超详细版)_hashmap结构

hashmap结构


影响HashMap性能的两个重要参数以及HashMap的几个重要成员变量

1.初始容量

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  • 1

初始容量用来规定哈希表数组的长度,默认值为16,因为16是2的整数次幂的原因,再小数据量下的情况下,能减少哈希冲突,提高性能。在大存储容量数据的时候,也尽量将数组长度定义为2的幂次方,这样能更好的与索引计算公式i=(n-1)&hash配合使用,从而提升性能。

2.加载因子

final float loadFactor;
  • 1

用来表示HashMap集合中元素的填满程度,默认为0.75f。越大则表示允许填满的元素就多,集合的空间利用率就越高,但是冲突的机会增加。反之,越小则冲突的机会就会越少,但是空间很多就浪费。

所以在设置初始容量时,应优先考虑到初始容量及其他加载因子,预估设置初始容量,最大程度的减少rehash重建内部数据结构的次数,极大的减少了扩容操作。

  • 底层数组
transient Node<K,V>[] table;
  • 1

保存KV键值对的数组,每个KV键值对都被封装成一个Node对象。

  • 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;//1073741824
  • 1

HashMap的最大容量值,扩容时如果超出,则不扩容。

  • 扩容阈值
int threshold
  • 1

用于判断数组是否需要扩,扩容阈值threshold=数组容量×加载因子。

  • KV键值对数量
int size
  • 1

HashMap底层存储机制概述

jdk1.8以前HashMap内部数据结构使用数组+链表进行存储。(了解即可)
jdk1.8以后HashMap内部数据结构使用数组+链表+红黑树进行存储。

//数组
transient Node<K,V>[] table;
//链表节点类
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;//哈希值
        final K key;//键
        V value;//值
        Node<K,V> next;//下一个元素
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

数组类型为Node[],每个Node都保存了某个KV键值对元素的key、value、hash、next等值。由于next的存在,所以每个Node对象都是一个单向链表中的组成节点。

当新添加一个KV键值对元素时,通过该元素的key的hash值,计算该元素在数组中应该保存的下标位置。如果该下标位置如果已经存在其它Node对象,则采用链地址法处理,即将新添加的KV键值对元素将以链表形式存储。将新元素封装成一个新的Node对象,插入到该下标位置的链表尾部(尾插法)。当链表的长度超过8并且数组长度大于64时,为了避免查找搜索性能下降,该链表会转换一个红黑树
(附带大佬做的好图一张,仅供参考理解)


HashMap的初始化与扩容方式

1.初始化

HashMap的构造方法进行了重载,具有无参、有参等多种构造方式。
无参构造:默认定义加载因子为0.75

 public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
 static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • 1
  • 2
  • 3
  • 4

指定容量的有参构造:传入所需的最小容量值,依旧使用默认加载因子。

public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
  • 1
  • 2
  • 3

指定容量和加载因子的有参构造方法:指定容量值与加载因子。

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;
        this.threshold = tableSizeFor(initialCapacity);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2.扩容方式

HashMap底层采用数组+链表+红黑树,扩容过程中需要按照数组容量加载因子来进行判断。

HashMap的扩容方法是resize()方法,发生下列几种情况时,会发生扩容:

// 添加新元素 
final V putVal(int hash, K key, V value){
 //... 
 // 判断当前集合中的元素数量,是否超过阈值threshol
  if (++size > threshold)
            resize();
 }
 //...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 当我们第一次添加KV键值对时,如果数组此时为空,则会默认扩容为16。
  2. 加入元素时,如果链表长度大于阈值(默认为8)并且数组长度小于6,就会产生数组扩容。
  3. 添加元素后,当HashMap中的元素个数超过【数组大小×加载因子】时,原数组扩容2倍。例如:加载因子的默认值为0.75,数组容量默认为16,当HashMap中的元素个数超过16×0.75=12时,数组容量扩容16×2=32。
  4. HashMap加入新元素时,如果链表长度大于8时,会尝试将当前链表转换为红黑树。在转换红黑树之前,会判断数组长度,如果小于64,会产生数组扩容。如果数组长度大于64,才会将链表转换为红黑树。
 final void treeifyBin(Node<K,V>[] tab, int hash) {
	 //...
 	//如果数组长度n小于64或者数组长度为空,		
 	if (tab == null || (n = tab.length) < 	MIN_TREEIFY_CAPACITY)
            resize();//数组扩容
  	//
  	 else if ((e = tab[index = (n - 1) & hash]) != null) {
  	 		//遍历链表节点,转换为红黑树
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

总结

  • HashMap的数据结构采用数组+链表+红黑树。
  • HashMap的按照key的hash值计算数组中的存储位置下标,计算方式:(n-1)&hash。
  • 如果在该下标位置已经存在元素,代表产生哈希冲突,则采用链地址法处理,以单向链表的形式,将新元素存储在链表的尾部(尾插法)。
  • 当链表中Node节点的数量大于8并且数组的长度大于64时,链表会转换成一个红黑树,有利于查找搜索。
  • HashMap的默认容量为16,加载因子为0.75f,当集合元素个数超过扩容阈值【容量×加载因子】时,HashMap会将底层数组容量按照2倍进行扩容。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/591038
推荐阅读
相关标签
  

闽ICP备14008679号