本文简单的介绍了我们最常中的集合的组成结构及特性,从以下几个方面展开:
- List
- Set
- Map
- Queue
注意在文章中使用实现箭头表示继承关系,虚线箭头表示实现关系:
—>表示继承关系
-->表示实现接口
一、数据结构的复杂度
数据结构的存储也有快慢之分,称之为时间复杂度O(N?),当然数据结构本身自带空间复杂度O(N),这里不做过多介绍,下面两篇文章,有详细介绍时间复杂度
二、Collection构成
classDiagram
Iterable <|-- Collection
Collection <|-- List
Collection <|.. AbstractCollection
Collection <|-- Set
Collection <|.. ArraySet
AbstractCollection <|-- AbstractList
AbstractCollection <|-- AbstractQueue
AbstractCollection <|-- AbstractSet
List <|.. CopyOnWriteArrayList
List <|.. AbstractList
AbstractList <|-- ArrayList
AbstractList <|-- Vector
AbstractList <|-- AbstractSequentialList
AbstractSequentialList <|-- LinkedList
Vector <|-- Stack
Set <|.. AbstractSet
Set <|-- SortSet
AbstractSet <|-- HashSet
AbstractSet <|-- TreeSet
AbstractSet <|-- EnumSet
HashSet <|-- LinkedHashSet
EnumSet <|-- JumboEnumSet
三、List
集合结构: 所有的元素都属于一个总体,除了同属于一个集合外没有其他关系。
classDiagram
Collection <|-- List
Collection <|-- Queue
Queue <|-- Deque
RandomAccess <|.. ArrayList
RandomAccess <|.. CopyOnWriteArrayList
RandomAccess <|.. Vector
Cloneable <|.. ArrayList
Cloneable <|.. CopyOnWriteArrayList
Cloneable <|.. Vector
Cloneable <|.. LinkedList
Serializable <|.. ArrayList
Serializable <|.. CopyOnWriteArrayList
Serializable <|.. Vector
Serializable <|.. LinkedList
List <|.. ArrayList
List <|.. CopyOnWriteArrayList
List <|.. Vector
List <|.. LinkedList
Deque <|.. LinkedList
Vector\Stack
Vector 内部结构是由数组构成,是线程安全的,addElement()和removeElement都是同步方法。
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable {
// 默认初始容量为10,可以自行设置
protected Object[] elementData;
protected int capacityIncrement;
// 扩容函数,当capacityIncement = 0,扩容为旧数组的两倍,省略部分代码
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
……
elementData = Arrays.copyOf(elementData, newCapacity);
}
public synchronized E firstElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(0);
}
public synchronized E lastElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(elementCount - 1);
}
// 同步方法,线程安全
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
}
Stack 栈继承自Vector,数据结构同Vector一样是数组,那么其也是线程安全的。初始容量和扩容方法同Vector一样。
栈中三个方法重要的方法,FILO(先进后出)的一套结构
public E push(E item) {
addElement(item);
return item;
}
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
ArrayList
- 底层以数组存储
- 创建时,默认赋值
elementData空数组 - 当加入值时,赋值默认初始容量长度为10
- 扩容算法为 size = size + size >> 1, (即 size = 1.5 * size)
- size 是指集合中元素的个数,capacity 是指在内存中预先分配一个元素所占内存空间的个数(内存地址是连续的),预备用来存放元素的。
- 在C语言中,数组名是当作指针来处理的。更确切的说,数组名就是指向数组首元素地址的指针,数组索引就是距数组首元素地址的偏移量。
CopyOnWriteArrayList
其内部数据结构也是数组,和 ReentrantReadWriteLock 读写锁的思想非常类似,也就是 读读共享、写写互斥、读写互斥、写读互斥。
- 和ArrayList不一样的是,COWAL没有默认值,也不存在扩容这么一说
- 每次增加数据,都是新开辟一个数组,将旧数组copy进入,然后加入新值
- 每次删除数据,就将旧数组中的其他元素使用Arrays.copyOf到新数组
- 这样读写完全分开,当同时读写的时候,读的是就数组,写的是新数组
因此会导致如下两个问题:
-
内存占用:如果CopyOnWriteArrayList经常要增删改里面的数据,经常要执行
add()、set()、remove()的话,那是比较耗费内存的。因为我们知道每次add()、set()、remove()这些增删改操作都要复制一个数组出来。 -
数据一致性:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。从上面的例子也可以看出来,比如线程A在迭代CopyOnWriteArrayList容器的数据。线程B在线程A迭代的间隙中将CopyOnWriteArrayList部分的数据修改了(已经调用
setArray()了)。但是线程A迭代出来的是原有的数据。
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 以前是 ReentrantLock,现在改成对象锁。
final transient Object lock = new Object();
public E get(int index) {
return get(getArray(), index);
}
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
}
}
LinkedList
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
// 头结点
transient Node<E> first;
// 尾结点
transient Node<E> last;
// 结点
private static class Node<E> {
E item;
// 下一个结点
Node<E> next;
// 上一个结点
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
// 获取第一个节点
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
// 获取第一个节点
public E element() {
return getFirst();
}
// 返回并且删除第一个节点
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
// 插入成为最后一个节点
public boolean offer(E e) {
return add(e);
}
// 插入成为第一个节点
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
// 插入成为最后一个节点
public boolean offerLast(E e) {
addLast(e);
return true;
}
// 返回第一个节点
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
// 返回最后一个节点
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
// 返回并删除第一节点
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
// 返回并删除最后一个节点
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
// 插入成为第一个节点
public void push(E e) {
addFirst(e);
}
// 返回并删除第一个节点
public E pop() {
return removeFirst();
}
// get(int index)取节点数据,当index大于一般时,从后面开始遍历,否则从前面开始遍历
Node<E> node(int index) {
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
// 普通添加链表方法
public boolean add(E e) {
linkLast(e);
return true;
}
}
从上面的代码可以看出,LinkedList可以实现多种数据结构:
- 普通的链表
- 不仅可以实现队列(首尾结点)(pollFirst、add),而且还实现了双端队列(Node结点中前后结点)(pollFirst, push,pollLast,add)
- 栈 (push,pop)FIFO
四、Set
classDiagram
SortSet <|-- NavigableSet
NavigableSet <|.. TreeSet
Cloneable <|.. HashSet
Cloneable <|.. LinkedHashSet
Cloneable <|.. TreeSet
Serializable <|.. HashSet
Serializable <|.. LinkedHashSet
Serializable <|.. TreeSet
Set <|.. HashSet
Set <|.. LinkedHashSet
Set <|.. ArraySet
简单介绍一下set底层的数据结构。
ArraySet
ArraySet在进行add和remove操作时,操作的是int[]类型的mHashes和Object[]类型的mArray,其中mHashes保存mArray每个元素的hash值,且mHashes和mArray相同下标的元素一一对应。也就是说其有两个数组,一个是用来保存obj的hashcode位置,另一数组对应位置则存放 obj。
其中使用 开放定址法来解决散列冲突,先向后搜索,搜索不到再向前搜索。ArraySet中还包含有大量的知识点,包括扩容、缓存(freeArrays、allocArrays),可以查看下边的文章。
ArraySet 添加和删除元素分析
HashSet
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
从上面简化的代码,可以看出HashSet是用HashMap或者LinkedHashMap存储值的。key是我们存的值,value是同一个object,用map.put(key,value)返回旧值来判断是否是重复数据。
五、Map
classDiagram
Map <|-- ConcurrentMap
Map <|.. AbstractMap
Map <|-- SortMap
Map <|.. Dictionary
Map <|.. ArrayMap
ConcurrentMap <|-- ConcurrentHashMap
ConcurrentMap <|-- LocalCache
AbstractMap <|-- ConcurrentHashMap
AbstractMap <|-- LocalCache
AbstractMap <|-- HashMap
AbstractMap <|-- WeakHashMap
AbstractMap <|-- IdentityHashMap
AbstractMap <|-- EnumMap
AbstractMap <|-- TreeMap
Dictionary <|-- HashTable
HashMap <|-- LinkedHashMap
SortMap <|-- NavigableMap
NavigableMap <|.. TreeMap
classDiagram
Map <|.. HashMap
Map <|.. WeakHashMap
Map <|.. LinkedHashMap
Map <|.. IdentityHashMap
Map <|.. HashTable
Cloneable <|.. HashMap
Cloneable <|.. HashTable
Cloneable <|.. TreeMap
Cloneable <|.. IdentityHashMap
Cloneable <|.. EnumMap
Serializable <|.. HashMap
Serializable <|.. HashTable
Serializable <|.. TreeMap
Serializable <|.. IdentityHashMap
Serializable <|.. EnumMap
HashMap
- tableSizeFor(initialCapacity),将传入初始值改为>=initialCapacity的2^n值
- 没有传initialCapacity值时,默认容量为16,负载因子为0.75
- 调整数组大小(当容器中的元素个数大于 capacity * loadfactor 时,容器会进行扩容resize 为 2n)
- 调用 hash(K) 方法计算 K 的 hash 值,
- 当碰撞导致链表大于 TREEIFY_THRESHOLD = 8 时,就把链表转换成红黑树,当链表数量减小到6时,将红黑树转换成链表。
- HashMap最多只允许一条记录的键为null,允许多条记录的值为null。
WeakHashMap
WeakHashMap,是用来存放可以被回收的数据,Entry节点用于存放k和v,继承自WeakReference,可以在GC时被回收。
其内部有ReferenceQueue用来判断被回收的数据。
默认值和扩容等和HashMap相似,只是没有红黑树,只有链表。
LinkedHashMap
- 继承自HashMap,存取数据和HashMap是一样的。
- accessOrder,表示按照访问顺序还是插入顺序,默认为false,按照插入顺序访问。
HashMap
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
LinkedHashMap
static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
LinkedHashMapEntry<K,V> before, after;
……
}
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMapEntry<K,V> p =
new LinkedHashMapEntry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
在put时,会在完成插入后调用 afterNodeAccess(),然后连接前后结点,这样在LinkedHashMap中不仅采用原HashMap存储数据的方式,还讲整个数据组成了一个链表,这样既能平衡查找,又能使用链表功能。
LruCache
public class LruCache<K, V> {
@UnsupportedAppUsage
private final LinkedHashMap<K, V> map;
private int size;
private int maxSize;
// 必须给定个数
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
// 按照访问顺序排版数据
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
// 调整容量,去除超出容量的数据。
public void resize(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
synchronized (this) {
this.maxSize = maxSize;
}
trimToSize(maxSize);
}
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
// 获取安全,代码块
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
// 需继承自己重写
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
// 数据超过了容量
if (size <= maxSize) {
break;
}
// 头节点,需要被删除
Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
//
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
}
IdentityHashMap
HashMap
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
IdentityHashMap
private static int hash(Object x, int length) {
int h = System.identityHashCode(x);
// Multiply by -127, and left-shift to use least bit as part of hash
return ((h << 1) - (h << 8)) & (length - 1);
}
两个计算hash值不同。
HashTable
Hashtable 也是用数组数组来存节点的,只不过查找数组index = (hash & 0x7FFFFFFF) % tab.length
Hashtable不同于HashMap,前者既不允许key为null,也不允许value为null;
HashMap中用于定位桶位的Key的hash的计算过程要比Hashtable复杂一点,没有Hashtable如此简单、直接;
在HashMap的插入K/V对的过程中,总是先插入后检查是否需要扩容;而Hashtable则是先检查是否需要扩容后插入;
Hashtable不同于HashMap,前者的put操作是线程安全的。
(1). HashMap和Hashtable的实现模板不同:虽然二者都实现了Map接口,但HashTable继承于Dictionary类,而HashMap是继承于AbstractMap。Dictionary是是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的实现,它以最大限度地减少实现此接口所需的工作。
(2). HashMap和Hashtable对键值的限制不同:HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。
(3). HashMap和Hashtable的线程安全性不同:Hashtable的方法是同步的,实现线程安全的Map;而HashMap的方法不是同步的,是Map的非线程安全实现。
(4). HashMap和Hashtable的地位不同:在并发环境下,Hashtable虽然是线程安全的,但是我们一般不推荐使用它,因为有比它更高效、更好的选择ConcurrentHashMap;而单线程环境下,HashMap拥有比Hashtable更高的效率(Hashtable的操作都是同步的,导致效率低下),所以更没必要选择它了。
ArrayMap
ArrayMap在进行add和remove操作时,操作的是int[]类型的mHashes和Object[]类型的mArray,其中mHashes保存mArray每个key元素的hash值。也就是说其有两个数组,一个是用来保存key的hashcode位置,另一数组相对应位置则存放 节点(key,value)。
mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;
mArray数组的长度是mHashes长度的两倍,扩容规则如下:
- 当map个数满足条件 osize<4时,则扩容后的大小为4;
- 当map个数满足条件 4<= osize < 8时,则扩容后的大小为8;
- 当map个数满足条件 osize>=8时,则扩容后的大小为原来的1.5倍;
其中使用 开放定址法来解决散列冲突,先向后搜索,搜索不到再向前搜索。ArraySet中还包含有大量的知识点,包括扩容、缓存(freeArrays、allocArrays),可以查看下边的文章。
TreeMap
是一棵红黑树:
- 每个节点都只能是红色或者黑色
- 根节点是黑色
- 每个叶节点(NIL节点,空节点)是黑色的。
- 如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
根据用户提供的comparator进行比较,也需要key值实现了Comparable,进行插值比较。
public V put(K key, V value) {
……
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
TreeMapEntry<K,V> e = new TreeMapEntry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
……
}
先插值,然后进行在进行旋转和着色。插入的节点默认颜色是红色。
六、Queue
classDiagram
Collection <|.. AbstractCollection
Collection <|-- Queue
AbstractCollection <|-- AbstractQueue
AbstractCollection <|-- ConcurrentLinkedDeque
AbstractCollection <|-- ArrayDeque
BlockingQueue <|-- SynchronousQueue
BlockingQueue <|-- ArrayBlockingQueue
BlockingQueue <|-- LinkedBlockingQueue
BlockingQueue <|-- PriorityBlockingQueue
AbstractQueue <-- SynchronousQueue
AbstractQueue <-- ArrayBlockingQueue
AbstractQueue <-- LinkedBlockingQueue
AbstractQueue <-- PriorityBlockingQueue
AbstractQueue <-- PriorityQueue
Queue <|.. AbstractQueue
Queue <|-- Deque
Deque <|.. LinkedList
Deque <|.. ArrayDeque
Deque <|.. ConcurrentLinkedDeque
图片出自【细谈Java并发】谈谈LinkedBlockingQueue
ArrayDeque
数组形式实现的双端队列,有以下几个特点:
- 初始容量为16
- doubleCapacity,顾名思义每次扩容是原先的两倍
- allocateElements,初始化容量,会将initialCapacity转换成 > numElements的2^n的数,这一点和hashmap的相似又有区别。
- 双端队列也可以当做栈使用,也有push和pop等方法。
ConcurrentLinkedDeque
ConcurrentLinkedDeque使用了自旋+CAS的非阻塞算法来保证线程并发访问时的数据一致性。由于队列本身是一种双链表结构,所以虽然算法看起来很简单,但其实需要考虑各种并发的情况,实现复杂度较高,并且ConcurrentLinkedDeque不具备实时的数据一致性,实际运用中,如果需要一种线程安全的栈结构,可以使用ConcurrentLinkedDeque。
PriorityQueue
- 底层结构使用数组来保存数据
- 初始默认容量是11
- 先扩容,然后再入值 oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1))
- 权限是通过比较传入的Comparator或者是值本身实现了Comparable接口
- 是通过小顶堆实现的
父子节点的编号之间有如下关系:
leftNo = parentNo*2+1
rightNo = parentNo*2+2
parentNo = (nodeNo-1)/2
构建小顶堆,插入数据上浮
@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
删除数据,下沉
// k = 0; x 数组中倒数第一个数。
@SuppressWarnings("unchecked")
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
//首先找到左右孩子中较小的那个,记录到c里,并用child记录其下标
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
// 右下标小于size,并且左子节点大于右子节点
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
if (key.compareTo((E) c) <= 0)
break;
queue[k] = c; //然后用c取代原来的值
k = child;
}
queue[k] = key;
}
二叉堆就是一颗二叉树,是一颗完全二叉树,最直观表现一个二叉树左边最多比右边深 1 层,二叉堆我们常常讨论的就是大顶堆和小顶堆,其中大顶堆根结点最大,左右节点依次递归,小顶堆类似
二叉堆算是一种比较重要的数据结构,实际中我们的堆排序就涉及到二叉堆,它也是优先级队列的基础
完全二叉树 遵循一定的大小规律 自行调整的特性
阻塞队列
在线程池中有应用。
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 当队列中没有消息则会等待nanos时长,否则立即取消息。
while (count == 0) {
if (nanos <= 0L)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
int c = -1;
Node<E> node = new Node<E>(e);
// 插入锁
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
// 释放锁
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
总结
这是我在准备面试过程中总结的集合资料,简单的介绍了各种数据结构的基本组成结构。所有的数据结构都是数组和链表的,数据结构只是在这两个结构上扩展的。
其中有一次面试官问我,ArrayList实现的接口,那么要知道面试官考察的是你对ArrayList的掌握程度,ArrayList实现了Cloneable、Serializable、RandomAccess和List,那么从这几个点来看ArrayList可以被序列化、clone和产生随机数等几个特性。
所有的集合都实现了Serializable,除了BlockingQueue其他集合都实现了Cloneable。在学习数据结构中我们不仅要学习其数据结构组成原理,还要学习其继承类和实现接口,这样才能更好地了解数据结构的特性。
这个总结还会持续不断更新。