Java中各种集合的异同与使用
Collection< E >是所有集合类的祖先 我们从这个类往下延伸
1.List
不同点:
名称 | 数据结构 | 安全性 | 插入和删除(时间复杂度) | getIndex | 内存空间占用 |
---|---|---|---|---|---|
ArrayList | 数组 | 非同步,不安全 | 首尾O(1),index O(n-i)) | 高效 | 结尾会预留一定的空间 |
LinkedList | 双向链表 | 非同步,不安全 | O(n) | 较慢 | 每一个元素都耗时 |
Vector(淘汰) | 数 组 | 线程安全(synchronized) | ----- | ----- | ----- |
- ArrayList增删没有想象中慢,ArrayList的增删底层调用的copyOf()被优化过,加上现代CPU对内存可以块操作,普通大小的ArrayList增删比LinkedList更快。
2.Set
- HashSet
- LinkedHashSet
- TreeSet
不同点:
名称 | 顺序 | 基础 |
---|---|---|
HashSet | 无序 | 基于HashMap |
LinkedHashSet | 自动排序 | 基于TreeMap |
TreeSet | 维护“插入顺序” | 基于HashSet,是HashSet的扩展 |
- Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false
3.Map
- Map
- HashMap
- HashTable
- LinkedHashMap
- TreeMap
不同点:
名称 | 实现 | 继承 | 是否允许null值 | 扩容方式 | 存储方式 |
---|---|---|---|---|---|
HashMap | 实现Map接口 | 继承 AbstractMap 类 | 允许key和value为null值 | size*2,且size必定为2^n | 1.如果冲突数量于8,则是以链表方式解决冲突。2.如果当冲突大于等于8时,就会将冲突的Entry转换为红黑树进行存储。3如果当数量小于6时,则又转化为链表存储。 |
HashTable | 实现Map接口 | 继承 Dictionary类 | 键值对都不能为空 | size*2+1 | 链表方式存储 |
LinkedHashMap | 实现Map接口 | 继承 HashMap类 | 允许key和value为null值 | 基于拉链式散列结构即由数组和链表或红黑树组成 | |
TreeMap | 实现NavigableMap接口 | 继承 AbstractMap类 | 不允许Key为null | 红黑树(自平衡的排序二叉树) |
已上是常见集合的梳理,但是在多线程环境下 , Vector被淘汰 , 使用synchronizedList又过于笨重
4. JUC下常用的几个线程安全容器
1. CopyOnWriteArrayList
2. CopyOnWriteArraySet
同CopyOnWriteArrayList
3. ConcurrentLinkedQueue
ConcurrentLinkedQueue
4. ConcurrentSkipListMap
ConcurrentSkipListMap
5. ConcurrentHashMap
因为HashTable也是线程安全的,总结一下不同点:
- 底层数据结构:
- JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。
- Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
- 实现线程安全的方式(重要):
- ① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
- ② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方 法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
如何选用集合?
主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,
需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.
当我们只需要存放元素值时,就选择实现Collection接口的集合,
需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,
不需要就选择实现List接口的比如ArrayList或LinkedList,
然后再根据实现这些接口的集合的特点来选用。