HashMap 的储存结构是 数组+单链表 的结构,如下图(盗的图):
看构造函数:
/**
* Constructs a new empty {@code HashMap} instance.
*/
@SuppressWarnings("unchecked")
public HashMap() {
table = (HashMapEntry<K, V>[]) EMPTY_TABLE;
threshold = -1; // Forces first put invocation to replace EMPTY_TABLE
}
/**
* Constructs a new {@code HashMap} instance with the specified capacity.
*
* @param capacity
* the initial capacity of this hash map.
* @throws IllegalArgumentException
* when the capacity is less than zero.
*/
public HashMap(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("Capacity: " + capacity);
}
if (capacity == 0) {
@SuppressWarnings("unchecked")
HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE;
table = tab;
threshold = -1; // Forces first put() to replace EMPTY_TABLE
return;
}
if (capacity < MINIMUM_CAPACITY) {
capacity = MINIMUM_CAPACITY;
} else if (capacity > MAXIMUM_CAPACITY) {
capacity = MAXIMUM_CAPACITY;
} else {
capacity = Collections.roundUpToPowerOfTwo(capacity);
}
makeTable(capacity);
}
/**
* Constructs a new {@code HashMap} instance with the specified capacity and
* load factor.
*
* @param capacity
* the initial capacity of this hash map.
* @param loadFactor
* the initial load factor.
* @throws IllegalArgumentException
* when the capacity is less than zero or the load factor is
* less or equal to zero or NaN.
*/
public HashMap(int capacity, float loadFactor) {
this(capacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new IllegalArgumentException("Load factor: " + loadFactor);
}
/*
* Note that this implementation ignores loadFactor; it always uses
* a load factor of 3/4. This simplifies the code and generally
* improves performance.
*/
}
/**
* Constructs a new {@code HashMap} instance containing the mappings from
* the specified map.
*
* @param map
* the mappings to add.
*/
public HashMap(Map<? extends K, ? extends V> map) {
this(capacityForInitSize(map.size()));
constructorPutAll(map);
}
多个重载构造函数,可以根据需求指定 加载因子 和 初始容量。
加载因子:储存的数据个数 > 容量 * 加载因子 的时候,容量会翻倍。默认是 0.75。
初始容量:会自动更正为 2 的幂次方 (最接近初始容量,但大于初始容量) ---- capacity = Collections.roundUpToPowerOfTwo(capacity);
这是因为,容量为 2 的幂次方数的时候,计算 hash 值的时候,发生碰撞的几率会更小,有兴趣可以自己研究研究。
再看,遍历 HashMap 的时候,用到的方法。
/**
* Returns a set containing all of the mappings in this map. Each mapping is
* an instance of {@link Map.Entry}. As the set is backed by this map,
* changes in one will be reflected in the other.
*
* @return a set of the mappings.
*/
public Set<Entry<K, V>> entrySet() {
Set<Entry<K, V>> es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}
返回由 mapping 组成的 Set 集合,每个 mapping 是一个 Map.Entry,Map.Entry 就是 key-value 组成的键值对。
看 Map.Entry 是什么:
/**
* {@code Map.Entry} is a key/value mapping contained in a {@code Map}.
*/
public static interface Entry<K,V> {
/**
* Compares the specified object to this {@code Map.Entry} and returns if they
* are equal. To be equal, the object must be an instance of {@code Map.Entry} and have the
* same key and value.
*
* @param object
* the {@code Object} to compare with this {@code Object}.
* @return {@code true} if the specified {@code Object} is equal to this
* {@code Map.Entry}, {@code false} otherwise.
* @see #hashCode()
*/
public boolean equals(Object object);
/**
* Returns the key.
*
* @return the key
*/
public K getKey();
/**
* Returns the value.
*
* @return the value
*/
public V getValue();
/**
* Returns an integer hash code for the receiver. {@code Object} which are
* equal return the same value for this method.
*
* @return the receiver's hash code.
* @see #equals(Object)
*/
public int hashCode();
/**
* Sets the value of this entry to the specified value, replacing any
* existing value.
*
* @param object
* the new value to set.
* @return object the replaced value of this entry.
*/
public V setValue(V object);
};
就是一个接口,注意其实现类就行。
注意到 entrySet 方法中,entrySet 为 null 时,entrySet 赋值为 new EntrySet(),看看 EntrySet 类:
private final class EntrySet extends AbstractSet<Entry<K, V>> {
public Iterator<Entry<K, V>> iterator() {
return newEntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Entry))
return false;
Entry<?, ?> e = (Entry<?, ?>) o;
return containsMapping(e.getKey(), e.getValue());
}
public boolean remove(Object o) {
if (!(o instanceof Entry))
return false;
Entry<?, ?> e = (Entry<?, ?>)o;
return removeMapping(e.getKey(), e.getValue());
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void clear() {
HashMap.this.clear();
}
}
当我们遍历 HashMap,先拿到 entrySet ,在获取其迭代器 iterator。
public Iterator<Entry<K, V>> iterator() {
return newEntryIterator();
}
iterator() 方法调用了 newEntryIterator() 方法,看看:
Iterator<Entry<K, V>> newEntryIterator() { return new EntryIterator(); }
返回了一个 EntryIterator 对象。
private final class EntryIterator extends HashIterator
implements Iterator<Entry<K, V>> {
public Entry<K, V> next() { return nextEntry(); }
}
我们使用迭代器调用 next() 方法的时候,实际上是调用了 EntryIterator 父类 HashIterator 的 nextEntry() 方法。
private abstract class HashIterator {
int nextIndex;
HashMapEntry<K, V> nextEntry = entryForNullKey;
HashMapEntry<K, V> lastEntryReturned;
int expectedModCount = modCount;
HashIterator() {
if (nextEntry == null) {
HashMapEntry<K, V>[] tab = table;
HashMapEntry<K, V> next = null;
while (next == null && nextIndex < tab.length) {
next = tab[nextIndex++];
}
nextEntry = next;
}
}
public boolean hasNext() {
return nextEntry != null;
}
HashMapEntry<K, V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == null)
throw new NoSuchElementException();
HashMapEntry<K, V> entryToReturn = nextEntry;
HashMapEntry<K, V>[] tab = table;
HashMapEntry<K, V> next = entryToReturn.next;
while (next == null && nextIndex < tab.length) {
next = tab[nextIndex++];
}
nextEntry = next;
return lastEntryReturned = entryToReturn;
}
public void remove() {
if (lastEntryReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
HashMap.this.remove(lastEntryReturned.key);
lastEntryReturned = null;
expectedModCount = modCount;
}
}
可能会抛出 ConcurrentModificationException 异常。迭代时,注意多线程问题。
HashIterator 中 nextEntry 就是下一个 Entry 的值。注意到,nextEntry() 方法中,过滤了 Entry 为 null 的对象。
构造函数中,给 nextEntry 赋值为第一个不为 null 的 Entry。
再看操作 HashMap 集合的方法。
先是 put() 方法:
/**
* Maps the specified key to the specified value.
*
* @param key
* the key.
* @param value
* the value.
* @return the value of any previous mapping with the specified key or
* {@code null} if there was no such mapping.
*/
@Override public V put(K key, V value) {
if (key == null) {
return putValueForNullKey(value);
}
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
// No entry for (non-null) key is present; create one
modCount++;
if (size++ > threshold) {
tab = doubleCapacity();
index = hash & (tab.length - 1);
}
addNewEntry(key, value, hash, index);
return null;
}
如果存入 key 值不为 null,根据 key 值计算出 key 对应的 hash 值,根据 hash 值,算出键值对在 table 中的位置。
transient HashMapEntry<K, V>[] table,table 是一个数组。table[index] 返回的是一个 HashMapEntry,HashMapEntry 是一个单链表结构,现在看上面的图,就好理解多了。
HashMap 就是一个单链表数组,这种结构是解决 hash 值的碰撞问题。
假设,存入多个 key-value 键值对,key1 计算出来的 hash 值,与 Key2 计算出来的 hash 值相等(但是 equals 方法却不相等),那么他们都要储存到同一个位置,为了解决这个问题,就使用链表来储存这些发生碰撞的数据。
拿到 hash 值对应位置的链表,查看链表有没有头,即 table[index] 是否有值。
如果 table[index] 有值,则遍历 key 值是否存在,存在则替换。
如果 table[index] 没有值,或者 key 值不存在,addNewEntry(key, value, hash, index);
/**
* Creates a new entry for the given key, value, hash, and index and
* inserts it into the hash table. This method is called by put
* (and indirectly, putAll), and overridden by LinkedHashMap. The hash
* must incorporate the secondary hash function.
*/
void addNewEntry(K key, V value, int hash, int index) {
table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);
}
将最新的值放入 table[index] 位置,其余的以链表形式挂到后面。
看 HashMapEntry 类:
static class HashMapEntry<K, V> implements Entry<K, V> {
final K key;
V value;
final int hash;
HashMapEntry<K, V> next;
HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {
this.key = key;
this.value = value;
this.hash = hash;
this.next = next;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
@Override public final boolean equals(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry<?, ?> e = (Entry<?, ?>) o;
return Objects.equal(e.getKey(), key)
&& Objects.equal(e.getValue(), value);
}
@Override public final int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
@Override public final String toString() {
return key + "=" + value;
}
}
最重要的就是里面的 next 成员变量,看到这个就知道,是链表,实际上就是一个 Wrapper 类。
如果存入 key 值为 null 的键值对,走 putValueForNullKey 方法(说明 HashMap 支持存放 null 值,null 键):
private V putValueForNullKey(V value) {
HashMapEntry<K, V> entry = entryForNullKey;
if (entry == null) {
addNewEntryForNullKey(value);
size++;
modCount++;
return null;
} else {
preModify(entry);
V oldValue = entry.value;
entry.value = value;
return oldValue;
}
}
entryForNullKey 为 null,表示还没有存放过 key 值为 null 的 Entry。有则替换,没有新建,很简单。
最后要注意的是 addNewEntry 和 preModify 方法,该方法被 LinkedHashMap 重写了。
看 get() 方法:
/**
* Returns the value of the mapping with the specified key.
*
* @param key
* the key.
* @return the value of the mapping with the specified key, or {@code null}
* if no mapping for the specified key is found.
*/
public V get(Object key) {
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
return e == null ? null : e.value;
}
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
return e.value;
}
}
return null;
}
key值为null,直接返回 entryForNullKey。
和 put 一样,计算出储存的位置,拿到链表遍历,key 相等,则返回。
思考一个问题,HashSet 的内部实现是使用的 HashMap,HashSet add 数据的时候,是存放到 HashMap 的 Key 中,还是 Value 中???
看 remove() 方法:
/**
* Removes the mapping with the specified key from this map.
*
* @param key
* the key of the mapping to remove.
* @return the value of the removed mapping or {@code null} if no mapping
* for the specified key was found.
*/
@Override public V remove(Object key) {
if (key == null) {
return removeNullKey();
}
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index], prev = null;
e != null; prev = e, e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
if (prev == null) {
tab[index] = e.next;
} else {
prev.next = e.next;
}
modCount++;
size--;
postRemove(e);
return e.value;
}
}
return null;
}
仍然是对链表的操作。
大致就这样吧,其余的方法自己翻翻源码。