HashMap简单说明

存储结构

  1. 初始化
```
// 初始化数组大小为默认大小
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;
            ...
        }
        
    }
  • 简单示意图


  1. 保存数据分为两种情况

    • 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();
            ...
        }
        ...
    }
  2. 获取数据

    首先计算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方法的原因

    以上仅个人观点, 欢迎留言讨论.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值