您好!欢迎来到源码码网!

Java 集合 | 底层源码解析

  • 源码教程
  • 来源:源码码网
  • 编辑:admin
  • 时间:2020-05-28 15:33
  • 阅读:1225

 在 Java 中,我们经常会使用到一些处理缓存数据的集合类,这些集合类都有自己的特点,今天主要分享下 Java 集合中几种经常用的 Map、List、Set。



目录


1、Map


一、背景

二、Map家族

三、HashMap、Hashtable等

四、HashMap 底层数据结构


2、List


一、List 包括的子类

二、ArrayList

三、ArrayList 源码分析

四、LinkedList

五、LinkedList 源码分析


3、Set


一、Set的实质

二、HashSet

三、TreeSet





01

集合 1:Map



背景

如果一个海量的数据中,需要查询某个指定的信息,这时候,可能会犹如大海捞针,这时候,可以使用 Map 来进行一个获取。因为 Map 是键值对集合。Map这种键值(key-value)映射表的数据结构,作用就是通过key能够高效、快速查找value。


举一个例子:

image.png

Map<K, V>是一种键-值映射表,当我们调用put(K key, V value)方法时,就把key和value做了映射并放入Map。当我们调用V get(K key)时,就可以通过key获取到对应的value。如果key不存在,则返回null。和List类似,Map也是一个接口,最常用的实现类是HashMap。

在 Map<K, V> 中,如果遍历的时候,其 key 是无序的,如何理解:

image.png

从上面的打印结果来看,其是无序的,有序的答案可以在下面找到。


接下来我们分析下 Map ,首先我们先看看 Map 家族:


image.png

它的子孙下面有我们常用的 HashMap、LinkedHashMap,也有 TreeMap,另外还有继承 Dictionary、实现 Map 接口的 Hashtable。


下面针对各个实现类的特点来说明:

(1)HashMap:它根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有高效的访问速度,但遍历顺序却是不确定的。HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap 非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的静态方法 synchronizedMap 方法使 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap(分段加锁)。

(2)LinkedHashMap:LinkedHashMap 是 HashMap 的一个子类,替 HashMap 完成了输入顺序的记录功能,所以要想实现像输出同输入顺序一致,应该使用 LinkedHashMap。

(3)TreeMap:TreeMap 实现 SortedMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用 TreeMap 时,key 必须实现Comparable 接口或者在构造 TreeMap 传入自定义的 Comparator,否则会在运行时抛出 ClassCastException 类型的异常。

(4)Hashtable:Hashtable继承 Dictionary 类,实现 Map 接口,很多映射的常用功能与 HashMap 类似,Hashtable 采用"拉链法"实现哈希表,不同的是它来自 Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 Hashtable,但并发性不如 ConcurrentHashMap,因为ConcurrentHashMap 引入了分段锁。Hashtable 使用 synchronized 来保证线程安全,在线程竞争激烈的情况下 HashTable 的效率非常低下。不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换。Hashtable 并不是像 ConcurrentHashMap 对数组的每个位置加锁,而是对操作加锁,性能较差。 

上面讲到了 HashMap、Hashtable、ConcurrentHashMap,接下来先看看 HashMap 的源码实现:

image.png

image.png

image.png

image.png

从上面看到,HashMap 主要是数组 + 链表结构组成。HashMap 扩容是成倍的扩容。为什么是成倍,而不是1.5或其他的倍数呢?既然 HashMap 在进行 put 的时候针对 key 做了一些列的 hash 以及与运算就是为了减少碰撞的一个概率,如果扩容后的大小不是2的n次幂的话,之前做的不是白费了吗?

image.png

扩容后会重新把原来的所有的数据 key 的 hash 重新计算放入扩容后的数组里面去。为什么要这样做?因为不同的数组大小通过 key 的 hash 出来的下标是不一样的。还有,数组长度保持2的次幂,length-1的低位都为1,会使得获得的数组索引 index 更加均匀。


