集合底层学习笔记
1|0集合的底层原理
数据结构中有 数组 和 链表 来实现对数据的存储,但这两者基本上就是两个极端。
- 数组:数组存储区间是连续的,占用内存严重,故空间复杂度很大。但数组的二分查找时间复杂度很小,为O(1);数组的特点是:寻址容易,插入和删除困难。
- 链表:链表存储区间不连续,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,O(N).链表的特点:寻址困难,插入和删除容易。
- 哈希表:哈希表既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。
当链表数组的容量超过初始容量的0.75时,再散列将链表数组扩大2倍,把原链表数组搬移到新链表的数组中
加载因子(默认0.75):为什么需要使用加载因子,为什么需要扩容?因为如果填充很大,说明利用的空间很多,如果一直不进行扩容,链表就会越来越长,这样查找的效率很低,因为链表的长度很大(最新版本使用了红黑树后会改进很多),扩容之后,将原来链表数组的每一个链表分成奇偶两个子链表分别挂在新链表数组的散列位置,这样就减少了每个链表的长度,增加查找效率。
1|1一.HashMap
HashMap本来是以空间换时间,所以填充比没必要太大。但是填充比太小又会导致空间浪费。如果关注内存,填充比可以稍大,如果关注查找性能,填充比可以稍小。
原理图:
解决hash冲突的办法:
- 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
- 再哈希法
- 连地址法
- 建立一个公共溢出区
Java中hashMap的解决办法就是采用的连地址法(也称为拉链法)。
源码分析:
1.位桶数组
2.数组元素Node<K,V>实现了Entry接口
3.HashMap如何put(key,value)
链地址法解决hash冲突
添加键值对put(key,value)的过程:
- 判断键值对数组tab[]是否为空或为null,否则以默认大小resize();
- 根据键值key计算hash值得到插入的数组索引i,如果tab[i]=null,直接新建节点添加,否则转入3
- 判断当前数组中处理hash冲突的方式为链表还是红黑树(check第一个节点类型即可),分别处理。
-
HashMap如何getValue值
get(Key)方法时获取key的hash值,计算hash&(n-1)得到再链表数组中的位置first=tab[hash&(n-1)],先判断first的key是否与参数key相等,不等就遍历后面的链表找到相同的key值返回对应的value值即可。
5.HashMap的扩容机制resize();
构建hash表时,如果不指明初始大小,默认大小为16(即Node数组大小16),如果Node[]数组中的元素达到(填充比Node.length)重新调整HashMap大小变为原来2倍大小,扩容很耗时。
6.JDK1.8使用红黑树的改进
再Java jdk8中对HashMap的源码进行了优化,再jdk7中,HashMap处理碰撞的时候,都是采用链表来存储,当碰撞的节点很多时,查询时间为O(N).
在jdk8中,HashMap处理碰撞增加了红黑树这种数据结构,当碰撞节点较少时,采用链表存储,当较大时(>8个),采用红黑树(特点是查询时间是O(lgn)存储(有一个阈值控制,大于阈值(8个),将链表存储转换为红黑树存储)。
问题分析:
哈希碰撞会对hashMap的性能带来灾难性的影响。如果多个hashCode()的值落到同一个桶内时,这些值是存储到一个链表中的。最坏的情况下,所有的key都映射到同一个桶内,这样hashMap就退化成了一个链表——查找时间从0(1)到O(N).
随着H爱上Map的到校的增长,get()方法的开销也越来越大,由于所有的记录都在同一个桶里的超长链表内,平均查询一条记录就需要遍历一半的列表。
JDK1.8HashMap的红黑树是这样解决的:
如果每个桶中的记录过大的话(当前是8),HashMap会动态的使用一个专门的treeMap实现来替换掉它。这样做的结果会更好,是O(logn),而不是糟糕的O(n).
前面产生冲突的哪些KEY对应的记录只是简单的追加到一个链表后面,这些记录只能通过遍历来进行查找。但是超过这个阈值后HashMap开始将列表升级成一个二叉树,使用哈希值作为树的分支变量,如果两个哈希值不同,但指向同一个桶的话,较大的那个会插入到右子树里。如果哈希值相等,HashMap希望key值最好是实现了Comparable接口的,这样它可以按照顺序来进行插入。这对HashMap的key来说并不是必须的,不过如果实现了当然最好。如果没有实现这个接口,在出现严重的哈希碰撞的时候,你就并别指望能获得性能提升了。
1|2二.HashSet
HashSet的内部采用了HashMap作为数据库存储,HashSet其实就是在操作HashMap的Key
- 因为HashMap是无序的,因此HashSet也不能保证元素的顺序。
- 因为HashSet中没有对应同步的操作,因此是线程不安全的。
- 支持null元素(因为HashMap也支持null键和null值)
总结:
1.基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75的HashMap。封装了一个HashMap对象来存储所有的集合元素,所有放入HashSet中的集合实际上由HashMap的key来保存,而HashMap的value则存储了一个PRESENT,它是一个静态的Object对象。
2.当我们试图把某个类的对象当成HashMap的key,或试图将这个类的对象放入HashSet中保存时,重写该类的equals方法和hashCode方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个hashCode返回值相同时,它们通过equals方法比较也应该返回true。通常来说,所有参与计算hashCode返回值的关键属性,都应该作为equals比较的标准。
3.HashSet的其他操作都是基于HashMap的。
1|3三. TreeMap
Java中的TreeMap是一个基于红黑树(Red-Black Tree)实现的有序Map接口的实现类。它提供了键的自然排序或者根据创建时提供的Comparator进行排序的能力。
数据结构:
排序:
内部构成:
主要操作:
性能:
非同步性:
总结:TreeMap通过红黑树数据结构保证了元素的排序和高效的查找,插入,删除操作,并支持自然排序和定制排序两种方式。然而,由于他是非同步的,因此在多线程环境中使用时需要额外的同步措施。
1|4四.TreeSet
TreeSet的底层是TreeMap,添加的数据存入了map的key的位置,而value则固定是PERSENT。TreeSet中的元素是有序且不重复的,因为TreeMap中的key是有序且不重复的。
数据结构:
排序性和唯一性:
主要操作:
排序方式:
总的来说,TreeSet通过红黑树数据结构保证了元素的排序和唯一性,提供了高效的插入、删除和查找操作,并支持两种排序方式:自然排序和定制排序。
1|5五.ArrayList
1.介绍:
ArrayList集合特点为什么是增删慢,查询快。
2.底层实现
3.构造方法
ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表,构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。
集合的属性:
4.扩容机制
扩容源码:
5.总结
1|6六.LinkedList
LinkedList是基于链表结构的一种List,在分析LinkedList源码前我们先对链表简单了解一下。
链表是由一系列非连续的节点组成的存储结构,简单分下类的话,链表又分为_单向链表和双向链表,而单向 / 双向链表又可以分为循环链表和非循环链表_,下面简单就这四种链表进行图解说明:
1.单向链表:
单向链表就是通过每个结点的指针指向下一个节点从而连接起来的结构,最后一个节点的next指向null。
2.单向循环链表:
单向循环链表和单向链表的不同是,最后一个节点的next不是null,而是指向head节点,形成一个环。
3.双向链表:
双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。
4.双向循环链表:
双向循环链表和双向链表的不同在于,第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个环。
1.数据结构
LinkedList是基于双向链表实现的。它内部包含一个存储元素的链表结构,所有的元素都被存储在这个链表中。链表的每个节点都包含一个存储元素的节点和一个指向前一个节点的引用和指向后一个节点的引用。
2.动态数组
LinkedList是一个动态链表,它的大小在运行时可以根据需要进行调整。当元素数量超过当前容量时,LinkedList会自动扩展其大小。
3.查询性能
LinkedList具有较低的查询性能,因为他需要从链表的头部或尾部遍历到指定的索引位置。这种遍历方式相对较慢,尤其是当要查找的元素位于链表的中间时。
4.插入和删除元素
LinkedList在插入和删除元素时具有较高的性能。只需要修改元素的前后引用,而不需要像ArrayList那样移动后续元素。插入元素的时间复杂度为O(1),删除元素的时间复杂度为O(1)。
5.线程安全
LinkedList不是线程安全的,在多线程环境下需要额外的同步措施。可以使用Collections.synchronizedList(List
__EOF__

本文链接:https://www.cnblogs.com/chenlei210162701002/p/18404639.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?