Java集合和映射的框架图
(备注:本图片摘自菜鸟教程(https://www.runoob.com/java/java-collections.html),个人理解Java中接口和接口之间使用的是继承(extends)关系,在UML图中,泛化关系就是我们常说的继承关系,所以接口之间感觉应该是“实线+空心三角形”连接,而实现类和接口之间是实现(implements)关系,所以用“虚线+空心三角形”,所以上图依鄙人之愚见存在一定的问题。)
1. Collection<E>
1.1 List
List接口常用的实现类有ArrayList、LinkedList、Vector等。List允许集合中有null和相同的元素。
(1) ArrayList其底层实现为一个可变长度的数组,既然是数组那么就满足数组的一切特性,其查找效率快,增删速度慢,线程不安全。初始容量为10个对象空间,满时以1.5倍扩容。
(2) LinkedList其底层实现为一个双向链表,尽管查找时使用了二分查找,但与ArrayList比较而言,其查找效率慢,增删速度快(增删只使用常量时间),线程不安全。
(3) Vector其底层实现为一个数组,这点与ArrayList相同,关键不同的地方在于其是线程安全的。
(备注:线程安全即拥有同步的机制,反之。通俗的讲,当有多个程序段同时执行某个方法,或对某个变量进行操作,它们只能依次轮流去执行,最终得到正确的结果。操作银行账户是个很好的例子。)
1.2 Set
Set接口常用的实现类有HashSet、TreeSet等,它和List的不同之处在于List是一个有序的容器,而Set是无序的。
(1) 针对HashSet,我们先分析其源代码,我们会发现它的底层是一个HashMap的实例。如下图,HashSet的构造方法默认先new一个HashMap出来。而HashMap的底层是哈希表。每次放入一个对象都会调用hashCode()方法和equals()方法,先按照一定的规则计算这个对象的哈希值,然后调用equals()方法判断集合中有无该哈希值,如果有,则添加,反之。如果放入的是“5、1、2、3、4”这样的对象,则输出是“1、2、3、4、5”感觉像是排序了,但如果想得到正确的自然排序建议使用TreeSet,HashSet集合只能放一个null,是线程不安全的。
(2) 针对TreeSet其底层实现为一个TreeMap的实例,源码如下。而TreeMap的底层是数据结构是红黑树。添加至TreeSet中的对象如果没有实现特定的比较器,则默认使用自然比较器。TreeSet集合中不能添加null(博主亲测),是线程不安全的。
(备注:我们这里所说的有序容器和无序容器和大多数初学者所想象的不一样,这也是最容易迷惑的地方。如果元素放进去是什么顺序,拿出来还是什么顺序,则称这个容器是有序的。而众所周知Set会对放入其中的元素改变一下顺序,所以称之为无序的。)
2. Map<K,V>
Map中所存放的对象为键(key)–值(value)对。
2.1. HashMap
HashMap底层数据结构为哈希表(至于哈希表是什么?博主在后续的博文中会详解),该映射的key只能放一个null,value也可以是null,是线程不安全的。
2.2. TreeMap
TreeMap底层数据结构是红黑树,会对键(Key)进行自然排序,所以key不允许有null,value可以是null,是线程不安全的。
2.3. Hashtable
Hashtable与HashMap相似,主要的不同点在于它是线程安全的,其次是key和value都不能添加null。
3. 总结
容器 | 底层数据结构 | 有序容器 | 自然排序 | 线程安全 | 允许重复元素或键(key) | null(空值) |
---|---|---|---|---|---|---|
ArrayList | 可变长数组 | 有序 | 不支持 | 不安全 | 允许 | 允许 |
LindkedList | 双向链表 | 有序 | 不支持 | 不安全 | 允许 | 允许 |
Vector | 可变长数组 | 有序 | 不支持 | 安全 | 允许 | 允许 |
HashSet | HashMap实例支撑 | 无序 | 不支持 | 不安全 | 不允许 | 只能放一个 |
TreeSet | TreeMap实例支撑 | 无序 | 支持 | 不安全 | 不允许 | 不允许 |
HashMap | 哈希表 | 无序 | 不支持 | 不安全 | 不允许 | key只能有一个 |
TreeMap | 红黑树 | 无序 | 支持 | 不安全 | 不允许 | key不允许,value允许 |
Hashtable | 哈希表 | 无序 | 不支持 | 安全 | 不允许 | key和value都不允许 |