JavaSE 集合 Vol.1 Collection 源码入门
1. 前言
· 下期传送门:JavaSE 集合 Vol.2 Map 源码入门 (含截图分析)
· 本文章是用于 个人学习过程中 整理知识点的帖子,主题为:JavaSE 集合 Vol.1 Collection 源码入门
· 本文章 只包含了各个 集合实现类 常用的方法源码分析,并不全面,旨在培养源码的阅读能力。
· 本文章出现的 遗漏、错误 欢迎点开这篇文章的各位指出。
· 本文章的知识大纲根据 韩顺平老师 JavaSE 教学视频 进行编写。
【感谢韩顺平老师带来的优质教学和对教育作出的贡献】
2. Collection 集合 框架体系图
3. Collection 集合
● 常用方法 ●
指令 | 解释 |
---|---|
add ( Object e ) | 添加单个元素 |
addAll ( Collection c ) | 添加多个元素 |
remove ( Object e ) | 删除单个元素 |
removeAll ( Collection<?> c ) | 删除多个元素 |
contains ( Object e ) | 查找单个元素 |
cotainsAll ( Collection<?> e ) | 查找多个元素 |
clear ( ) | 清空集合元素 |
size ( ) | 查看集合长度 |
isEmpty ( ) | 判断集合是否为空 |
● List 接口
基本介绍:
· List 集合类的元素是 有序可重复 的,也就是说它的 添加和取出 顺序是一致的,并且支持索引。
基本方法:
指令 | 解释 |
---|---|
add ( Object e ) | 插入单个元素 |
add ( int index , Object element ) | 从 指定位置 中 插入单个元素 |
addAll ( Object element ) | 插入多个元素 |
addAll ( int index , Collection c ) | 从 指定位置 中 插入多个元素 |
indexOf ( Object o ) | 返回 元素在集合中 首次出现 的 位置下标 |
lastIndexOf ( Object o ) | 返回 元素在集合中 最后一次出现 的 位置下标 |
remove ( Object o ) | 删除元素 |
set ( int index , Object element ) | 修改 指定位置下标 的 元素 |
subList ( int fromIndex , int toIndex ) | 返回 指定位置范围内 的 元素内容 ( 前闭后开 ) |
· ArrayList 实现类
● 无参构造 ●
● ArrayList 的数据都存放在数组: transient Object [ ] elementData 之中
● transient 译为 短暂的 作用是:不希望该属性被序列化
● 无参构造时 对象将 elementData 初始化为一个空数组
● 有参构造 ●
● 有参构造时 对象直接定义 elementData 数组的大小
● 扩容机制 ●
· LinkedList 实现类
● 无参构造 ●
● LinkedList 底层维护着一个双向链表
● 无参构造时,直接新建一个 LinkedList 对象
● 添加数据 [ 流程概述 ] ●
执行添加操作时 调用 add() 方法 【 添加元素默认从尾部添加 】
① 进入 add() 方法 进入 创建双向链表 的方法
② 进入 linkLast() 方法
a. 如果 新添加的节点 为 双向链表的 第一个节点
i. 则让头节点 和 尾节点 都指向 该节点
b.如果 新添加的节点 不为 双向链表的 第一个节点
i. 则让原尾节点的后一个节点 指向 该节点
ii. 该节点的前一个节点 指向 原尾节点
iii. 尾节点 指向 该节点
③ 完成 添加数据 操作
综上所述:下面将对 添加数据 进行源码分析
● 添加数据 ●
● 删除数据 [ 流程概述 ] ●
执行 删除操作时 调用 remove() 方法 【 无参数代表删除头节点 】
① 进入 removeFirst() 方法 定义 头节点 的 指针F 进入 真正删除数据 的方法
② 进入 unlinkFirst() 方法
a. 定义 头节点的后节点 的 指针N
b. 通过 指针F 删除 头节点 的数据 和 后节点
c. 如果 指针N 不为空 则设置 指针N 的 前节点 为空
d. 返回 头节点 原来的数据 用于特殊需求
③ 完成 删除数据 操作
综上所述:下面将对 删除数据 进行源码分析
● 删除数据 ●
· Vector 实现类
● 无参构造 ●
● Vector 的数据都存放在数组: protected Object[] elementData 之中
● protected 译为 受保护的 作用是:线程安全
● 无参构造时,通过有参构造 将 elementData 初始化为 10 长度的数组
● 有参构造 ●
● Vector 的数据都存放在数组: protected Object[] elementData 之中
● protected 译为 受保护的 作用是:线程安全
● 有参构造时,通过有参构造 将 elementData 初始化为 10 长度的数组
● 扩容机制 [ 流程概述 ] ●
① 确认是否要进行扩容
a. 进入 enshurCapacityHelper 方法
如果 所需最小容量 > 当前数组长度 则进入 真正的扩容 方法
b. 进入 grow 方法
oldCapacity 属性 [保存旧数组的长度]
newCapacity 属性 [保存新数组的长度,如果容量增量 > 0 扩容为旧数组的2倍]
如果 新容量 < 所需最小容量 则 新容量 = 所需最小容量
如果 新容量 > 类最大容量 则 进入大数扩容
最后使用 Arrays.CopyOf() 方法 进行数组扩容
② 对数组进行赋值
综上所述:下面将对 扩容机制 进行源码分析
● 扩容机制 ●
● Set 接口
基本介绍:
· 每一个实现 Set 接口类 的对象所包含元素是无序不可重复的,而且不支持索引。
· HashSet 实现类
● 无参构造 ●
● HashSet 的底层本质上是 HashMap
● 创建 HashSet 对象 也就是 创建 HashMap 对象
● 添加元素 [ 流程概述 ] ●
① 使用 HashSet 添加元素时,对象会先得到元素的 hash 值 并将其转成索引值
② 找到存储数据表 Table 查看这个索引位置是否已经存放元素
a.如果没有 则直接加入
b.如果有 则调用 equals 比较【equals 可以通过重写修改判断条件】
i.如果添加的元素重复 则放弃添加
ii.如果不重复 则添加元素到索引值对应链表的最后一位
根据 添加情况的不同分为:首次添加元素 、元素查重 两种添加情况
元素查重 是指:当 待添加的节点 分配到 不为空的链表中 进行处理的行为
元素查重 分为:添加不重复元素、添加重复元素 两种情况
综上所述:下面将对于 首次添加元素 、添加不重复元素 、 添加重复元素 三种情况 进行源码分析
● 首次添加元素 ●
● 添加不重复元素 ●
● 添加重复元素 ●
● 扩容机制 [ 流程概述 ] ●
① 首次添加数据时,将 Table 数组容量 设置为 默认初始容量 16 ,数组阈值 12
② 如果发生以下两种情况的任意一种:
A.Table 数组长度 超过当前 数组阈值
B.Table 数组 中某一条链表的 结点个数 超过当前 链表阈值
Table 数组容量 就会扩容到 16 × 2 = 32 ,数组阈值 也会随之更新:32 × 0.75 = 24 以此类推
名词解释:
【 Table 数组:当前 HashSet 对象的 哈希表 】
【 数组阈值 :Table 数组容量 × 加载因子 】
【 数组阈值 :threshold
】【 Table 数组长度 :size
】
【 加载因子 :LOADFACTOR
默认值 0.75 】
【 默认初始容量 :DEFAULT_INITIAL_CAPACITY
默认值 16 】
【 链表阈值:TREEIFY_THRESHOLD
默认值 8 】
综上所述:下面将根据如上 三种情况 分别对 扩容机制 进行不同角度的源码分析
● 扩容机制 [ ① 首次添加数据 ] ●
● 扩容机制 [ ② 扩容哈希表 的 A 情况 ] ●
● 扩容机制 [ ② 扩容哈希表 的 B 情况 ] ●
● 树化链表 [ 流程概述 ] ●
在 Java 8 中,如果以下两个条件同时发生:
1. Table数组 中的一条链表的 结点个数 超过 链表阈值
2. Table 数组长度 不小于 最小树容量
就会对该链表进行树化操作,将其变成 红黑树。
名词解释:
【 Table 数组:当前 HashSet 对象的 哈希表 】
【 Table 数组长度 :size
】
【 链表阈值:TREEIFY_THRESHOLD
默认值 8 】
【 最小树容量:MIN_TREEIFY_CAPACITY
默认值 64 】
【红黑树:平衡二叉树的子分支,一种用于存储操作数据的高效数据结构 】
综上所述:下面将对 树化链表 进行源码分析
● 树化链表 ●
· LinkedHashSet 实现类
● 基本介绍 ●
LinkedHashSet 是 HashSet 的子类。它的底层是 LinkedHashMap。其结构是:数组+双向链表。
LinkeHashSet 遍历元素 和 插入元素 的 顺序一致,但它 不允许元素重复。
● 底层结构 ●
LinkedHashSet 底层维护的是 LinkedHashMap 【HashMap的子类】
首次添加元素时,直接将 Table数组 扩容到 16 ,将添加的元素打包成一个结点
结点 包含两个属性:pre
前结点、 after
后结点
【Table数组的类型:HashMap$Node[]
】 【存放结点的类型: LinkedHashMap$Entry
】
【这里体现出了多态性质: 子类的对象可以存放至父类的对象数组中】
● 无参构造 ●
● 添加元素 [ 流程概述 ] ●
在添加一个元素时,先求出它的 hash 值,再求出索引位置,确定该元素要放在 Table 数组的 哪一个
位置,然后将添加的元素加入到双向链表中,如果元素重复,则不添加。
添加元素 分为 首次添加元素、元素查重 两种情况
综上所述:下面将对 首次添加元素、元素查重 两种情况 进行源码分析
● 首次添加元素 ●
元素查重 分为 元素不重复、元素重复 两种情况
● 元素查重 [ 元素不重复 ] ●
● 元素查重 [ 元素重复 ] ●
· TreeSet 实现类
● 基本介绍 ●
· TreeSet 与 HashSet 是 并类关系 。它的底层是 TreeMap 。其结构是:二叉树 。
· TreeSet 遍历元素 和 插入元素 的 顺序不一致,而且 不允许元素重复。
● 底层结构 ●
· TreeSet 底层维护的是 TreeMap 。
● 基本特点 ●
· 与 HashSet 不同,TreeSet 中提供了一个 特殊的有参构造方法 。
· 这个有参构造方法传入的参数是 Comparator
接口对象 。
· Comparator
接口,又称作 比较器,该接口可以自定义 用于数据之间排序的规则 。
综上所述:下面将演示 有参构造中自定义排序 的 过程。
● 无参构造下遍历 TreeSet ●
● 有参构造自定义排序 ●
· 从 有参构造 自定义排序 中可以看到,我们实现了 Comparator
接口 的 compare 方法 。
· 通过 compare 方法,我们将 当前 TreeSet 对象 存储的数据进行 自定义排序规则 。
· 这里演示的是使用 String 类 的 compareTo 方法 ,比较两个字符串 首字母 ASCII码值 的大小 。
【详细的源码分析,放在 下一章节:Map 源码入门 中讲解】
● Queue 接口
基本介绍:
·
· LinkedList 实现类
· PriorityQueue 实现类
· 下期传送门:JavaSE 集合 Vol.2 Map 源码入门 (含截图分析)