存储结构
- 初始化
```
// 初始化数组大小为默认大小
Map<String, String> map = new HashMap<>();
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
```
HashMap是链表散列
数据结构, 通过使用数组
和链表
组合方式实现快速定位, 快速修改.
-
数组(table): 查找方便, 修改困难
-
链表(Entry<K,V>): 查找困难, 修改方便
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { /** * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry<K,V>[] table; static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; ... } }
-
简单示意图
-
保存数据分为两种情况
- key == null
总是保存在数组第一个位置(Entry<K,V> e = table[0]), 如果数组第一个位置桶已经存在, 找到第一个key == null的节点, 使用新值覆盖原来的值。 如果没有就创建一个Entry节点,并在数组中保存对节点的引用。
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); ... } 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++; addEntry(0, null, value, 0); return null; }
- key != null
首先计算key的hash值, 然后根据hash值找到桶的位置 ,
hash & (table.length-1)
, 由于table.length总是2的倍数, 该操作与hash % table.length
结果一致 ,再找到桶( 链表 )中与key一致的节点 if (e.hash == hash && ((k = e.key) == key || key.equals(k))). 如果找到, 就替换掉原来的值 e.value = value.如果没找到就在该桶的位置创建新的节点, 并把原来已存在的数据放在新节点之后 table[bucketIndex] = new Entry<>(hash, key, value, table[bucketIndex]).
map.put(0, "0"); public V put(K key, V value) { ... // 计算hash值 int hash = hash(key); // 通过hash值找到桶的位置 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; } } // 在指定桶中添加新的节点 modCount++; addEntry(hash, key, value, i); ... }
注意: 值覆盖/修改不会改变modCount, 也就是说影响视图迭代器(View Iterator)的使用, 而通过HashMap.put/HashMap.remove会导致视图迭代器失效, 并抛出异常ConcurrentModificationException
private abstract class HashIterator<E> implements Iterator<E> { ... HashIterator() { expectedModCount = modCount; ... } final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); ... } ... }
-
获取数据
首先计算key的hash值, null总是在第一个桶 int hash = (key == null) ? 0 : hash(key) , 再找到与key一致的节点 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(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; }
这也导致覆盖一个对象的equals方法时, 需要同时覆盖hashCode方法的原因
以上仅个人观点, 欢迎留言讨论.