一、Collection接口说明
Collection接口是一个非常重要的基础接口
add | 向集合中添加单个元素 |
---|---|
addAll | 向集合中批量添加元素 |
clear | 删除集合中的所有元素 |
contains | 判断集合是否包含某个元素 |
isEmpty | 判断集合是否为空 |
iterator | 返回一个集合迭代器 |
remover | 从集合中批量删除元素 |
removerAll | 保留指定入参集合中的元素,删除其它元素 |
retainAll | 保留指定入参集合中的元素,删除其它元素 |
size | 获取集合中元素个数 |
toArray | 将集合转换为数组 |
二、Map接口说明
Map接口是一个非常重要的接口
Clear | 删除所有元素 |
---|---|
containsKey | |
containsValue | 判断是否包含某个值 |
entrySet | 将Map键值对以Map.Entry的形式放入Set集合 |
get | 返回key值所对应的对象 |
isEntry | 判断是否为空 |
keySet | 返回所有键的Set集合 |
put | 向Map中添加单个集合 |
putAll | 向Map中批量添加元素 |
remove | 删除Key所对应的对象 |
size | 获取Map中键值对的个数 |
values | 返回所有值的集合 |
三、List
List用于定义以列表形式存储的集合,List接口为集合的每个对象分配了一个索引,用来标记该对象在List中的位置,并可以通过索引定位到指定位置的对象。
在开发过程在,List类的集合出现频率非常高,对于List类的集合,需要常用的有ArrayList、LinkedList、Vector、CopyOnWriteArrayyList,特别是ArrayList和CopyOnWriteArrayyList,更是频繁使用
1.ArrayList
通过名称基本上就能看出来,ArrayList基于数组实现的非线程安全的集合,在内部实现上,其维护了一可变长度的对象数组,集合内所有对象存储于这个数组中,并实现该数组长度的动态伸缩。ArrayList有三个特性:
(1)插入和删除元素性能较差
(2)索引元素性能非常高
(3)涉及数组长度动态伸缩,影响性能
注意:如果涉及到频繁的插入和删除元素,ArrayList不是最好的选择
2.LinkedList
LinkedList基于链表实现的非线程安全的集合,在内部实现上,其实现了静态类的Node,集合中的每个对象都右一个Node保存,每个Node都拥有到自己的前一个和后一个Node引用。它具备四个特性:
(1)在头/尾节点执行插入/删除操作的效率高
(2)查询元素慢
(3)不设计动态伸缩
(4)遍历LinkedList时应用iterator方式,不要用get(int)方式,否则效率会很低
3.Vector
基于数组实现的线程安全的集合。线程同步(方法被synchronized修饰),性能比ArrayList差。当并发量增多时,锁竞争的问题严重,会导致性能下降
4.CopyOnWriteArrayyList
与Vector一样,CopyOnWriteArrayyList也可以认为是ArrayList的线程安全版,不同之处在于CopyOnWriteArrayyList在写操作时会先复制出一个副本,在新副本上执行写操作,然后再修改引用。这种机制让CopyOnWriteArrayyList可以对读操作不加锁,这就使CopyOnWriteArrayyList的读效率远高于Vector。CopyOnWriteArrayyList的理念比较类似读写分离,适合读多写少的多线程。CopyOnWriteArrayyList只能保证数据的最终一致性,并不能保证数据的实时一致性,如果一个写操作正在进行中且并未完成,此时的读操作无法保证能读到这个写操作的结果。
CopyOnWriteArrayyList写时复制的集合,在执行写操作(如:add、set、remove等)时,都会将原数拷贝一份,然后在新数组上做修改操作,最后集合的引用指向新数组。CopyOnWriteArrayyList和Vevtor都是现成安全的,不同的是:前者使用ReentrantLock类,后者使用synchronized关键字。ReentrantLock提供了更多的锁投票机制,在锁竞争的情况下能表现更佳的性能。就是它让JVM能更快的调度线程。这就是为什么CopyOnWriteArrayyList的性能在大并发量的情况下优于Vector的原因。
对于CopyOnWriteArrayyList来说,非常适合高并发的读操作(读多写少)的场景下使用。若写的操作非常多,会频繁复制容器,从而影响性能
四、Map
Map存储的是键值对,它将key和value封装至一个叫做Entry的对象中。每一个Map根据其自身的特点,都有不同的Entry实现,以对应Map的内部类形式实现
Map比List类的集合更常用。对于Map类的集合有HashMap、HashTable、SortedMap、TreeMap、WeakHashMap和ConcurrentSkipListMap。
1.HashMap
HashMap的底层是基于数组+链表+红黑树的方式实现的。HashMap将Entry对象存储在一个数组中,并通过哈希表来实现对Entry的快速访问。HashMap的特性:
(1)当存储对象时,我们将键值对传递给put(key,value)方法时,它调用键对象key的hashCode()方法来计算hashcode,然后找到bucket位置,来存储值对象value
(2)hash表里可以存储元素的位置成为桶(bucket),如果通过key计算hash值发生冲突时,那么将采用链表的形式,来存储元素
(3)HashMap的扩容操作是一项很耗时的任务,所以如果能估算Map的容量,最好给它一个默认初始值,避免进行多次扩容;当数量达到了16*0.75=12就需要将当前16的容量进行扩容,而扩容这个过程涉及到rehash、复制数据等操作,所以非常消耗性能
(4)允许使用null键和null值
(5)非线程安全
2.HashTable
HashTable是HashMap的线程安全版,HashTable的实现方法里面都添加了synchronized关键字来确保线程同步。对于HashTable这种上古的东西,在开发中不建议使用,因为现在已经提供了ConcurrentHashMap来使用
3.ConcurrentHashMap
ConcurrentHashMap是HashMap的线程安全版,提供比Hashtable更高效的并发性能
HashTable在进行读写操作时会锁住整个Entry数组,这就导致数据越多性能越差。而ConcurrentHashMap使用分离锁的思路解决并发性能,其将Entry数组拆分至16个Segment中,以哈希算法决定Entry应该存储在哪个Segment。这样就可以实现在写操作时只对一个Segment加锁,大幅提升了并发写的性能。在进行读操作时,ConcurrentHashMap在绝大部分情况下都不需要加锁,其Entry中的value是volatile的,这保证了value被修改时的线程可见性,无需加锁便能实现线程安全的读操作。
ConcurrentHashMap采用了分段锁技术,其中Segment继承于ReentrantLock。不会像HashTable那样不管是put还是get操作都需要做同步处理,理论上ConcurrentHashMap支持CurrencyLevel(Segment数组数量)的线程并发。每当一个线程占用锁访问一个Segment时,不会影响到其他的Segment。
五、Set
Set用于村粗不含重复元素的集合,几乎所有的Set实现都是基于同类Map的。简单地说,Set是阉割版的Map。每一个Set内都有一个同类型的Map实例(CopyOnWriteArraySet除外,它内置的是CopyOnWriteArrayList实例),Set把元素作为key存储在自己的Map实例中,value则是一个空的object。Set的常用实现包括HashSet、TreeSet和ConcurrentSkipListSet,由于实现原理和对应的Map是完全一致的
六、Queue
Queue用于模拟“队列”这种数据结构(先进先出FIFO)。队列的头部保存着队列中存放时间最短的元素。新元素插入到队列的尾部。这种队列基本都只是在小数量的情况下使用,对于互联网应用来说,基本都是在使用分布式消息队列中间件。Deque接口继承了Queue接口,Deque接口代表一个“双端队列”,双端队列可以同时从两端来添加、删除元素,因此Deque的实现类既可以当成队列使用、也可以当成栈使用。常用的Queue实现类有ArrayDeque、ConcurrentLinkQueue、LinkedBlockingQueue、ArrayBlockQueue、SynchronousQueue、PriorityQueue和PriorityBlockingQueue。
(1)ArraryDeque是一个基于数组的双端队列,和ArrayList类似,它们的底层都采用一个动态的,可重分配的object[]数组来存储集合元素,当集合元素超出该数组的容量时,系统会在底层重新分配一个object[]数组来存储集合元素
(2)ConcurrentLinkQueue是基于链表实现的线程安全、无界非阻塞队列,队列中每个Node拥有到下一个Node的引用。它能够保证入队和出队操作的原子性和一致性,但在遍历的size()操作时只能保证数据的弱一致性。
(3)LinkedBlockingQueue与ConcurrentLinkQueue不同,LinkedBlockingQueue是一种无界的阻塞队列。阻塞队列就是在入队时如果队列已满,线程会被阻塞,直到队列有空间供入队再返回;同时在出队时,如果队列已空,线程也会被阻塞,直到队列中有元素供出队时再返回。LinkedBlockingQueue同样基于链表实现,其出队和入队操作都会使用ReentrantLock进行加锁。所以本身是线程安全的,但同样的,只能保证入队和出队操作的原子性和一致性,在遍历时只能保证数据的弱一致性。
(4)ArrayBlockQueue是一种有界的阻塞队列,基于数组的实现。其同步阻塞机制的实现LinkedBlockingQueue基本一致,区别仅在于前者的生产和消费使用同一个锁,后者的生产和消费使用分离的两个锁。
(5)SynchronousQueue算是JDK实现的队列中较奇葩的一个,它不能保存任何元素,size永远是0,peek()永远返回null。向其中插入元素的线程会阻塞,直到有另一个线程将这个元素取走,反之从其中取元素的线程也会阻塞,直到有另一个线程插入元素。这种实现机制适合传递性的场景。也就是说如果生产者线程需要及时确认到自己生产的任务已经被消费者线程取走后才能执行后续逻辑的场景下,适合使用SynchronousQueue
(6)PriorityQueue是基于最小堆数据结构,可以在构造时指定Comparator或者按照自然顺序排序。优先队列有最大最大优先队列和最小优先队列,分别由最大堆和最小堆实现。PriorityQueue是非阻塞队列,不是线程安全的
(7)PriorityBlockingQueue实现原理和PriorityQueue一样,但是PriorityBlockingQueue是阻塞队列,线程安全