JDK 1.7 HashMap,ConcurrentHashMap源碼解析

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

靜態常量就不說了,先來看看 HashEntrySegment 對象的定義

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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章