Java HashMapのput処理の内部実装解説

JavaのHashMapは効率的なキー値ペア管理を実現するためのコアデータ構造です。putメソッドの動作を理解するためには、ハッシュ値計算、衝突解決、テーブル拡張の仕組みを把握する必要があります。以下、処理の詳細を解説します。

基本処理フロー

putメソッドは指定されたキーと値をマップに追加または更新します。内部ではputValメソッドを呼び出し、ハッシュ値と制御フラグを渡します。

public V put(K key, V value) {
    return putVal(computeHash(key), key, value, false, true);
}

putValの詳細処理

putValメソッドはハッシュ値、キー、値、制御フラグを受け取り、以下のステップで処理を行います。

テーブル初期化

テーブルが未初期化または空の場合、resizeメソッドで初期化します。

if (buckets == null || (tableSize = buckets.length) == 0) {
    tableSize = (buckets = resize()).length;
}

インデックス算出

テーブルサイズが2の累乗である特性を利用し、(n-1) & hashで効率的にインデックスを計算します。

int index = (tableSize - 1) & hash;
if (buckets[index] == null) {
    buckets[index] = createNode(hash, key, value, null);
}

衝突解決

インデックス位置に既存エントリが存在する場合、衝突を解決します。

Node current = buckets[index];
if (current.hash == hash && (current.key == key || (key != null && key.equals(current.key)))) {
    existingNode = current;
} else if (current instanceof TreeNode) {
    existingNode = ((TreeNode)current).putTreeValue(this, buckets, hash, key, value);
} else {
    for (int chainLength = 0; ; chainLength++) {
        Node nextNode = current.next;
        if (nextNode == null) {
            current.next = createNode(hash, key, value, null);
            if (chainLength >= TREEIFY_THRESHOLD - 1) {
                convertToTree(buckets, hash);
            }
            break;
        }
        if (nextNode.hash == hash && (nextNode.key == key || (key != null && key.equals(nextNode.key)))) {
            break;
        }
        current = nextNode;
    }
}

値更新と挿入後処理

既存ノードが見つかった場合は値を更新し、新規挿入時はサイズを増加させて必要に応じてテーブルを拡張します。

if (existingNode != null) {
    V previousValue = existingNode.value;
    if (!onlyIfAbsent || previousValue == null) {
        existingNode.value = value;
    }
    afterNodeAccess(existingNode);
    return previousValue;
}
modCount++;
if (++size > threshold) {
    resize();
}
afterNodeInsertion(evict);
return null;

テーブル拡張メカニズム

resizeメソッドはテーブル容量を2倍に拡張し、既存エントリを再配置します。リハッシュ処理では元テーブルのエントリを新テーブルに再配置します。

Node[] previousTable = table;
int previousCapacity = previousTable != null ? previousTable.length : 0;
int previousThreshold = threshold;
int newCapacity, newThreshold = 0;
if (previousCapacity > 0) {
    if (previousCapacity >= MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return previousTable;
    }
    newCapacity = previousCapacity << 1;
    if (newCapacity < MAXIMUM_CAPACITY && previousCapacity >= MIN_INITIAL_CAPACITY) {
        newThreshold = previousThreshold << 1;
    }
} else if (previousThreshold > 0) {
    newCapacity = previousThreshold;
} else {
    newCapacity = DEFAULT_INITIAL_CAPACITY;
    newThreshold = (int)(DEFAULT_LOAD_FACTOR * newCapacity);
}
if (newThreshold == 0) {
    float newThresholdFloat = newCapacity * loadFactor;
    newThreshold = (newCapacity < MAXIMUM_CAPACITY && newThresholdFloat < MAXIMUM_CAPACITY) ?
        (int)newThresholdFloat : Integer.MAX_VALUE;
}
threshold = newThreshold;
Node[] newTable = new Node[newCapacity];
table = newTable;
if (previousTable != null) {
    for (int i = 0; i < previousCapacity; i++) {
        Node entry = previousTable[i];
        if (entry != null) {
            previousTable[i] = null;
            if (entry.next == null) {
                newTable[entry.hash & (newCapacity - 1)] = entry;
            } else if (entry instanceof TreeNode) {
                ((TreeNode)entry).split(this, newTable, i, previousCapacity);
            } else {
                Node lowerHead = null, lowerTail = null;
                Node higherHead = null, higherTail = null;
                do {
                    Node nextEntry = entry.next;
                    if ((entry.hash & previousCapacity) == 0) {
                        if (lowerTail == null) lowerHead = entry;
                        else lowerTail.next = entry;
                        lowerTail = entry;
                    } else {
                        if (higherTail == null) higherHead = entry;
                        else higherTail.next = entry;
                        higherTail = entry;
                    }
                    entry = nextEntry;
                } while (entry != null);
                if (lowerTail != null) {
                    lowerTail.next = null;
                    newTable[i] = lowerHead;
                }
                if (higherTail != null) {
                    higherTail.next = null;
                    newTable[i + previousCapacity] = higherHead;
                }
            }
        }
    }
}
return newTable;

タグ: Java HashMap hash-collision resizing data-structure

8月12日 04:49 投稿