Java集合之HashMap

一、简介

HashMap采用hash表的数据结构,本质上就是数组+单向链表(或者是红黑树)。

静态变量:

 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //hash表的初始容量
static final int MAXIMUM_CAPACITY = 1 << 30; //hash表的最大容量,2的30次方
static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认负载系数
static final int TREEIFY_THRESHOLD = 8; //同hash的链表最大长度,超过这个值链表变为树
static final int UNTREEIFY_THRESHOLD = 6; //红黑树节点小于这个值会转成链表
static final int MIN_TREEIFY_CAPACITY = 64;

成员变量:

transient Node<K,V>[] table; //hash表
transient Set<Map.Entry<K,V>> entrySet; //KV数据
transient int size; //元素个数
transient int modCount; //修改次数
int threshold; //负载容量
final float loadFactor; //负载系数

单向链表节点结构:

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
    ......
    ......
}

红黑树节点结构:

二、初始化

三、增加

四、删除

五、查找

六、修改

复用增加方法put

七、快速失败机制

fail—fast快速失败机制是是java集合中的一种机制, 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。

原理

迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。

集合在被遍历期间如果内容发生变化,就会改变modCount的值。

每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug。

解决方案

在单线程遍历中如果使用remove或者add就会导致这个问题,那么在单线程中如何解决呢,有两种方式:

  1. 使用for循环

  2. 使用iterator.remove();

八、安全失败机制

java.util里面的集合是快速失败机制,而java.util.concurrent里面则是安全失败机制(fail-safe)。采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛 ConcurrentModificationException 异常。

缺点就是:

  1. 需要复制集合,产生大量的无效对象,开销大;

  2. 无法保证读取的数据是目前原始数据结构中的数据。

九、线程不安全导致的问题

  1. 数据不一致;

  2. 扩容rehash的时候可能会形成循环链表,后续遍历的时候产生死锁;(https://www.jianshu.com/p/4930801e23c8)

  3. 多线程put操作可能导致数据丢失,比如两个线程两个不同的key产生了hash冲突,在插入的时候都发现没有值,就都去put,那么后面put的会把前面put的值覆盖掉;

参考:

https://liuyanzhao.com/9034.html

重点问题

1 如何扩容

Last updated

Was this helpful?