Java集合【5】-- Collections源码分析
- 一、Collections接口是做什么的?
- 二、Collections源码之大类方法
- 三、从源码看其他常用方法
- 1. Sort(排序)
- 2. binarySearch(二分搜索)
- 3. reverse(反转)
- 4. Shuffling(混排)
- 5. 交换(swap)
- 6. 拷贝(copy)
- 7. 返回最小的元素(min)
- 8. 返回最大的元素(max)
- 9. 旋转(Rotate)
- 10. 替换所有元素(replaceAll)
- 11.填充所有的元素(fill)
- 12.查找子序列的索引位置(indexOfSubList)
- 13.查找子序列的索引位置(lastIndexOfSubList)
- 14.拷贝元素一样的list(nCopies)
- 15.获取反转的比较器(reverseOrder)
- 16.返回集合的枚举(enumration)
- 17.返回枚举集合的list(list)
- 18.返回某个元素出现的频率(frequency)
- 19.判断是否有相同的元素(disjoint)
- 20.批量添加(addAll)
- 21.Map转成Set(newSetFromMap)
- 22.转换成后进先出队列(asLifoQueue)
- 四、总结
一、Collections接口是做什么的?
用官网文档的介绍:
The polymorphic algorithms described here are pieces of reusable functionality provided by the Java platform. All of them come from the Collections class, and all take the form of static methods whose first argument is the collection on which the operation is to be performed. The great majority of the algorithms provided by the Java platform operate on List instances, but a few of them operate on arbitrary Collection instances.(这里描述的多态算法是Java平台提供的可重用功能的一部分。它们都来自Collections类,都采用静态方法的形式,其第一个参数是要执行操作的集合。Java平台提供的绝大多数算法都对列表实例进行操作,但也有少数算法对任意集合实例进行操作。)
从介绍来看,这其实是一个工具类,实现了一些常用的算法,方便我们操作集合,如果没有这个类,也是可以的,就是自己写比较麻烦😂。但是呢,有了这个类,平时写代码我们可以直接调用(前提是了解里面怎么实现的,实现了什么功能),毕竟不是所有的轮子都要重复造。但是,不是有轮子了,我们就可以不去深究里面到底是啥,重要的不是造轮子,而是,我们必须有能够造轮子的能力。
二、Collections源码之大类方法
1.提供不可变集合
一般是通过一个方法直接获取不可变的集合,里面包含了不可变的Collection
,Set
,SortedSet
,NavigableSet
,List
,SortedMap
,NavigableMap
。
但是这些不可变的集合到底是什么呢?怎么使用呢?
我们来看一个最普遍的UnmodifiableCollection
类的源码:
从上面的代码可以看出其实所谓不可变的集合,就是用一个包装类,持有对实际的集合的引用,只能执行查询操作,其他操作都会抛出异常UnsupportedOperationException
。我们来看其他的,随便挑一个UnmodifiableMap
:
UnmodifiableMap
稍微复杂一点,就是里面分成了entry<key,value>
,key
,value
三个维度,UnmodifiableEntrySet<K,V>
继承了UnmodifiableEntry<K,V>
而UnmodifiableEntry<K,V>
实现了Map.Entry<K,V>
,事实上也是持有对集合的引用,把修改操作全部禁掉,一旦调用就会抛出异常。
上面的不可变集合提供方法直接获取,如下图:
2、提供同步的集合
看下面的图片,我们可以看到这个工具类其实提供了很多同步的集合类,但是都是基于Synchronize来实现的,开销相对比较大,有点点暴力了。源头就是SynchronizedCollection
,实现了Collection
接口。
我们来看源码,明显可以发现,里面处理集合的引用外,还有一个对象,mutex
,这就是锁的关键了,也就是同步的对象,可以看到下面几乎所有的方法都被加上了Synchronize
同步,这样子确实保持了线程安全,但是这样有点影响效率,如果不那么考虑效率的话,也可以使用这个。
值得注意的是,还有几个方法没有被加上锁,那就是所有获取流和迭代器的方法都没有,iterator()
,spliterator
,stream
,parallelStream
,都有一句话注释:Must be manually synched by user!,就是叫我们长点心,如果迭代或者流计算设计涉及到并发操作,可能会有问题,要使用者自己考虑。
至此,以上的同步内部类都有方法与之对应,也就是传进来集合就行了,会给我们返回一个同步类,可以直接获取,这也太方便了吧🙈🙈🙈没听过鲁迅说便宜的东西往往最贵么(鲁迅说没有说过),也就是效率自己权衡好就行啦👀~
3、类型检查
这个包装类还提供了很多关于类型检查的方法:
我们来看看是干啥的,挑一个checkedCollection()
看看:
首先这个方法是返回了一个CheckedCollection
,我猜这个类也是封装了Collection
,跟进去源码看看:
上面的源码可以看出,这些封装类没有什么特殊的地方,只是包装了一层,执行添加的时候,检查类型,如果不符合则抛出异常。
4.提供空集合或者迭代器
这个类还提供了一些方法可以获取到空集合,会生成指定类型的空 List
, Set
, Map
,而且是不可变的,如进行 add()
操作会报 java.lang.UnsupportedOperationException
。
我们来看EmptyIterator
的源码,里面有可以final的对象EMPTY_ITERATOR
,里面的hashNext()
方法直接返回false,remove()
,next()
方法直接不合法。
为啥要做这样的集合呢?看了一下其他的空集合,都是空的。🐶我想了很久🤔🤔
我们写测试用例的时候可能需要一个空的集合,有可能用到,还有就是它是空集合,但是它不为null,如果遍历null就会抛空指针,这样的话还是用一个空集合好了。还是蛮有道理的,而且当有这样的需求的时候,使用这样初始化大小为0的集合省空间啊,还是蛮方便的💯
然后就是Set
,List
,Map
都有对应的对象可以直接Collections.EMPTY_SET
即可。
5.提供singleton的集合或者迭代器
从下面的图片啊,我们可以看到这个类还提供了获取singleton的List,Set,Map,还有Iterator,Spliterator。
这些是啥,有啥用?🤔🤔
先看看singletonIterator
源码:
SingletonList的源码:
如果我们只有一个元素并且永远只有一个元素,那么我们可以考虑使用这些类,迭代器因为这些类都可以通过迭代器进行遍历,所以迭代器也限制了只能遍历一个元素。在创建的时候,就将唯一的元素传进去,不允许改变,也不允许删除。
三、从源码看其他常用方法
1. Sort(排序)
public static <T extends Comparable<? super T>> void sort(List<T> list)
,元素需要实现Comparable
接口,按照比较器进行排序。内部调用的是List的sort()方法,先转成数组,再对数组进行排序,排好序之后再set修改值。
一共有两种排序方法,第一种:
第二种:
两种看起来的区别在于有没有比较器Comparator
,内部都是调用了List
的sort()
方法,这个方法的源码如下,内部其实是调用了数组的排序方法。
使用方式,如果没有传入Comparator,那么所排序的类需要继承Comparable接口并重写compareTo方法,注意:String,Integer这些类已经实现了Comparable接口,所以不需要传入比较器也是可以默认排序的。
2. binarySearch(二分搜索)
二分搜索同样有两个:
一个需要List里面的对象继承Comparable
,实现里面的方法,另一个则是不需要,但是需要传入Comparator
,这其实都是一个意思,也就是你得告诉我怎么比较啊是不是😬😬😬
我们可以看到里面还是有两个分支,一个是使用索引二分,一个是迭代器二分,这是啥?这不得不说RandomAccess
,ArrayList
实现了一个叫做 RandomAccess
的接口,而 LinkedList
是没有的,这个接口的意思是一个标识,谁实现了,谁就(牛逼),开玩笑的,谁实现了,就说明这个接口是支持快速随机访问的,所谓快速随机访问就是底层不是链表,而是数组,数组是可以直接通过下标就能快速查找的嘛,牛逼牛逼~再底层的细节就后面再研究研究了。
3. reverse(反转)
老样子,看源码:
REVERSE_THRESHOLD
叫反转阈值,看上面的源码实说list的大小还有是否是RandomAccess会导致使用两种不同的算法进行反转,一种是基于数组索引,一种是基于Iterator的思路。
4. Shuffling(混排)
public static void shuffle(List<?> list)
,将list的元素随机打乱,这个方法也有两种参数,一种是只需要list,一种还需要随机值Random,但是底层一样,没有随机数的会方法内部生成再进行调用。
里面同样是分成根据索引混排和迭代器混排,老套路了,混排的关键就是根据随机数,将顺序打乱,说是打乱,其实就是两个两个随机互换啦。
5. 交换(swap)
public static void swap(List<?> list, int i, int j)
交换两个索引的元素
上面的混排接口,打乱顺序调用的就是交换接口,这个就是set和get,直接上源码。
6. 拷贝(copy)
public static <T> void copy(List<? super T> dest, List<? extends T> src)
,copy出一个内容一致的list
。
这里面也是分为两种,一种是迭代器copy,一种是索引,看来是惯用手法了啊,条件都是src instanceof RandomAccess
或者超过某个阈值。
7. 返回最小的元素(min)
所谓大小,根据指定的比较器决定,static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll)
同样分为有比较器和无比较器,有的话直接使用传进来的参数,没有的话,就会使用对象的。
8. 返回最大的元素(max)
static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
这个和找最小一个道理,就是比较的时候相反。
9. 旋转(Rotate)
将一个List旋转,假如有个序列列list是[1,2,3,4],调用方法Collections.rotate(list, 1)后,得到list就变成[4,1,2,3],public static void rotate(List<?> list, int distance)
也是分成两种,一种基于索引一种基于迭代器:
我们来看看基于索引的旋转,首先把非法情况处理,再通过取余数计算出旋转距离,然后两层循环进行旋转,说实话,这代码写得值得我学习。牛啊🐮🐮🐮
10. 替换所有元素(replaceAll)
public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)
这个没啥好说啊,就是替换元素。写代码的人是觉得代码太短了么,根据索引替换和根据迭代器的都写到一个方法,老长了这🙄🙄🙄
11.填充所有的元素(fill)
这个接口主要是将list中的元素全部替换成传入的obj,底层也是分成两种,一个是按照索引来遍历,一种是按照迭代器来遍历。
12.查找子序列的索引位置(indexOfSubList)
参数是两个list,一个是源List,一个是目标list,作用主要是查找到目标list在源list中的起始位置,需要是连续的,底层用了两层循环,同样分为两种情况来讨论。
13.查找子序列的索引位置(lastIndexOfSubList)
和上面不太一样的地方是,这个是从后面开始查找,其他的差不多一样,只要有一个匹配不满足,则需要改变匹配位置。
14.拷贝元素一样的list(nCopies)
这个方法功能是返回一个大小为n,元素全是o的list。
里面用到了CopiesList
,这个主要是继承AbstractList
,封装了只有一种元素的大小为n的list。
15.获取反转的比较器(reverseOrder)
在方法调用返回一个比较器,实现Comparable接口的对象的集合的自然顺序相反。
16.返回集合的枚举(enumration)
主要是用来获取一个枚举指定集合
17.返回枚举集合的list(list)
正好和上面的相反,这个是根据枚举类型集合返回一个list。
18.返回某个元素出现的频率(frequency)
主要是通过equals比较,遍历一遍,相等则次数加一。
19.判断是否有相同的元素(disjoint)
主要是比较两个集合中是否有相同的元素,当两个集合中没有相同的元素的时候 返回 true ,当有相同的元素的时候返回 false.
20.批量添加(addAll)
支持可变参数,可以实现添加多个元素。
21.Map转成Set(newSetFromMap)
将Map转成一个Set(SetFromMap),这也是一个封装的Set,实现没有什么特别的。
22.转换成后进先出队列(asLifoQueue)
传参数为双向队列,是先进先出的,转换之后,成为后进先出队列,也就是再调用add()方法,会插入在队列的最前面。
四、总结
Collections
这个类就是个工具类,主要的方法都是获取线程安全集合,集合类型检查,转换,截取,获取单个对象的集合或者空集合,排序,查找,旋转,混排等等,这些都是我们日常的操作,使用频率比较高,所以都给封装成工具类了。
里面很多地方都根据阈值或者类型来使用不同的索引遍历或者迭代器遍历,里面比较偏心List
,大多都是List
相关的呢...
看看源码也挺好的,有时候知道的越多,感觉自己不知道的越多。不知道自己不知道,才是最大的阻碍,继续加油💪💪💪
此文章仅代表自己(本菜鸟)学习积累记录,或者学习笔记,如有侵权,请联系作者删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有错误之处,还望指出,感激不尽~
技术之路不在一时,山高水长,纵使缓慢,驰而不息。
公众号:秦怀杂货店
__EOF__

本文链接:https://www.cnblogs.com/Damaer/p/13992152.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库