为何说 Hashmap 是非线程安全的呢?原因:当多线程并发时,检测到总数量超过门限值的时候就会同时调用 resize 操作,各自生成新的数组并rehash 后赋给底层数组,结果最终只有最后一个线程生成的新数组被赋给table 变量,其他线程均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的 table 作为原始数组,这样也是有问题滴。

image.png

image.png

ConcurrentHashMap 采用了分段锁技术来将数据分成一段段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

02

集合 2:List



集合 List 是接口 Collection 的子接口,也是大家经常用到的数据缓存。List 进行了元素排序,且允许存放相同的元素,即有序,可重复。我们先看看有哪些子类:

image.png

ArrayList:


优点:操作读取操作效率高,基于数组实现的,可以为null值,可以允许重复元素,有序,异步。

缺点:由于它是由动态数组实现的,不适合频繁的对元素的插入和删除操作,因为每次插入和删除都需要移动数组中的元素。


LinkedList:


优点:LinkedList由双链表实现,增删由于不需要移动底层数组数据,其底层是链表实现的,只需要修改链表节点指针,对元素的插入和删除效率较高。

缺点:遍历效率较低。HashMap和双链表也有关系。


ArrayList 底层是一个变长的数组,基本上等同于Vector,但是ArrayList对writeObjec() 和 readObject()方法实现了同步。

image.png

从注释,我们知道 ArrayList 是线程不安全的,多线程环境下要通过外部的同步策略后使用,比如List list = Collections.synchronizedList(new ArrayList(…))。

image.png

image.png

image.png

当调用add函数时,会调用ensureCapacityInternal函数进行扩容,每次扩容为原来大小的1.5倍,但是当第一次添加元素或者列表中元素个数小于10的话,列表容量默认为10。

image.png

扩容原理:根据当前数组的大小,判断是否小于默认值10,如果大于,则需要扩容至当前数组大小的1.5倍,重新将新扩容的数组数据copy只当前elementData,最后将传入的元素赋值给size++位置。

image.png

image.png


image.png

image.png


根据源码可知,当调用add函数时,首先要调用ensureCapacityInternal(size + 1),该函数是进行自动扩容的,效率低的原因也就是在这个扩容上了,每次新增都要对现有的数组进行一次1.5倍的扩大,数组间值的copy等,最后等扩容完毕,有空间位置了,将数组size+1的位置放入元素e,实现新增。


删除时源码:

image.png


在删除index位置的元素时,要先调用 rangeCheck(index) 进行 index 的check,index 要超过当前个数,则判定越界,抛出异常,throw new IndexOutOfBoundsException(outOfBoundsMsg(index)),其他函数也有用到如:get(int index),set(int index, E element) 等后面删除重点在于计算删除的index是末尾还是中间位置,末尾直接--,然后置空完事,如果是中间位置,那就要进行一个数组间的copy,重新组合数组数据了,这一就比较耗性能了。


而查询:

image.png


获取指定index的元素,首先调用rangeCheck(index)进行index的check,通过后直接获取数组的下标index获取数据,没有任何多余操作,高效。




