HashMap、HashTable和Vector是面试时比较高频问到的知识点,今天就从三个的底层源码的角度分析三者之间的存储、扩容原理和异同点。
HashMap:实现Map接口
实现原理:HashMap采用链地址法。即底层是一个数组实现。数组的每一项(即一个Entry)又是一个链表。结构图如下:
每个Entry是一个键值对。源码如下:
1. transient Entry[] table;
2.
3. static class Entry<K,V> implements Map.Entry<K,V> {
4. final K key;
5. V value;
6. Entry<K,V> next;
7. final int hash;
8. ……
9. }
HashMap的存储机制:首先根据key计算出该key对应的Entry在数组中的位置,然后判断是否该Entry是否在该位置对应的链表中,如果不在,则插入链表的头部,如果在,则更新该链表中对应Entry的value。
HashMap计算key所属数组的位置方法:首先计算key的hash值,然后根据hash函数hashcode & (length – 1)计算出所在数组的位置(因为HashMap的数组长度为2的整数幂,所以采用位运算的结果和hash % length相同,但是位运算的效率要远高于求余运算)。源码如下:
1. static int indexFor(int h, int length) {
2. return h & (length-1);
3. }
HashMap的扩容:每当初始化一个HashMap,默认的数组大小(table.size)为16,默认的增长因子(loadFactor)为0.75,;当元素个数超过数组大小的loadFactor
时,就会
对数组进行扩容。HashMap采用的扩容方法为:每次把数组大小扩大一倍,然后重新计算HashMap中每个元素在数组中的位置。也可以
自定义扩展容量的大小(
HashMap(int initialCapacity))。
HashTable:实现了Map接口,同时也是Dictionary抽象类的具体实现。
HashTable通过Synchronize实现线程安全。实现原理与HashMap相同,也是采用数组+链表的结构实现。不同于HashMap的是:
1) HashTable的默认大小为11
2) 数组位置的计算方法不同;HashTable的 index = (hashcode & Integer.MAX_VALUE) % table.length
3) 扩容的方法不一样;newlength = oldlength * 2 + 1
4) 在使用中,HashTable不允许key值为null,也不允许value为null
Vector:实现了List接口,是一个用Synchronize修饰的线程安全的动态数组。
扩容方法:
1,计算新容量大小。NewSize = oldSize + (Increment>0 ) ? Increment:oldSize。(如果增长因子小于零,每次扩容大小为增长前的一倍,如果增长因子大于零,则每次扩容大小为增长因子的大小)
2,判断新容量大小和最小需求大小。如果小于最小需求,则把最小需求容量复制给新容量。
3,如果新容量大小大于数组最大容量,则判断最小需求大小和最大数组容量大小;如果最小需求大小大于最大数组容量,则最终新容量为Integer.MaxValue,反之则最终新容量大小为最大数组容量(最大数组容量 = Integer.MaxValue-8)。
设置第三步的原因:vector的增长不是无节制的。Vector的最大长度限制为Integer.MaxValue,所以在第二步计算出新容量大小后,需要比较新容量大小和最大数组容量大小,如果大于,则对最小需求容量调用最终容量计算函数来求出最终的容量。
源码分析:
/**
* This implements the unsynchronized semantics of ensureCapacity.
* Synchronized methods in this class can internally call this
* method for ensuring capacity without incurring the cost of an
* extra synchronization.
*
* @see #ensureCapacity(int)
*/
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
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;
//步骤三 保证Vector不会不限制增长
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}