Java的MAP集合总结,包括ConcurrentHashMap

Map(映射关系)的集合,其所有的key是⼀个Set集合,即key⽆序且不能重复)

1.1. HashMap
1.1.1. 底层实现:

// 如果链表元素个数大于等于TREEIFY_THRESHOLD(8)
        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
            // 红黑树转换(并不会直接转换成红黑树)
            treeifyBin(tab, hash);

//treeifyBin 方法中判断是否真的转换为红黑树。
           // 判断当前数组的长度是否小于 64
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
          // 如果当前数组的长度小于 64,那么会选择先进行数组扩容
        resize();
//否则才将列表转换为红黑树
 else if ((e = tab[index = (n - 1) & hash]) != null) {
        // 否则才将列表转换为红黑树

        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
1.1.2. 长度2的幂次方
  • hash%length==hash&(length-1)---------前提是 length 是 2 的 n 次方
  • 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方。
1.1.3. 扩容机制:

1.1.4. 线程不安全:(数据覆盖的风险)

1.1.5. 死循环问题:
  • JDK1.7:这是由于当一个桶位中有多个元素需要进行扩容时,多个线程同时对链表进行操作,头插法可能会导致链表中的节点指向错误的位置,从而形成一个环形链表,进而使得查询元素的操作陷入死循环无法结束。
  • JDK1.8:用了尾插法而不是头插法来避免链表倒置,使得插入的节点永远都是放在链表的末尾,避免了链表中的环形结构。
1.2. LinkedHashMap

底层实现:按插⼊顺序排序

1.3. TreeMap

底层实现:

  • 实现 NavigableMap 接口(有了对集合内元素的搜索的能力。)
  • 实现SortedMap接口(有了对集合中的元素根据键排序的能力)
1.4. ConcurrentHashMap
1.4.1. 底层实现:

1.4.2. 如何理解它的线程安全性特征?

1.7:

  • 对整个桶数组进行了分割分段(Segment,分段锁),每一把锁只锁容器其中一部分数据(下面有示意图),多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。
  • Segment 继承了 ReentrantLock,是一种可重入锁(可重入锁允许一个线程多次获取同一个锁,而不会造成死锁。)

1.8:

  • ConcurrentHashMap 已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。
  • 在进行一些可能会修改数据结构的操作时(如put、remove、clear等),会使用synchronized关键字来确保锁定涉及到的部分代码的线程安全。
  • 在进行读取操作以及部分put操作时,使用无锁的CAS操作来确保原子性。

总结:

  1. 线程安全实现方式不同
  2. Hash 碰撞解决方法
  3. 并发度:JDK 1.7 最大并发度是 Segment 的个数,默认是 16。JDK 1.8 最大并发度是 Node 数组的大小,并发度更大

1.4.3. key-value非空性:
  • 可以存储空值
  • 但是ConcurrentHashMap在某些操作中对空值进行了特殊处理,以避免出现空指针异常,这是因为在并发环境下,空值可能会导致不确定的行为。
  • 因此,这也是为什么在单线程环境下,HashMap可以存储 null 的 key 和 value,而多线程下,依然也会出现问题。
1.4.4. 能保证复合操作的原子性?

what:

理解什么是原子性:在预期操作直接是否会插入别的操作

why:

  • 不会,要理解线程安全的本质是保证多个线程同时对它进行读写操作时,不会出现数据不一致的情况。和死循环的情况。
  • 没有保证每一次操作都是无误的!

how:

提供了一些原子操作,如 putIfAbsent、compute、computeIfAbsent 、computeIfPresent、merge等。

1.5. HashMap和ConcurrentHashMap的区别

线程安全性

HashMap

不是

ConcurrentHashMap

迭代是否加锁

如果在迭代过程中有其他线程对其进⾏修改,可能抛出ConcurrentModificationException 异常。

允许在迭代时进⾏并发的插⼊和删除操作,⽽不会抛出异常

初始化容量和负载因子

16 0.75

16 1(这意味着当桶达到其容量时,它会进行扩容操作)

性能

低并发情况下好

高并发度下好

ConcurrentModificationException:迭代器不是线程安全的,不能在迭代过程中修改集合的结构。

为了避免这种情况,有几种方法可以实现:

1.不要在迭代过程中修改集合,而是修改完后再重新添加到集合中

2.使用并发迭代器,如 ConcurrentHashMap 提供的 keySet()、values() 和 entrySet() 方法返回的迭代器,这些迭代器是线程安全的。

3.使用 Java 8 引入的 forEach 方法(由于 forEach 方法是在集合的副本上操作的,所以不会抛出 ConcurrentModificationException。)

1.6. Map遍历方式

重点记住:迭代器,流

//1.通过键遍历
Map<String, Integer> map = new HashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);

Set<String> keys = map.keySet();
for (String key : keys) {
    System.out.println("Key: " + key + ", Value: " + map.get(key));
}
//2.通过值遍历
for (Integer value : map.values()) {
    System.out.println("Value: " + value);
}
//3.通过键值对遍历
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
//4.迭代器:
Iterator<String> keyIterator = map.keySet().iterator();
while (keyIterator.hasNext()) {
    String key = keyIterator.next();
    System.out.println("Key: " + key + ", Value: " + map.get(key));
}
//5.Java 8+ 流式遍历
map.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value));

//6.对于ConcurrentHashMap,可以使用Java 8的parallelStream()方法进行并行遍历
map.parallelStream().forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value));

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值