HashMap
靜態常量
/**
* The default initial capacity - MUST be a power of two.
* 初始容量是2的冪:16
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
* 最大容量
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
* 加載因子,0.75
* 初始大小=16,當數組大小達到16*0.75=12時,擴容
* 擴容目的:減短鏈表
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
put
先走一遍整體流程,再詳細看方法
public V put(K key, V value) {
// 初始化數組,threshold = 16
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
// HashMap的key可以爲null,將null放到index=0的位置
if (key == null)
return putForNullKey(value);
// 爲什麼不直接key.hashCode(),而是要進行其他操作? 讓數據更加分散
int hash = hash(key);
// 數組下標,初始時,一定在 0~15
int i = indexFor(hash, table.length);
// 遍歷鏈表,如果鍵重複,使用新值覆蓋舊值,並且返回舊值
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 用於快速失敗,HashMap的線程是不安全的,所以在其他線程操作時改變了HashMap的結構,就需要拋出異常
modCount++;
// 鍵未重複,創建鏈表,加到數組中;擴容
addEntry(hash, key, value, i);
return null;
}
1. inflateTable
/**
* Inflates the table.
*/
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);
// 修改臨界值,初始時,threshold = 12
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
// 初始化數組,size=16
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
2. putForNullKey
/**
* Offloaded version of put for null keys
*/
private V putForNullKey(V value) {
// 遍歷鏈表,如果鍵重複,使用新值覆蓋舊值,並且返回舊值
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 將null的鍵放入哈希表0索引的位置
addEntry(0, null, value, 0);
return null;
}
3. addEntry
void addEntry(int hash, K key, V value, int bucketIndex) {
// size >= 12並且該位置已經有鏈表,擴容。多線程情況下,擴容死循環問題?
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
4. createEntry
void createEntry(int hash, K key, V value, int bucketIndex) {
// 當前數組下標的節點對象
Entry<K,V> e = table[bucketIndex];
// 放入鏈表頭
table[bucketIndex] = new Entry<>(hash, key, value, e);
// key-value size++
size++;
}
5. indexFor
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
// 初始length=16,length-1=15,與任何h&,其值肯定在 0~15
return h & (length-1);
}
get
get比較簡單
public V get(Object key) {
// 獲取key爲null的值,index=0的地方
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
1. getForNullKey
private V getForNullKey() {
if (size == 0) {
return null;
}
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
2. getEntry
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
// 遍歷鏈表,拿數據
// indexFor(hash, table.length),獲取key對應的下標
for (Entry<K,V> e = 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;
}
ConcurrentHashMap
靜態常量就不說了,先來看看 HashEntry 和 Segment 對象的定義
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
...
}
static final class Segment<K,V> extends ReentrantLock implements Serializable {
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
transient volatile HashEntry<K,V>[] table;
transient int count;
transient int modCount;
transient int threshold;
final float loadFactor;
Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
this.loadFactor = lf;
this.threshold = threshold;
this.table = tab;
}
...
}
操作和HashMap可以類比,就不詳說了,具體說說兩個地方。
1. 初始化ConcurrentHashMap對象
// 初始化segment數組和segment[0],其他的HashEntry數組不會立即初始化,用到的時候,再初始化
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
// UNSAFE類,基於內存
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
2. put操作的線程安全實現 ensureSegment
// while + CAS 樂觀鎖的實現,併發安全
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}