LinkedList 继承AbstractSequentialList和实现List接口,新增接口如下:

    addFirst(E e):将指定元素添加到刘表开头
    addLast(E e):将指定元素添加到列表末尾
    descendingIterator():以逆向顺序返回列表的迭代器
    element():获取但不移除列表的第一个元素
    getFirst():返回列表的第一个元素
    getLast():返回列表的最后一个元素
    offerFirst(E e):在列表开头插入指定元素
    offerLast(E e):在列表尾部插入指定元素
    peekFirst():获取但不移除列表的第一个元素
    peekLast():获取但不移除列表的最后一个元素
    pollFirst():获取并移除列表的最后一个元素
    pollLast():获取并移除列表的最后一个元素
    pop():从列表所表示的堆栈弹出一个元素
    push(E e);将元素推入列表表示的堆栈
    removeFirst():移除并返回列表的第一个元素
    removeLast():移除并返回列表的最后一个元素
    removeFirstOccurrence(E e):从列表中移除第一次出现的指定元素
    removeLastOccurrence(E e):从列表中移除最后一次出现的指定元素

    LinkedList 的实现原理:LinkedList 的实现是一个双向链表。在 Jdk 1.6中是一个带空头的循环双向链表,而在 Jdk1.7+ 中则变为不带空头的双向链表,这从源码中可以看出:


    image.png

    image.png

    为什么说 LinkedList 增删很快呢?

    image.png

    image.png


    从注释看,add函数实则是将元素append至list的末尾,具体过程是:新建一个Node节点,其中将后面的那个节点last作为新节点的前置节点,后节点为null;将这个新Node节点作为整个list的后节点,如果之前的后节点l为null,将新建的Node作为list的前节点,否则,list的后节点指针指向新建Node,最后size+1,当前llist操作数modCount+1。


    在add一个新元素时,LinkedList 所关心的重要数据,一共两个变量,一个first,一个last,这大大提升了插入时的效率,且默认是追加至末尾,保证了顺序。


    image.png

    image.png

    image.png


    删除指定index的元素,删除之前要调用checkElementIndex(index)去check一下index是否存在元素,如果不存在抛出throw new IndexOutOfBoundsException(outOfBoundsMsg(index));越界错误,同样这个check方法也是很多方法用到的,如:get(int index),set(int index, E element)等。


    注释讲,删除的是非空的节点,这里的node节点也是通过node(index)获取的,分别根据当前Node得到链表上的关节要素:element、next、prev,分别对 prev 和 next 进行判断,以便对当前 list 的前后节点进行重新赋值,frist和last,最后将节点的element置为null,个数-1,操作数+1。根据以上分析,remove节点关键的变量,是Node实例本身的局部变量 next、prev、item 重新构建内部变量指针指向,以及list的局部变量first和last保证节点相连。这些变量的操作使得其删除动作也很高效。

    image.png


    获取指定index位置的node,获取之前还是调用checkElementIndex(index)进行检查元素,之后通过node(index)获取元素,上文有提到,node的获取是遍历得到的元素,所以相对性能效率会低一些。


    03

    集合 3:Set



    Set 集合在我们日常中,用到的也比较多。用于存储不重复的元素集合,它主要提供下面几种方法:

    • 将元素添加进Set<E>:add(E e)

    • 将元素从Set<E>删除:remove(Object e)

    • 判断是否包含元素:contains(Object e)

    这几种方法返回结果都是 boolean值,即返回是否正确或成功。Set 相当于只存储key、不存储value的Map。我们经常用 Set 用于去除重复元素,因为 重复add同一个 key 时,会返回 false。

    image.png


    Set 子孙中主要有:HashSet、SortedSet。HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口,而TreeSet 实现了SortedSet接口,从而保证元素是有序的。


    HashSet 添加后输出也是无序的:

    image.png


    看到输出的顺序既不是添加的顺序,也不是String排序的顺序,在不同版本的JDK中,这个顺序也可能是不同的。


    换成TreeSet:

    image.png

    在遍历TreeSet时,输出就是有序的,不是添加时的顺序,而是元素的排序顺序。


    注意:添加的元素必须实现Comparable接口,如果没有实现Comparable接口,那么创建TreeSet时必须传入一个Comparator对象。


    本文转自网络,如有侵权请及时联系我们删除。


























    特别声明:
    1、如无特殊说明,内容均为本站原创发布,转载请注明出处;
    2、部分转载文章已注明出处,转载目的为学习和交流,如有侵犯,请联系客服删除;
    3、编辑非《源码码网》的文章均由用户编辑发布,不代表本站立场,如涉及侵犯,请联系删除;
    全部评论(0)
    推荐阅读
    • 陀螺匠OA办公系统源码 PHP + Vue 前后端分离 完整可商用
    • 陀螺匠OA办公系统源码 PHP + Vue 前后端分离 完整可商用
    • 项目介绍《陀螺匠》是一套采用Laravel9框架结合Swoole高性能框架与Vue.js前端技术栈构建的AI+低代码双引擎智能办公系统。陀螺匠深度融合了AI智能体以及低代码开发平台,系统简约,易于功能扩展,方便二次开发。旨在为企业提供一站式、数智化转型的全方位解决方案,助力企业高效运营与智能决策。源码说明源码说明:项目代码100%开源,基于AGPL-3.0,支持免费商用!技术栈:系统基于Laravel9+Swoole+Vue.
    • 软件教程
    • 来源:CRMEB
    • 编辑:CRMEB
    • 时间:2026-03-27 15:33
    • 阅读:654
    • open claw最近很火,如何理性看待?
    • open claw最近很火,如何理性看待?
    • openclaw是2026年初科技圈最火爆的现象级话题。它不仅仅是又一个AI产品,更被百川智能的王小川誉为与ChatGPT诞生同等重要的里程碑——标志着AI从”只会说”的对话者,进化到了”能够做”的行动者。对于这股全民”养虾”热潮,我的观察是:它代表了一次人机关系的革命性跃迁,但同时也是一场充满风险与泡沫的狂欢,其真正的价值需要在喧嚣褪去、安全与成本的挑战被克服后才能显现。下
    • 行业资讯
    • 来源:源码码网
    • 编辑:源码码网
    • 时间:2026-03-17 11:06
    • 阅读:716
    • 原生小程序和封装的小程序有什么区别
    • 原生小程序和封装的小程序有什么区别
    • 原生小程序和封装的小程序是两种不同的开发模式,主要区别在于开发方式、性能表现、代码维护以及跨平台能力。为了让你更清晰地理解,我们可以把“原生”比作用钢筋水泥直接盖楼,而“封装”则是用预制板或套件来组装楼。以下是详细的对比分析:1.定义与本质原生小程序:定义: 直接使用微信官方提供的开发工具、语法和API进行开发。语言: 使用微信自己定义的一套技术栈(WXML(微信标记语言)做结构、WXSS(微信样式表)做样式、JS
    • 行业资讯
    • 来源:源码码网
    • 编辑:源码码网
    • 时间:2026-03-06 11:54
    • 阅读:944
    • 基于NetCore(Razor Page)开发的Cms建站系统MIT协议
    • 基于NetCore(Razor Page)开发的Cms建站系统MIT协议
    • FytSoaCms一款基于NetCore2.2/3.1(RazorPage)功能强大的Cms建站系统,支持前后端分离。简介:模块化:全新的架构和模块化的开发机制,便于灵活扩展和二次开发。模型/栏目/分类信息体系:通过栏目和模型绑定,以及不同的模型类型,不同栏目可以实现差异化的功能,轻松实现诸如资讯、下载、讨论和图片等功能。通过分类信息和栏目绑定,可以自动建立索引表,轻松实现复杂的信息检索。FytSoa是一套基于NetCore+SqlS
    • 开发工具
    • 来源:gitee
    • 编辑:源码码网
    • 时间:2026-01-24 22:21
    • 阅读:799
    • 基于JAVA开发的企业级平台微信公众号管理系统SmartWx
    • 基于JAVA开发的企业级平台微信公众号管理系统SmartWx
    • SmartWx微信公众号管理系统是一个完整的微信公众号web操作版,直接编译即可运行。让用户不再用关心微信的底层接口,直接使用页面进行操作,简单方便。包括服务器绑定、文本管理、图文管理、菜单管理、粉丝管理、群发消息等。技术框架开发语言:JAVA数据库:MYSQLJAVA开发框架:SpringMVC+Spring+Mybatis缓存框架:j2cache前端开发框架:Layui+JQuery+html前台模板引擎:art-templat
    • 源码教程
    • 来源:gitee
    • 编辑:源码码网
    • 时间:2026-01-24 21:53
    • 阅读:833
    联系客服
    源码代售 源码咨询 技术开发 联系客服
    029-84538663
    手机版

    扫一扫进手机版
    返回顶部