HashMap 笔记

HashMap 笔记

HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。

HashMap数组每一个元素的初始值都是Null;

1. 两个常用的方法:

  1. Put方法
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
//Hash方法
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

当调用Put方法,比如插入一个元素,Hash方法算出Index = 1

然后随着元素的越来越多,,,Index会发生冲突,随会形成链表

HashMap的每个元素不止是一个Entry对象,也是一个链表的头节点。每个新的Entry对象通过next指向下一个Entry节点。

插入链表方式:头插法

  1. Get方法
public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

同原理计算Hash值,遍历链表,取出元素


2. 细节

HashMap 初始容量为 16,并且自动扩展或者初始化,长度必须为2的幂

/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

resize()函数注释

    /**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */

HashMap的公式:

index = HashCode(Key) & (Length - 1)

引用 => Hash算法的均匀分布,减少哈希碰撞几率:16或者其他2的幂,Length-1的值是后面的二进制位全为1,例如16-1=15二进制1111,32-1=31二进制11111,这种情况下,index = key值HashCode&(length-1),index的结果等同于key值的HashCode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。


HashMap扩容

resize因素:

  1. Capacity

HashMap的当前长度。上一期曾经说过,HashMap的长度是2的幂。

  1. LoadFactor

HashMap负载因子,默认值为0.75f。

  1. 衡量是否进行resize

HashMap.Size >= Capacity * LoadFactor

扩容

  1. 扩容
    创建新的Entry空数组,长度是原来的2倍。
  2. ReHash
    遍历原Entry数组,把所有的Entry重新计算并Hash到新数组。

HashMap线程不安全

关于线程不安全,,没看明白。。。后续再记下来。。。

不过好像会进入一个死循环


JDK 1.8 前后区别

在JDK1.8以前版本中,HashMap的实现是数组+链表,它的缺点是即使哈希函数选择的再好,也很难达到元素百分百均匀分布,而且当HashMap中有大量元素都存到同一个桶中时,这个桶会有一个很长的链表,此时遍历的时间复杂度就是O(n),当然这是最糟糕的情况。

在JDK1.8及以后的版本中引入了红黑树结构,HashMap的实现就变成了数组+链表或数组+红黑树。添加元素时,若桶中链表个数超过8,链表会转换成红黑树;删除元素、扩容时,若桶中结构为红黑树并且树中元素个数较少时会进行修剪或直接还原成链表结构,以提高后续操作性能;遍历、查找时,由于使用红黑树结构,红黑树遍历的时间复杂度为 O(logn),所以性能得到提升。

posted @ 2019-11-21 14:10  带了1个小才艺  阅读(99)  评论(0)    收藏  举报