Java 中的 Collection 接口是 Java Collection 体系的根接口。
![c255c4979aeb2e0e55bb0187b0035cfc.png](https://i-blog.csdnimg.cn/blog_migrate/6e6b0be001a07d0ad4dc75c4d2eb7853.jpeg)
因为 Java 有接口,所以它先定义了一个 Collection 接口,进行了一个抽象,关于 Collection 的解释,源码中的注释部分是讲得最好的:
![634953d991974c243d69248568bbeeb1.png](https://i-blog.csdnimg.cn/blog_migrate/6d6827bf0428ed1aa5b51aa136c23536.png)
是 collection 继承体系的根接口,代表一组对象,被称为“元素”。一些 集合(collection)允许重复元素,还有一些一盒不允许。一些是有序的,还有一些是无序的...
根据图片,我们可以看到,有几个比较重要的分支,分别具有不同的特性:
- List
- Set
- Map
它们也都是接口,描述了这一类型的 Collection 应该有的方法,下面我会介绍这几个类型比较重要的实现类。
ArrayList
有序、可重复的集合。
本质上就是一个数组,但是是一个可自动扩容的数组。我们看 ArrayList 的源码的话,就会发现,实际上是在每次存储的时候,ArrayList 内部都会判断当前数组的长度是否够大,如果不够,则会创建一个更大的数组,并把当前的值拷贝进去。
oldCapacity >> 1
这个我们在位运算中遇到过,左移一位的话,就是相当于除以当前的进制,所以这里可以看到,ArrayLIst 每次扩容的大小是当前大小的 1.5 倍。
![c99f9c91553a7d6b3709663da4970db6.png](https://i-blog.csdnimg.cn/blog_migrate/7b93381aaeaab4e31a1aefc2d78be7e2.jpeg)
List 的其他实现类
- TreeList 支持排序的 List。比如按对象的年龄属性从小到大,按字符串的长度等等。
- LinkedList 链表,适合插入和删除操作比较频繁的 List。
HashSet
无序,不可重复的集合。
不可重复的意思是不能包含相同元素,那如何判断元素是否相同呢?使用 equals
方法来判断。
我们先假设我们自己要实现一个不可重复的集合,我们会怎么做呢?
好像也不难。
![05acbdd7c3df2676cac8bbe3d18c3c80.png](https://i-blog.csdnimg.cn/blog_migrate/a5d56d40d7d51820c901566cf9b80a5a.png)
如果当前集合中包含的一个元素,等于即将添加的元素,那我们就什么都不做,反之就添加,似乎就实现了。但是这个实现存在非常严重的效率问题,即当你的数据越来越多,你每添加一个元素,就要遍历一遍现有的元素。
我们使用 Set 来做一个测试:
![3502ac9dd9bac248ef16cd14567f4cd6.png](https://i-blog.csdnimg.cn/blog_migrate/a2b1eef2574235a8532e7f8a1a393f6d.jpeg)
判断当前集合是否包含一个元素,ArrayList 用了 7.8毫秒,而 HashSet 只用了 0.04毫秒,可以想象这个效率的差距是多么巨大。
那 HashSet 是怎么做到的呢?这个就和它名字中的 Hash 有关了。
假如我们现在有 1000 个人,然后新来了一个人,我们要判断新来的人的名字是否在 1000 个人里面已经存在了。
ArrayList 的做法是:让 1000 个人按顺序排好,然后挨个找,如果找到了同名的,就结束。
HashSet 的做法是:让 1000 个人按照姓氏分组,姓张的一组,姓王的一组...这样把 1000 个人分成了很多组,当新来一个人的时候,直接在对应的组里去找,如果找不到,就找不到了。或者根本连对应的组都找不到。
看出来了吗?ArrayList 永远是在所有数据里找,而 HashSet 是在一小部分数据里找,所以 HashSet 当然要快很多。
人可以根据姓氏分组,那 String,Integer,以及其他的 Object,又应该如何分组呢?
hashCode
hashCode 和 equals 通常会被同时重写,关于 hashCode 有这几个约定:
- 同一个对象必须始终返回相同的 hashCode
- 两个对象的 equals 返回 true,必须返回相同的 hashCode
- 两个对象不同,也可能返回相同的 hashCode
要特别注意的是:hashCode 方法返回的是 int,我们都知道 int 的取值范围是有限的,所以极有可能两个完全不同的对象,但是返回相同的 hashCode,但是 hashCode 相同并不代表 equals 相同,也不代表两个对象相同,判断对象相同的方法只有 equals。
所以我们每个对象都有自己的“姓氏”了,也就方便了一些 Hash 结构。
Set 的其他实现类
- TreeSet 支持排序的 Set。比如按对象的年龄属性从小到大,按字符串的长度等等。
- LinkedHashSet 元素按照插入顺序排列的 Set。
HashMap
无序,Key 不可重复,Value 可重复的集合,存储的是 Key-Value 键值对。
首先名字里面的 Hash 我们已经理解了,就是为了实现 Key 不重复,借助 Hash,可以快速判断出是否存在重复的 Key。诶,等等,那 HashMap 的 Key 不就是一个 HashSet 吗?
我们打开 HashSet 的源码:
![3bb815ec23bf996537de29868977e357.png](https://i-blog.csdnimg.cn/blog_migrate/46e13cc638e7f54bb7318a8fb0aaf46b.jpeg)
原来 HashSet 中真的包含了一个 HashMap,因为 HashSet 能做的事情,HashMap 应该都能做,而 HashMap 又多出了 Key-Value 的映射功能,所以这里「组合」了 HashMap 的实现。
HashMap 的其他实现类
- LinkedHashMap 根据上面所提到的,Linked 就代表元素的顺序是根据插入顺序的。
- TreeMap 根据上面所提到的,Tree 就代表可以支持自定义排序的。
不常用的实现
- Queue 队列(先进先出)
- Deque 和 Quue 类似,但是是一个双端队列,支持获取第一个元素,以及最后一个元素
- PriorityQueue 优先级队列,和普通的队列区别是支持排序
总结
一个实现类,往往代表了一种数据结构,我们应该使用特定的数据结构来解决特定的问题,如果发现 JDK 自带的实现类满足不了我们特定的问题,可以考虑使用优秀的第三方库,如 Guava。