JAVA源码之HashMap笔记

120 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

fail-fast机制

在集合中定义一个变量modCount来记录集合的修改次数,每次对集合结构的修改都通过modCount++来告知集合的操作者,尽可能的提示其他操作者当前集合被修改过了。如果 操作者认为集合被操作过对自己的逻辑有影响,那么则可以抛出ConcurrentModificationException异常。

常用的集合

  • Set

    • HashSet : 基于HashMap的Set实现,其内部核心是HashMap<E,Object>;
    • TreeSet : 基于TreeMap的NavigableSet实现,其内部核心是TreeMap<E,Object>;
  • List

    • ArrayList

      • List接口的可变长数组实现。

      • 允许空元素

      • get、set等操时间复杂度O(1)。add操作时间复制度O(n)

      • 非线程安全的。多线程情况下需要加锁,如果么有外部锁,需要Collections.synchronizedList用包裹起来。

        List list = Collections.synchronizedList(new ArrayList(...));
        

      • 默认初始长度是10。

      • 最大长度是Integer.MAX_VALUE;

    • LinkedList

      • List接口的双向链表实现。
      • 允许空元素。
      • get、set操作时间复杂度是O(n),add操作时间复杂度是O(1)。
      • 非线程安全。
  • Map

    • HashMap
    • TreeMap

HashMap数据结构?

20220308173249.png

HashMap在1.8中是由三个部分组成的,数组、链表、红黑树;

put、get方法的时间复杂度是O(1)~O(log(n))

HashMap的关键参数:

  1. 初始容量:默认16,必须是2的幂(因此在扩容的时候,每个bin中的元素要么保持在相同的索引上,要么在新表中以2的幂次移动。 总之一句话就是提高扩容效率 );
  2. 负载因子:默认0.75;

put操作:

image-20220311170151309.png

表格扩容/初始化:

注意:扩容扩的是table.length,是数组的长度,不是HashMap的总容量,理论上讲HashMap没有容量上限。

  1. HashMap的初始化操作是在第一次put操作是进行的。
  2. 扩容是将容量扩大一倍
  3. 最大容量是 2^30
  4. 扩容后对所有元素进行rehash

使用hashmap的注意事项:

核心重点是减少rehash操作,也就是扩容操作。还有就是减少链表长度和红黑树深度,也就是所谓的哈希冲突。

  1. 在能预计到放入元素个数的情况下,初始化时就指定HashMap的容量和负载因子,initialCapacity=Math.ceil( N / loadFactor )。

    放入元素个数N,初始化容量initialCapacity=Math.ceil( N / loadFactor ),譬如需要放入15个元素,使用负载因子0.75,那么初始化为 new HashMap(20,0.75);如果我们用new HashMap(15,0.75),那么hashMap在初始化的时候容量是16,当元素放入到12后,hashMap将触发扩容,即Rehash。

  2. 合理使用Key的hashcode方法,减少hash冲突。