【HashMap JDK1.7】Map 接口方法实现


翻译来源 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)clonereadObject的行为。

    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 中删除对应的映射。它不支持addaddAll操作。

    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, retainAllclear 操作从 map 中删除对应的映射。它不支持addaddAll操作。

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。略

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值