翻译来源 java.util.HashMap JDK1.7
HashMap API 所有翻译,请查看翻译目录。
Map接口方法实现
查询、修改、批量、视图、比较和散列
查询操作
1. size
返回该 map 中键值对的数目。
public int size() {
return size;
}
2. isEmpty
如果该 map 不包含键值对,则返回true。
public boolean isEmpty() {
return size == 0;
}
3. containsKey
如果该 map 包含指定键的映射,则返回true。
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
getEntry
返回与HashMap
中指定键关联的项。如果HashMap
不包含键的映射,则返回null。
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 && // 尝试匹配每个条目 Entry
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;// 没有匹配中,返回null
}
hash
检索对象哈希码,并对结果哈希应用一个补充的哈希函数,这可以防御质量差的哈希函数。因为HashMap
使用的是2次幂长度的哈希表,所以,这一点非常关键。否则,在低位没有差别的哈希码会出现哈希碰撞。注意:Null
键总是映射到哈希值0
,因此索引为0
。
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// 这个函数确保,在每个位位置只相差常数倍的哈希码,有一定数量的冲突(在默认负载因子下约为8次)。
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
indexFor
返回哈希码h的索引。
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
4. containsValue
如果该 map 将一个或多个键映射到指定值,则返回true。
public boolean containsValue(Object value) {
if (value == null)
return containsNullValue();
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)// 遍历数组
for (Entry e = tab[i] ; e != null ; e = e.next)// 遍历槽上链表
if (value.equals(e.value)) // 只要有一个匹配,就立即返回 true
return true;
return false; // 遍历完毕,没有匹配中,返回 false
}
containsNullValue
包含null
参数的containsValue
的特殊情况代码
private boolean containsNullValue() {
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (e.value == null) // 只要有一个值为 null,就立即返回
return true;
return false;
}
5. get
返回指定键映射到的值,如果该map不包含该键的映射,则返回null
。
更正式地说,如果该map包含从键k到值v的一个映射,使得(key==null ? k==null : key.equals(k))}
,则该方法返回v; 否则返回null
。 (最多可以有一个这样的映射。)
返回值为null
,并不一定表示该map不包含该键的映射;它也可能显式地将键映射到null
。使用containsKey
操作可以区分(distinguish)这两种情况。
public V get(Object key) {
if (key == null) // 键为null
return getForNullKey();
Entry<K,V> entry = getEntry(key); // 键不为null
return null == entry ? null : entry.getValue();
}
getForNullKey
卸载版本的get()
来查找null键。空键映射到索引0。为了提高两个最常用操作(get和put)的性能,这个空例被分割成不同的方法,但是在其他操作中与条件语句合并。
private V getForNullKey() {
if (size == 0) {
return null;
}
for (Entry<K,V> e = table[0]; e != null; e = e.next) {// 遍历槽0上的链表
if (e.key == null)
return e.value;
}
return null;
}
修改操作
1. put
将指定的值与该map中的指定键相关联。如果该map先前包含该键的一个映射,则替换旧值。
public V put(K key, V value) {
if (table == EMPTY_TABLE) { // 空表,扩容
inflateTable(threshold);
}
if (key == null)// null键,单独处理
return putForNullKey(value);
// 计算索引
int hash = hash(key);
int i = indexFor(hash, table.length);
// map先前包含该键的一个映射,则替换旧值,并返回旧值
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;
}
}
// 不包含,头插法添加,并返回 null
modCount++;// 结构化修改
addEntry(hash, key, value, i);// 头插法
return null;
}
inflateTable
膨胀表
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);// 容量,根据给定的阈值计算
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);//阈值,根据新容量计算
table = new Entry[capacity]; // 表,新条目数组,长度为容量
initHashSeedAsNeeded(capacity);//初始化散列掩码值。 我们推迟初始化直到需要它。
}
putForNullKey
用于null键的卸载版本的put()
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {// 遍历槽0上的链表,替换旧值,并返回旧值
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 不包含,头插法添加,并返回 null
modCount++;
addEntry(0, null, value, 0);
return null;
}
addEntry
将具有指定键、值和散列码的新条目添加到指定的桶中。在适当的时候,这个方法有职责调整表的大小。
子类重写此方法以更改put方法的行为。
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) { // 扩容2倍
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length); // 重新计算索引
}
createEntry(hash, key, value, bucketIndex); // 创建条目,并使用头插法插入
}
resize
将该 map 的内容重新放入具有更大容量的新数组中。 当该 map 中的键数达到其阈值时,将自动调用此方法。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) { // 老容量太大,不再扩容
threshold = Integer.MAX_VALUE;
return;
}
// 老容量不够大,扩容
Entry[] newTable = new Entry[newCapacity];// 创建新表
transfer(newTable, initHashSeedAsNeeded(newCapacity)); // 原内容转存到新表
table = newTable; // 更新表
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);// 更新阈值
}
transfer
将当前表中的所有条目转存到newTable
。
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {// 遍历数组
while(null != e) {// 遍历槽位上的链表
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity); //计算新槽位
e.next = newTable[i];// 头插法,插入到新槽位
newTable[i] = e;
e = next;
}
}
}
createEntry
与addEntry
类似,只是在创建map构造或“伪构造”(克隆、反序列化)的部分条目时,使用了这个版本。这个版本不必担心调整表的大小。子类覆盖它来更改HashMap(Map)
、clone
和readObject
的行为。
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
2. remove
如果该map中存在指定键的映射,删除该映射。
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
removeEntryForKey
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {// 空表,直接返回null
return null;
}
// 计算索引(槽位)
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) { // 遍历槽位上的链表
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash && // 尝试匹配
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)// 第一个条目
table[i] = next;
else // 后继条目
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
批量操作
1. putAll
复制指定map中的所有映射,到该map。这些映射将替换该map拥有的指定map中的当前任何键的任何映射。
public void putAll(Map<? extends K, ? extends V> m) {
int numKeysToBeAdded = m.size();// 要插入条目数
if (numKeysToBeAdded == 0) // 插入0条
return;
if (table == EMPTY_TABLE) { // 空表,则膨胀表
inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold));
}
/*
* Expand the map if the map if the number of mappings to be added
* is greater than or equal to threshold. This is conservative; the
* obvious condition is (m.size() + size) >= threshold, but this
* condition could result in a map with twice the appropriate capacity,
* if the keys to be added overlap with the keys already in this map.
* By using the conservative calculation, we subject ourself
* to at most one extra resize.
*/
if (numKeysToBeAdded > threshold) { // 提前扩容
int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
if (targetCapacity > MAXIMUM_CAPACITY)
targetCapacity = MAXIMUM_CAPACITY;
int newCapacity = table.length;
while (newCapacity < targetCapacity)
newCapacity <<= 1;
if (newCapacity > table.length)
resize(newCapacity);
}
// 逐条插入
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
2. clear
从该map中删除所有映射。此调用返回后,该map将为空。
public void clear() {
modCount++;
Arrays.fill(table, null);// 遍历数组,并置null
size = 0;
}
视图
1. keySet
返回该 map 中包含的键的集合视图。集合由 map 支持,因此对 map 的更改反映在集合中,反之亦然。如果在对集合进行迭代时修改 map (除了通过迭代器自己的删除操作),迭代的结果是未定义的。集合支持元素删除,元素删除通过Iterator.remove
, Set.remove
, removeAll
, retainAll
, clear
操作从 map 中删除对应的映射。它不支持add
或addAll
操作。
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
使用
Eclipse IDE
调试时,发现keySet
变量中有值,这只是错觉。keySet
对象中没有任何成员变量,调试器之所以显示有值,是因为调试器调用了keySet.toString()
方法,并打印其返回值。而keySet.toString()
方法内部又调用了iterator()
方法。
KeySet 类
这是一个内部类。
该类没有任何成员变量,仅通过各种方法与 HashMap
对象建立联系。
private final class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() { // 迭代器
return newKeyIterator();
}
public int size() { // 大小
return size;
}
public boolean contains(Object o) { // 是否包含某键
return containsKey(o);
}
public boolean remove(Object o) { // 移除某键
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() { // 清空
HashMap.this.clear();
}
}
newKeyIterator
用子类覆盖该方法,就可以更改视图的iterator()
方法的行为
Iterator<K> newKeyIterator() {
return new KeyIterator();
}
KeyIterator 类
Iterator
方法:next()
private final class KeyIterator extends HashIterator<K> {
// 下一个键
public K next() {
return nextEntry().getKey();
}
}
HashIterator 类
Iterator
方法:hasNext()
、remove
private abstract class HashIterator<E> implements Iterator<E> {
Entry<K,V> next; // 要返回的下一个 entry
int expectedModCount; // 用来做快速失败机制(fast-fail)
int index; // 当前槽
Entry<K,V> current; // 当前 entry
HashIterator() {
expectedModCount = modCount; // 期望修改计数
if (size > 0) { // 推进到第一个entry
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null) // 循环找到第一个entry
;
}
}
// 是否有下一个 entry,有 entry 必然有 key
public final boolean hasNext() {
return next != null;
}
// 返回下一个 entry,并更新当前对象的成员变量的值
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount) // 快速失败机制
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null) // 没有下一个 entry
throw new NoSuchElementException();
if ((next = e.next) == null) { // 到达链表尾部,快速推进到下一个 entry,并更新 next 和 index
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e; // 更新 current
return e; // 返回
}
// 删除当前 entry
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount) // 快速失败机制
throw new ConcurrentModificationException();
Object k = current.key;
current = null; //删除当前 entry,并更新 current 和 expectedModCount。
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
}
2. values
返回该 map 中包含的值的集合视图。集合由 map 支持,因此对 map 的更改反映在集合中,反之亦然。如果在对集合进行迭代时修改 map (除了通过迭代器自己的删除操作),迭代的结果是未定义的。集合支持元素删除,元素删除通过Iterator.remove
, Collection.remove
, removeAll
, retainAll
和clear
操作从 map 中删除对应的映射。它不支持add
或addAll
操作。
public Collection<V> values() {
Collection<V> vs = values;
return (vs != null ? vs : (values = new Values()));
}
Values 类
这是一个内部类。
该类没有任何成员变量,仅通过各种方法与 HashMap
对象建立联系。
private final class Values extends AbstractCollection<V> {
public Iterator<V> iterator() { // 迭代器
return newValueIterator();
}
public int size() { // 大小
return size;
}
public boolean contains(Object o) { // 是否包含某值
return containsValue(o);
}
public void clear() { // 清空
HashMap.this.clear();
}
}
newValueIterator
用子类覆盖该方法,就可以更改视图的iterator()
方法的行为
Iterator<V> newValueIterator() {
return new ValueIterator();
}
ValueIterator 类
Iterator
方法:next()
private final class ValueIterator extends HashIterator<V> {
public V next() {
return nextEntry().value;
}
}
3. entrySet
public Set<Map.Entry<K,V>> entrySet() {
return entrySet0();
}
private Set<Map.Entry<K,V>> entrySet0() {
Set<Map.Entry<K,V>> es = entrySet;
return es != null ? es : (entrySet = new EntrySet());
}
EntrySet 类
这是一个内部类。
该类没有任何成员变量,仅通过各种方法与 HashMap
对象建立联系。
private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() { // 迭代器
return newEntryIterator();
}
public boolean contains(Object o) { // 是否包含某条目
if (!(o instanceof Map.Entry))
return false;
Map.Entry<K,V> e = (Map.Entry<K,V>) o;
Entry<K,V> candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
public boolean remove(Object o) { // 移除某条目
return removeMapping(o) != null;
}
public int size() { // 大小
return size;
}
public void clear() { // 清空
HashMap.this.clear();
}
}
newEntryIterator
用子类覆盖该方法,就可以更改视图的iterator()
方法的行为
Iterator<Map.Entry<K,V>> newEntryIterator() {
return new EntryIterator();
}
EntryIterator 类
Iterator
方法:next()
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
public Map.Entry<K,V> next() {
return nextEntry();
}
}
4. Entry 接口实现
略
比较和散列
equals
继承自抽象类AbstractMap
。略
hashCode
继承自抽象类AbstractMap
。略