ソースコードはいつでも見ることができます。
そして、HashMap にバケットの配列があることがわかります。
transient Entry[] table;
すべてのバケットは基本的にリンクされたリストです。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
配列は、指定されたハッシュ コードのバケットへの一定時間のアクセスを提供し、そのリストをループする必要があります (1 つまたは 2 つ以上のエントリが含まれていないことを願っています)。
final Entry<K,V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
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;
}
異なる hashCode を持つ要素が HashSet に追加されると、新しい要素が追加されますよね?
既存のものと同じ hashCode を持つ要素が追加されると、同じバケット (リンクされたリストの最後) に入ります。
新しい hashCode を持つ要素が追加されると、別のバケットに移動する場合と移動しない場合があります (バケットよりもはるかに多くの hashCode があるため)。
すべてのバケットは、マップのサイズが決定されるときに事前に作成されます。容量制限に達すると、バケットのサイズが変更され、すべてが新しいバケットに入れられます。
この新しいバケットはどのデータ構造に追加されますか?
バケットは追加されません。バケットの固定配列があります。より多くの容量が必要な場合は、構造全体がより大きな配列で再構築されます。
新しい要素が追加されるたびに、ある種の配列とサイズ変更に再び頼るので、HashSet O(n) への追加と削除が複雑になりますか?
毎回ではありません。理想的には決して。容量の計算を誤って、さらに容量が必要になった場合のみ。次に、すべてが新しい配列にコピーされるため、コストがかかります。このプロセスは基本的に ArrayList と同じです。