HashMap与HashSet的不安全问题

HashSet的不安全问题

  • HashSet与ArrayList一样也存在不安全的问题,当多线程时也会出现ConcurrentModificationException,并发修改异常
  • 需要提出解决方案

问题

public static void main(String[] args) {
    Set<Integer> set = new HashSet<>();
    for (int i = 0; i < 30;i++) {
        int finalI = i;
        new Thread(()->{
            set.add(finalI);
            System.out.println(set);
        },String.valueOf(i)).start();
    }
}

解决方案

Set set = Collections.synchronizedSet(new HashSet());

  • 这种方式是使用工具类的方式,而不是使用JUC下的内容

Set set = new CopyOnWriteArraySet<>();

  • 与ArryList类似

  • 看看CopyOnWriteArraySet的add方法

    • private final CopyOnWriteArrayList<E> al;
      
      /**
      * Creates an empty set.
       */
      public CopyOnWriteArraySet() {
          al = new CopyOnWriteArrayList<E>();
      }
      
      public boolean add(E e) {
          return al.addIfAbsent(e);
      }
      
    • public boolean addIfAbsent(E e) {
          Object[] snapshot = getArray();
          return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
              addIfAbsent(e, snapshot);
      }
      
      /**
       * A version of addIfAbsent using the strong hint that given
       * recent snapshot does not contain e.
       */
      private boolean addIfAbsent(E e, Object[] snapshot) {
          final ReentrantLock lock = this.lock;
          lock.lock();
          try {
              Object[] current = getArray();
              int len = current.length;
              if (snapshot != current) {
                  // Optimize for lost race to another addXXX operation
                  int common = Math.min(snapshot.length, len);
                  for (int i = 0; i < common; i++)
                      if (current[i] != snapshot[i] && eq(e, current[i]))
                          return false;
                  if (indexOf(e, current, common, len) >= 0)
                          return false;
              }
              Object[] newElements = Arrays.copyOf(current, len + 1);
              newElements[len] = e;
              setArray(newElements);
              return true;
          } finally {
              lock.unlock();
          }
      }
      
    • 从上述可以看出

    • 当创建一个CopyOnwriteArraySet时,其实就是创建了一个ArrayList

    • 因为addIfAbsent这个类用来重入锁ReetrantLock,所以他可以实现同步,并发执行

  • 为什么可以使用

  • 在这里插入图片描述

聊聊HashSet的底层

  • 是通过Map实现的,Map的key是唯一的,所以将Map的key作为HashSet的值,这样就可以实现唯一性质
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

HashMap的不安全问题

  • HashMap与上述几个一样,也存在不安全的问题
HashMap<String, Integer> map = new HashMap<>();

for (int i = 0; i < 10; i++) {
    int finalI = i;
    new Thread(()->{
        map.put(String.valueOf(finalI)+"==>", finalI);
    },String.valueOf(i)).start();
    System.out.println(map);
}
  • 会出现ConcurrentModificationException,并发修改异常

解决方案

Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>());

Map<String, Integer> map = new ConcurrentHashMap<>();

在这里插入图片描述

  • 上述是类之间的关系

HashMap的底层是什么样子的?

HashMap 的底层实现基于哈希表(Hash Table)结构,并结合了 数组 和 链表 或红黑树

  1. 数组HashMap 内部维护一个数组(通常称为“桶”或“槽”),数组的每个元素都是一个链表或红黑树的根节点。数组的大小通常是 2 的幂次方,这是为了优化哈希运算中的位运算。
  2. 哈希函数:当向 HashMap 中添加元素时,会使用哈希函数计算键(Key)的哈希值。这个哈希值会被用来确定元素应该存储在哪个桶中。通常,哈希值会对数组的长度取模(hash % array.length),以确定桶的索引
  3. 链表或红黑树:当两个或更多的键具有相同的哈希值时(这称为哈希冲突),它们会被存储在同一个桶中的链表里。在 Java 8 中,当链表长度超过一定阈值(默认为 8)时,链表会转换为红黑树,以提高查询效率。当树的大小小于一定阈值(默认为 6)时,又会退化为链表
  4. 扩容:当 HashMap 中的元素数量超过数组大小的一定比例(负载因子,默认为 0.75)时,HashMap 会进行扩容,即创建一个新的、更大的数组,并将现有元素重新哈希到新数组中。扩容是一个相对昂贵的操作,因为它涉及到重新计算所有元素的哈希值和重新分配元素。
  5. 哈希值优化:为了减少哈希冲突和提高性能,HashMap 的键对象需要实现 hashCode()equals() 方法。hashCode() 方法应返回键对象的哈希码,而 equals() 方法则用于比较两个键对象是否相等。

这是HashMap底层的数组【桶】

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

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}
  • 29
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值