Java基础20--【Set&HashMap】的底层实现

本文详细探讨了Java中Set系列集合的底层实现,包括HashSet、TreeSet和LinkedHashSet。它们都基于特定的Map实现,如HashMap、TreeMap和LinkedHashMap。HashMap在JDK1.7及之前的版本采用数组+链表,JDK1.8开始引入红黑树。当冲突严重时,链表会转换为红黑树以提高查询效率。文章还分析了HashMap的扩容策略和插入过程,并对比了不同加载因子的影响。同时,文章提到了JDK1.8版本的变化,包括树化和反树化的阈值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java基础20-Set系列集合的底层实现

Set的底层实现是什么?
1、HashSet :底层是 HashMap
在这里插入图片描述
new了一个hashmap

2、TreeSet : 底层是TreeMap
在这里插入图片描述

3、LinkedHashSet:底层是 LinkedHashMap
在这里插入图片描述

Set添加元素时用add(元素),而Map添加元素put(key,value)。
发现添加到Set中的元素,是作为底层的Map的key,那么value它们选用了一个Object类型的常量对象PRESENT。
所有的HashSet共用同一个PRESENT对象。
所有的TreeSet共用同一个PRESENT对象。
所有的LinkedHashSet共用同一个PRESENT对象。
在这里插入图片描述
在这里插入图片描述

HashMap的底层实现

Map的底层实现是什么?
1、哈希表系列:
数组 + 链表
数组 + 链表/红黑树
2、TreeMap:红黑树

HashMap的底层实现:
JDK1.7以及之前:数组 + 链表
JDK1.8以及之后:数组 + 链表/红黑树

在这里插入图片描述
数组 + 链表:数组里含有链表
在这里插入图片描述
数组 + 链表/红黑树:数组里含有链表或者树

数组的优点:访问速度快,因为可以根据下标直接定位到某个元素
链表的优点:不需要元素是挨着存储,不需要连续空间,在添加和删除元素时不需要移动元素,只需要修改前后元素的引用关系就可以。
HashMap:会根据key的hashCode–>公式/算法–>[index]
链表: 因为不同的hashCode值,可能得到的[index]是相同的,那么此时就冲突了,那么只能把[index]的多个映射关系用链表连接起来
二叉树的优点:查找的速度比链表快
旧版的HashMap,如果key的hashCode算出了[index]相同的话(我们称为冲突)都在一个table[index]下面,
如果严重的话,会导致[index]下面的链表很长,就会导致查询速度减慢。当链表长到一定程度时,就需要把链表变为二叉树,以提高我们查询速度。
在这里插入图片描述

JDK1.7版本的HashMap的源码跟踪

一、JDK1.6的HashMap:数组+链表
(1)new HashMap()
table数组初始化为了一个长度为16的空数组,threshold=12

  public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;//0.75
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);//12
        table = new Entry[DEFAULT_INITIAL_CAPACITY];//16
    }

二、几个常量与变量的介绍
1、DEFAULT_INITIAL_CAPACITY:16
2、DEFAULT_LOAD_FACTOR:0.75 默认加载因子
3、threshold:阈值/临界值 数组需要考虑扩容的阈值
threshold = capacity(容量,数组的长度) * load factor(加载因子,默认是0.75)
例如: threshold = 16 * 0.75 当数组大概3/4满的时候就考虑扩容
思考:load factor设置为0.9和0.1有什么区别?
0.1:扩容太频繁
0.9:会导致table[index]下面的链表会很长,查询速度就低

三、JDK1.7的HashMap:数组+链表

  1new HashMap()
  table数组初始化为了一个长度为0的空数组

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
源代码分析过程

JDK1.7版本的HashMap的put过程的源码总结和调试过程

2、 put()

步骤:
(1)发现数组table是空数组后,会把数组初始化为长度为16的Entry类型的数组,并且把threshold计算为12(16*0.75)
这里如果你手动指定了数组的capacity,就会判断这个capacity是不是2的n次方,如果不是,会自动纠正为2的n次方

