/**
* Increases the capacity of this ArrayList instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新的数组容量为当前容量的1.5倍
if (newCapacity - minCapacity < 0) // 如果扩充后的容量仍不满足存储所有新的元素,则扩充至添加元素后,所有元素的总数
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
单向队列是从队尾添加元素,从对头删除元素,其在java中定义的接口为java.util.Queue。\
PriorityQueue
PriorityQueue又叫做优先级队列,保存队列元素的顺序不是按照及加入队列的顺序,而是按照队列元素的大小进行重新排序。因此当调用peek()或pool()方法取出队列中头部的元素时,并不是取出最先进入队列的元素,而是取出队列的最小元素。
底层实现:PriorityQueue使用了一个高效的数据结构:堆。底层是使用数组保存数据。还会进行排序,优先将元素的最小值存到队头。
双端队列是从队列的两端都可以插入或删除元素,其在java中定义的接口为java.util.Deque。LinkedList也是Deque的一个实现类
LinkedBlockingQueue:的容量是没有上限的(说的不准确,在不指定时容量为Integer.MAX_VALUE,不要然的话在put时怎么会受阻呢),但是也可以选择指定其最大容量,它是基于链表的队列,此队列按 FIFO(先进先出)排序元素。
ArrayBlockingQueue:在构造时需要指定容量, 并可以选择是否需要公平性,如果公平参数被设置true,等待时间最长的线程会优先得到处理(其实就是通过将ReentrantLock设置为true来 达到这种公平性的:即等待时间最长的线程会先操作)。通常,公平性会使你在性能上付出代价,只有在的确非常需要的时候再使用它。它是基于数组的阻塞循环队 列,此队列按 FIFO(先进先出)原则对元素进行排序。
PriorityBlockingQueue:是一个带优先级的 队列,而不是先进先出队列。元素按优先级顺序被移除,该队列也没有上限(看了一下源码,PriorityBlockingQueue是对 PriorityQueue的再次包装,是基于堆数据结构的,而PriorityQueue是没有容量限制的,与ArrayList一样,所以在优先阻塞 队列上put时是不会受阻的。虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会导致 OutOfMemoryError),但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,往入该队列中的元 素要具有比较能力。
DelayQueue:(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。
HashMap是目前最常用的集合类,其内部结构和实现也比较复杂,接下来我通过阅读源代码(JDK1.8),按照自己的理解,尽可能详细的讲解其内部工作原理。
首先,有几个重要的知识点需要掌握:
首先看一下类的继承结构,其结构相对比较简单:
其次,我们需要了解几个重要的成员变量
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
/**
* The load factor for the hash table.
*
* @serial
*/
final float loadFactor;
/**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
// (The javadoc description is true upon serialization.
// Additionally, if the table array has not been allocated, this
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)
int threshold;
这样,我们就梳理出了HashMap底层的数据结构,首先是一个table数组,数组中的每个元素是一个链表的头结点或红黑树的根节点。当一个元素链接的节点数小于阈值时(默认为8),该节点为一个链表,当节点数大于阈值时,会执行treefy操作,将链表转化为红黑树。即当节点数目较多时,可以利用到红黑树的查询和修改效率较高的优势,以提高HashMap的性能。因此,HashMap底层是 数组+链表+红黑树 的结构。
接下来,在了解几个重要的操作方法
1. put方法
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with key, or
* null if there was no mapping for key.
* (A null return can also indicate that the map
* previously associated null with key.)
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) {
// existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
简单描述一下思路:这里首先计算key的哈希值,然后判断若table数组为空或空间不足,则进行resize()操作,进行动态扩容。如果key所对应的节点已经存在,则用value覆盖原有的值,否则将新的值添加到哈希计算的节点中,如果是红黑树,则插入到红黑树的对应位置。如果是链表,则插入到链表的尾部,判断该元素的节点数目是否超过了treefy的阈值(默认为8),如果超过阈值,则进行treefy操作,将链表转换为红黑树。最后再次判断++size > threshold,如果成立,则进行resize()动态扩容,为下一次put操作做好准备。
2. get方法
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise
* it returns {@code null}. (There can be at most one such mapping.)
*
*
A return value of {@code null} does not necessarily
* indicate that the map contains no mapping for the key; it's also
* possible that the map explicitly maps the key to {@code null}.
* The {@link #containsKey containsKey} operation may be used to
* distinguish these two cases.
*
* @see #put(Object, Object)
*/
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
* Implements Map.get and related methods.
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
get操作的逻辑相对简单一些:首先判断table表是否为空,如果为空则返回null。然后根据key的哈希值,确定元素的位置,检查头结点是否与key相同,如果相同则返回该节点。否则判断该元素是链表的头结点还是红黑树的根节点,分别进行链表的遍历或者二叉树的查找操作,返回对应的节点。
注意一个细节:这里拿到一个元素后,先判断头结点的key是否为待查找的节点,然后再判断该元素是链表还是红黑树。如果头结点能够满足要求,则直接返回,不进行后续的操作。
3. 动态扩容(resize)
resize操作主要完成两件事情:
(1) 建立一个长度为原有数组2倍的新的数组
(2) 将原有数组中的数据拷贝到新数组中,并同时根据新数组的长度进行rehash的操作
具体的实现细节大家可以参考源代码 resize() 方法。
一个小Tips:
我们注意到HashMap的源码中使用了大量的位运算,包括取模等操作都是通过位运算实现的,这有效提高了HashMap的执行效率,这是我们在写代码的时候也可以借鉴的地方。
/**
* Node in the Tree. Doubles as a means to pass key-value pairs back to
* user (see Map.Entry).
*/
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
}
注意一下TreeMap类的继承结构
TreeMap主要实现了NavigableMap接口,并继承自AbstractMap类。更多源码层面的东西,大家有兴趣可以自己阅读一下TreeMap的源代码。
HashTable也是用于key-value形式的数据存储,因此这里我们主要来对比一下HashTable和HashMap之间的区别:
它们的父类不同,HashMap继承自AbstractMap抽象类,而HashTable继承自Dictionary抽象类。
它们底层封装的数据结构不同
HashMap底层是Node类型的数组(JDK1.8)
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
HashTable底层是Entry类型的数组
/**
* The hash table data.
*/
private transient Entry<?,?>[] table;
HashMap是非线程安全的,HashTable是线程安全的,所有公共方法都有synchronized修饰。
由于HashTable的方法有synchronized修饰,在单线程环境下其开销较大,而HashMap的性能更好。
初始容量和扩容机制不同:
HashMap可以接受为null的key和value,HashTable不接受空值。这是因为HashMap在实现时对null做了特殊处理,将null的hashCode值定为了0,从而将其存放在哈希表的第0个bucket中(数组中下标为0的元素)。
在迭代方面,HashTable使用Enumeration,HashMap使用Iterator。Iterator其实与Enmeration功能上很相似,只是多了删除的功能。