采用哈希表来存储数据。结构如下:
它在内部维护了一个Entry数组
transient Entry[]table;
Entry 的结构如下:
static class EntryimplementsMap.Entry{
final K key;
V value;
Entry next;
int hash;
}
HashMap 中table 的大小是动态变化的,并可以设置一装载因子(load factor)。当HashMap中元素总量超过阈值时(capacity*loadFactor),再向其中新增元素将会重新分配一个table 替换当前的。Table大小一定是2的幂,比如最小是16,然后增长的话会调整到32,64,128…,直到最大 map 大小2^30。
每个元素根据其 hash的不同存储在 table 数组的不同位置:
static int indexFor(int h,int length){
return h &(length-1);
}
final EntrygetEntry(Object key){
int hash = (key== null)? 0:hash(key);
for(Entrye=table[indexFor(hash,table.length)];e!=null;e=e.next){
Object k;
if(e.hash ==hash &&((k=e.key)==key||(key!=null&&key.equals(k))))
return e;
}
return null;
}
获取数据时直接找到相应 index位置的链表,遍历链表找到对应元素。
如果已经有对应 key的元素,则用新元素替代,并返回旧元素。否则返回 null。
Put的时候判断当前元素数是否达到阈值,如果达到需要resize table。先判断大小是否达到最大限制,没有的话 capacity 翻倍。然后将旧 table中的元素重新计算 bucket位置放入新table。
void resize(int newCapacity){
Entry[] oldTable=table;
Int oldCapacity= oldTable.length;
if(oldCapacity ==MAXIMUM_CAPACITY){
threshold=Integer.MAX_VALUE;
return;
}
Entry[] newTable= newEntry[newCapacity];
Boolean oldAltHashing= useAltHashing;
useAltHashing|=sun.misc.VM.isBooted()&&
(newCapacity>=Holder.ALTERNATIVE_HASHING_THRESHOLD);
Boolean rehash= oldAltHashing^ useAltHashing;
transfer(newTable,rehash);
Table = newTable;
threshold = (int)Math.min(newCapacity*loadFactor,MAXIMUM_CAPACITY+1);
}
HashMap访问迭代器的时候可能抛出 ConcurrentModificationException。
final Entry nextEntry(){
if(modCount!=expectedModCount)
throw new ConcurrentModificationException();
Entrye=next;
if(e==null)
throw new NoSuchElementException();
if((next=e.next)==null){
Entry[]t=table;
while(index
modCount在每次HashMap发生结构性变化的时候都会改变(新增、删除元素,rehash),而 expectedModCount 是迭代器维护的,并不会随着HashMap结构变化而改变。
需要注意,并不是一定是多线程访问才会触发 ConcurrentModificationException。在单线程的访问中,迭代器操作中不能直接通过HashMap.put和 HashMap.remove操作更改 HashMap。如果确实需要删除,可以通过迭代器提供的 remove方法删除元素,迭代器的 remove方法会修改expectedModCount。当然,多线程访问还是一定会有问题的。
public voidremove(){
if(current==null)
throw newIllegalStateException();
if(modCount!=expectedModCount)
throw newConcurrentModificationException();
Objectk=current.key;
current=null;
HashMap.this.removeEntryForKey(k);
expectedModCount =modCount;
}
还有几点需要注意
1. 初始化时,如果可以提前预知HashMap 的大小,可以指定一个initialSize,避免因为size 过小导致put 的时候多次 resize 操作
2. HashMap 最大size 大小为 1<<30, 也就是1G。因此需要注意向Map 中放入的内容一定不要超过1G。