为什么要纠正为2的次方?
①后面算index = hash & table.length-1,这样才能保证[0,table.length-1]范围内
②2的次方,根据它的散列算法,可以保证比较均匀的分散在它的数组的各个位置,避免一条链表上很多元素
(2)如果key是null,特殊对待,key为null的映射关系的hash值为0,index也为0
(3)hash = hash(key)
为了干扰我们key的hashCode值
(4)index = hash & table.length-1
(5)先判断table[index]下面是否有映射关系的key是和我新添加的映射关系的key有重复的,如果有,就用新的value替换旧的value,就结束了
(6)如果没有重复的,决定添加新的映射关系
①看是否需要扩容
扩容的条件:A:size达到阈值threshold B:table[index]下面已经有映射关系,即不为空
如果扩容了,会重新计算hash和index

②把新的映射关系new为一个Entry的对象,放到table[index]中,原来table[index]的映射关系作为新的映射关系的next连接起来。

Entry相当于是一个结点类型。是一个单向链表的结点类型。
class Entry{
int hash;
Object key;
Object value;
Entry next;
}

@Test
	public void test(){
		HashMap map = new HashMap();
		
		map.put(null, "aa");
		map.put("Aa", "bb");
		map.put("BB", "bb");
		map.put("A", "xx");
		map.put("B", "xx");
		
		for (int i = 0; i < 15; i++) {
			map.put(i+"", "xx");
		}
	}
	
	@Test
	public void test2(){
		System.out.println("Aa".hashCode());//2112
		System.out.println("BB".hashCode());//2112
	}

扩容后。table的length变了,算出来的元素的位置也会重新计算,位置会改变,存储过程

JDK1.8版本的HashMap的源码跟踪(day21)

JDK1.8的HashMap的底层实现:数组+链表/红黑树

几个常量和变量:
(1)DEFAULT_INITIAL_CAPACITY:默认的初始容量 16 (1<<4: 1往左移4位,相当于2的4次方,即6)
(2)MAXIMUM_CAPACITY:最大容量 1 << 30
(3)DEFAULT_LOAD_FACTOR:默认加载因子 0.75
(4)TREEIFY_THRESHOLD:默认树化阈值8(当数组中链表的长度到达8时,考虑树化),当链表的长度达到这个值后,要考虑树化
(5)UNTREEIFY_THRESHOLD:默认反树化阈值6(当删除节点后链表的结点变为6时,考虑将树变为链表,链表的结构比树简单)当树中的结点的个数达到这个阈值后,要考虑变为链表
(6)MIN_TREEIFY_CAPACITY:最小树化容量64
当单个的链表的结点个数达到8,并且table的长度达到64,才会树化。
当单个的链表的结点个数达到8,但是table的长度未达到64,会先扩容(扩容后所有映射关系会挪位置,可能不在一个链表上)
(7)Node<K,V>[] table:数组
(8)size:记录有效映射关系的对数,也是Entry对象的个数
(9)int threshold:阈值,当size达到阈值时,考虑扩容
(10)double loadFactor:加载因子,影响扩容的频率

1、new HashMap()
public HashMap() {
//加载因子赋值为0.75
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted 其他字段都是默认值
//threshold是0
//table=null
//size = 0
}

2、put(key,value)
(1)如果第一次添加时
把table初始化为长度为16的数组,threshold = 12
(2)如果不是第一次添加
①会考虑是否key有重复,那么就替换value
②如果table[i]下面不是树,统计table[i]的结点的个数,添加之前达到7个,考虑树化
当单个的链表的结点个数添加之前达到7,并且table的长度达到64,才会树化。
当单个的链表的结点个数添加之前达到7,table的长度未达到64,先扩容。
③table[i]下面已经是树,单独处理,直接把新的映射关系连接到树的叶子结点
④添加后,size达到threshold,还要扩容
一旦扩容,就会调整所有映射关系的位置

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值