1. Java集合分类
Java集合可分为两大类:Collection和Map
集合的数据结构主要有两种:数组和链表
数组常用于查询多、修改删除少的场景
链表常用于修改删除多、查询少的场景
2. 集合特点整理
2.1 List
2.1.1 ArrayList
特点:数据不唯一、有序;
优点:随机索引查询速度快
缺点:随机插入删除数据效率低
ArrayList有两个重要的属性:Object数组(elementData)、size;ArrayList实例化的时候如果没有设置初始大小的时候,Object数组会赋值一个空数组,只有执行put的时候才会初始化一个长度为10的数组;当size大于elementData的长度,List会进行扩容至原来的1.5倍。
ArrayList扩容会进行数组数据转移,会耗费一定的时间,所以当已知数组的长度可以在初始化的时候给定长度。
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
2.1.2 LinkedList
优点:随机插入删除效率比数组高
缺点:随机索引查询效率比数组低
- LinkedList是双向链表实现的List、非线程安全、其元素允许null及重复元素
- LinkedList基于链表实现,所以不存在容量不足的情况,也就没有扩容的方法
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
/**
* Constructs an empty list.
*/
public LinkedList() {
}
2.2 Set
2.2.1 HashSet
特点: 元素具有唯一性、无序性
HashSet是由数组+链表构成(HashMap);数据唯一性判定先通过hashCode()判断,再通过equals校验;使用hashCode的原因是为了提高比较检索速度。
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
2.2.2 LinkedHashSet
特点:元素具有唯一性、有序性(按插入顺序)
LinkedHashSet是HashSet的子类,通过数组+链表的方式保证元素的唯一性,再次基础上加上一个双向链表来确保元素按照插入顺序进行排序(LinkedHashMap);因为有一个双端队列,所以在插入元素方面略低于HashSet,但是在迭代访问的时候不受影响。
# LinkedHashSet类
/**
* Constructs a new, empty linked hash set with the default initial
* capacity (16) and load factor (0.75).
*/
public LinkedHashSet() {
super(16, .75f, true);
}
#HashSet类
/**
* Constructs a new, empty linked hash set. (This package private
* constructor is only used by LinkedHashSet.) The backing
* HashMap instance is a LinkedHashMap with the specified initial
* capacity and the specified load factor.
*
* @param initialCapacity the initial capacity of the hash map
* @param loadFactor the load factor of the hash map
* @param dummy ignored (distinguishes this
* constructor from other int, float constructor.)
* @throws IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
2.2.3 TreeSet
特点: TreeSet实现SortedSet接口,可以保证元素的排序状态。
TreeSet底层使用红黑树结构存储数据。
TreeSet有两种排序方法: 自然排序和定制排序。默认情况下,TreeSet采用自然排序。
/**
* Constructs a new, empty tree set, sorted according to the
* natural ordering of its elements. All elements inserted into
* the set must implement the {@link Comparable} interface.
* Furthermore, all such elements must be <i>mutually
* comparable</i>: {@code e1.compareTo(e2)} must not throw a
* {@code ClassCastException} for any elements {@code e1} and
* {@code e2} in the set. If the user attempts to add an element
* to the set that violates this constraint (for example, the user
* attempts to add a string element to a set whose elements are
* integers), the {@code add} call will throw a
* {@code ClassCastException}.
*/
public TreeSet() {
this(new TreeMap<E,Object>());
}
/**
* Constructs a new, empty tree set, sorted according to the specified
* comparator. All elements inserted into the set must be <i>mutually
* comparable</i> by the specified comparator: {@code comparator.compare(e1,
* e2)} must not throw a {@code ClassCastException} for any elements
* {@code e1} and {@code e2} in the set. If the user attempts to add
* an element to the set that violates this constraint, the
* {@code add} call will throw a {@code ClassCastException}.
*
* @param comparator the comparator that will be used to order this set.
* If {@code null}, the {@linkplain Comparable natural
* ordering} of the elements will be used.
*/
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
2.3 Queue
Queue分为三大类:继承Queue接口的BlockingQueue(阻塞队列)、Deque(双端队列),抽象实现Queue的AbstractQueue(非阻塞队列)
特点:先进先出(FIFO)
图中我们能看到最上层是Collection 接口,Queue满足了集合类的所有类的方法,都是非阻塞式的
:
add(E e):增加元素,容量受限队列没有空间使用add会抛出IllegalStateException异常
offer(E e):增加元素,容量受限队列没有空间使用offer不会抛出异常
remove(Object o):删除元素,如果队列为空,会引起异常
poll():检索并删除队列头,如果队列为空,则返回null
element():检索但不删除队列头,如果队列为空,会引起异常
peek():检索但不删除队列头,如果队列为空,则返回null
clear():清除集合中所有元素;
size():集合元素的大小;
isEmpty():集合是否没有元素;
contains(Object o):集合是否包含元素o。
BlockingQueue接口继承至Queue接口,拓展了一些方法:
put(E e):插入元素 //阻塞
take():移除元素//阻塞
2.3.1 Deque
Deque在Queue的基础上,增加了下面几个方法:
addFirst(E e):在前端插入元素,异常处理和add一样;
addLast(E e):在后端插入元素,和add一样的效果;
offerFirst(E e):在前端插入元素,异常处理和offer一样;
offerLast(E e):在后端插入元素,和offer一样的效果;
removeFirst():移除前端的一个元素,异常处理和remove一样;
removeLast():移除后端的一个元素,和remove一样的效果;
pollFirst():移除前端的一个元素,和poll一样的效果;
pollLast():移除后端的一个元素,异常处理和poll一样;
getFirst():获取前端的一个元素,和element一样的效果;
getLast():获取后端的一个元素,异常处理和element一样;
peekFirst():获取前端的一个元素,和peek一样的效果;
peekLast():获取后端的一个元素,异常处理和peek一样;
removeFirstOccurrence(Object o):从前端开始移除第一个是o的元素;
removeLastOccurrence(Object o):从后端开始移除第一个是o的元素;
push(E e):和addFirst一样的效果;
pop():和removeFirst一样的效果。
2.3.2 阻塞队列
2.3.2.1 BlockingQueue
阻塞队列(BlockingQueue)支持put和take方法:put是当队列满时,存储元素线程会等待队列可用;take是当队列为空时,获取元素的线程会等待队列非空。
阻塞队列提供了四种方法:
方法 | 异常抛出方法 | 返回特殊值 | 阻塞 | 超时退出 |
---|---|---|---|---|
插入方法 | add(e),队列满,抛出IIIegaISlabEepeplian异常 | offer(e),队列满,返回false | put(e),队列满一直阻塞 | offer(e,time,unit) |
移除方法 | remove(),队列空,抛出IIIegaISlabEepeplian异常 | poll(),队列空,返回false | take(),队列空一直阻塞 | poll(time,unit) |
检查方法 | element(),队列空,抛出IIIegaISlabEepeplian异常 | peek(),队列空,返回false | 空 | 空 |
- 阻塞
当阻塞队列满时,如果生产者线程往队列中put元素,队列会一直阻塞生产者线程,直到队列有空位,让元素插入到队列中或者响应中断停止
当阻塞队列空时,如果消费者线程从队列中take元素,队列会阻塞线程,直到拿到队列中的元素 - 超时退出
当阻塞队列满时,offer(e,time,unit)插入元素,队列会阻塞生产者线程一段时间,超过设置的时间,生产线程就会退出
当阻塞队列空时,poll(time,unit)移除元素,队列会阻塞消费者线程一段时间,超过设置的时间,消费者线程就会退出
阻塞队列成员:
队列 | 有界性 | 锁 | 数据结构 |
---|---|---|---|
ArrayBlockingQueue | bounded(有界) | 加锁 | 数组 |
LinkedBlockingQueue | optionally-bounded(默认Integer.MAX_VALUE,最好重新设置值) | 加锁 | 链表 |
PriorityBlockingQueue | unbounded | 加锁 | 堆 |
DelayQueue | unbounded | 加锁 | 堆 |
SynchronousQueue | unbounded | 加锁 | 无 |
LinkedTransferQueue | unbounded | 加锁 | 堆 |
LinkedBlockingDeque | unbounded | 无锁 | 堆 |
2.3.2.2 BlockingDeque
BlockingDeque(阻塞双端队列)在Deque的基础上实现了双端阻塞等待的功能。实现类:LinkedBlockingDeque
2.3.2.3 TransferQueue
TransferQueue比BlockingQueue更强大的一点是,生产者会一直阻塞到添加到队列的元素被某一个消费者消费 。阻塞发生在元素从一个线程transfer到另外一个线程的过程中,有效地实现了元素在线程间的传递。
接口提供的标准方法:
tryTransfer(E e):若当前存在一个正在等待获取的消费者线程(使用take()或者poll()函数),使用该方法会即刻转移/传输对象元素e并立即返回true;若不存在,则返回false,并且不进入队列。这是一个不阻塞的操作。
transfer(E e):若当前存在一个正在等待获取的消费者线程,即立刻移交之;否则,会插入当前元素e到队列尾部,并且等待进入阻塞状态,到有消费者线程取走该元素。
tryTransfer(E e, long timeout, TimeUnit unit):若当前存在一个正在等待获取的消费者线程,会立即传输给它;否则将插入元素e到队列尾部,并且等待被消费者线程获取消费掉;若在指定的时间内元素e无法被消费者线程获取,则返回false,同时该元素被移除。
hasWaitingConsumer():判断是否存在消费者线程。
getWaitingConsumerCount():获取所有等待获取元素的消费线程数量。
2.3.3 非阻塞队列
非阻塞队列:PriorityQueue
和 ConcurrentLinkedQueue
-
PriorityQueue 类实质上维护了一个有序列表。加入到 Queue 中的元素根据它们的天然排序(通过其 java.util.Comparable 实现)或者根据传递给构造函数的 java.util.Comparator 实现来定位。
-
ConcurrentLinkedQueue 是基于链接节点的、线程安全的队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们,所以只要不需要知道队列的大 小,ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列。入队和出队操作均利用CAS(compare and set)更新,这样允许多个线程并发执行,并且不会因为加锁而阻塞线程,使得并发性能更好。
2.4 Map
Map是一个接口,由key-value键值对构成,Map中不能包含重复的key,一个key最多对应一个值。
2.4.1 HashMap
HashMap底层是数组+链表;
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
HashMap初始化如果带入的不是map数据,那么初始化只会设定初始容量及负载因子数据,数组之类的并不会初始化;HashMap具体内容的初始化发生在put插入数据时。初始容量设定会根据tableSizeFor方法找到第一个大于等于initialCapacity的2的平方数,用于作为初始化table。之所以要table为2的倍数,是为了indexFor方法取下标的时候,h & (length-1)能取代mod更快实现下标取值。
threshold 是HashMap进行扩容的阈值,当HashMap存储的元素个数超过这个阈值的时候,就会进行扩容。阈值threshold=当前容量capacity*负载因子loadFactor。
当HashMap扩容时,容量会变成原来的两倍,原来存储的元素会重新计算哈希值,位置也会发生变化,这比较耗费性能,所以如果提前知道Map的size,可以在一创建的时候设定合适的大小减少resize开销。
2.4.2 LinkedHashMap
LinkedHashMap = HashMap + 双向链表
LinkedHashMap的存储顺序是按照调用put方法插入的顺序进行排序的。
LinkedHashMap有插入顺序和访问顺序两种。
2.4.3 ConCurrentHashMap
主要在多线程中用,解决HashMap可能会导致死锁问题,ConCurrentHashMap既可以保持同步也可以提高并发效率
ConCurrentHashMap关于扩容需要知道两个重要的参数:
- MIN_TREEIFY_CAPACITY : 最小树形化容量阈值,默认为64
- TREEIFY_THRESHOLD : 树化阈值,指定桶位链表长度达到8的话,就可能发生树化操作
当元素个数大于最小树形化容量阈值并且链表长度大于8,才会调用treeifyBin()把链表转换成红黑树,否则会进行扩容操作。