首页 > 其他分享 >Map

Map

时间:2023-05-07 21:35:04浏览次数:33  
标签:Map HashMap 链表 异或 线程 数组 null

Map

map 线程安全方式 k/v为null 数据结构(1.8) 扩容机制 迭代器
HashMap 不安全 均可 数组+链表+红黑树 初始16,扩容2倍 容器本身
ConcurrentHashMap 锁分段+CAS NPE 数组+链表+红黑树 初始16,扩容2倍 容器的克隆
Hashtable synchronized NPE 数组+链表 初始11,扩容2倍加1 容器的克隆
  • 扩容机制:三者均是在插入元素后判断是否需要扩容
  • 迭代机制:均使用fail-safe迭代器
    • ConcurrentHashMap和Hashtable是基于容器的克隆进行迭代,不会抛出ConcurrentModificationException异常,但是也不能反映出容器的最新状态;HashMap使用了fail-fast迭代器,它直接对容器进行迭代,因此如果在迭代过程中有其他线程修改了容器结构(增加或删除元素),就会抛出ConcurrentModificationException异常。

HashMap

  • HashMap的hash算法:HashMap使用了一个自定义的hash函数,它将key的hashCode值的高16位和低16位进行异或运算,然后再与数组长度减一进行按位与运算,得到数组下标。这样做的目的是为了增加hash值的随机性,减少哈希冲突的概率。
  • HashMap的扩容机制:HashMap在插入元素后,会判断当前元素个数是否超过了数组长度乘以负载因子(默认为0.75)的阈值,如果超过了,就会触发扩容操作。扩容操作会将数组长度翻倍,并重新计算每个元素的位置,然后重新插入到新的数组中。这个过程是比较耗时的,所以建议在创建HashMap时,尽量预估容量,避免频繁扩容。
  • HashMap的链表转红黑树:HashMap在JDK1.8中引入了一种优化措施,当某个数组位置上的链表长度超过了8(TREEIFY_THRESHOLD),并且数组长度大于等于64(MIN_TREEIFY_CAPACITY)时,就会将链表转换为红黑树。这样可以提高查找效率,因为红黑树是一种平衡二叉树,它的查找时间复杂度是O(logn),而链表的查找时间复杂度是O(n)。反之,如果某个数组位置上的红黑树节点个数小于等于6(UNTREEIFY_THRESHOLD),就会将红黑树退化为链表。
  • HashMap的线程不安全问题:HashMap是线程不安全的,如果在多线程环境下对它进行并发修改,可能会导致一些问题,比如死循环、数据丢失、数据不一致等。这是因为多线程同时修改HashMap时,可能会造成hash冲突、扩容竞争、链表环形等情况。为了解决这个问题,可以使用同步包装类Collections.synchronizedMap()或者并发类ConcurrentHashMap来代替HashMap。
  • HashMap和Hashtable的区别:Hashtable是一个早期的散列表实现类,它和HashMap有很多相似之处,但也有一些区别。主要区别有:Hashtable是线程安全的,而HashMap是线程不安全的;Hashtable不允许使用null作为键或值,而HashMap允许;Hashtable使用Enumeration接口作为迭代器,而HashMap使用Iterator接口作为迭代器;Hashtable的初始容量是11,扩容后是原来的2倍加1,而HashMap的初始容量是16,扩容后是原来的2倍。

HashMap线程不安全的问题:

数据丢失:当多个线程同时对同一个HashMap进行put操作时,如果其中一个线程触发了扩容,那么其他线程可能会看到一个不一致的状态,导致某些元素没有被正确复制到新的数组中,或者被其他线程覆盖掉。
死循环:当多个线程同时对同一个HashMap进行put操作时,如果其中一个线程触发了链表转换为红黑树的操作,那么其他线程可能会看到一个不完整的树结构,导致在遍历或者插入时陷入无限循环。

HashMap的死锁:

HashMap的put操作是这样导致死锁的:当HashMap的容量达到阈值时,它会进行扩容,即重新分配一个更大的数组,并将原来的元素重新计算哈希值并插入到新的数组中。这个过程中,它使用了头插法,即将新的元素插入到链表的头部。如果在多线程环境下,两个线程同时对同一个链表进行扩容操作,可能会导致链表的循环或者断裂。例如,假设有两个线程A和B,它们同时对一个链表进行扩容操作,如下图所示1:

  1. 线程A先执行transfer方法,将原来的链表分成两部分,一部分是1->2->3->null,另一部分是4->null。
  2. 线程A将1->2->3->null插入到新的数组中,此时新数组的第一个位置是3->2->1->null。
  3. 线程B开始执行transfer方法,由于它还没有看到线程A的修改,它仍然认为原来的链表是1->2->3->4->null。
  4. 线程B将1->2->3->4->null插入到新的数组中,此时新数组的第一个位置是4->3->2->1->null。
  5. 线程A继续执行transfer方法,将4->null插入到新的数组中,由于它使用了头插法,它将4插入到了3的前面,此时新数组的第一个位置是4->4->3->2->1->null。注意这里出现了两个4,并且形成了一个循环链表。
  6. 当其他线程对这个位置进行get操作时,就会陷入死循环1。

ConcurrentHashMap

ConcurrentHashMap是Java5中支持高并发,高吞吐量的线程安全的HashMap实现

  • 其由Segment数据结构和HashEntry数组构成
  • egment结构与HashMap类似,是一个数组和链表结构
  • 一个Segment包含一个HashEntry数组,每个HashEntry是一个链表,HashEntry用于存储键值对数据
  • Segment在ConcurrentHashMap中扮演锁的角色,当修改HashEntry数据时都需要获取对应Segment的锁
  • 个别方法是需要锁整个map,比如size(),containsValue(),实现上按顺序锁所有桶,操作之后按顺序释放所有桶

ConcurrentHashMap为什么不支持key或者value为null?

ConcurrentHashMap不支持key或者value为null的原因是为了避免二义性和保证线程安全。

  • 如果get方法返回null,无法判断是没有找到对应的key还是存储的值就是null。

  • 在多线程环境下,使用null作为key或者value可能会导致空指针异常或者数据不一致。

  • ConcurrentHashMap的设计者Doug Lea不喜欢null

    • 理由:如果map.get(key)返回null,你无法判断是key映射到null还是key不存在。在非并发的map中,你可以通过map.contains(key)来检查,但是在并发的map中,map可能在两次调用之间发生了变化。

Hashtable

其所有方法都是同步的,因此是线程安全的,每次操作数据锁的范围是整个map

  • 拉链法实现的hash表,其通过数据+链表的方式存储的数据
  • 内部refresh方法来扩容,当键值对数量超过阈值时调用
  • 扩容阈值:默认为键值对数量 > Entry数组长度 * 0.75,默认大小11
  • 扩容大小:newCapacity = oldCapacity * 2 + 1

扩展

异或运算

异或运算是一种二进制运算,它的特点是:

  • 异或运算符号是“^”或“⊕”,英文是exclusive OR,缩写为XOR。
  • 异或运算的结果是:如果两个输入位不同,输出为1;如果两个输入位相同,输出为0。
  • 异或运算具有以下性质:
    • 归零律:x ^ x = 0
    • 恒等律:x ^ 0 = x
    • 交换律:x ^ y = y ^ x
    • 结合律:x ^ (y ^ z) = (x ^ y) ^ z
    • 自反性:x ^ y ^ y = x

异或运算的使用场景有:

  • 简化计算:多个值的异或运算可以根据运算性质进行简化,例如 a ^ b ^ c ^ a ^ b = c。
  • 交换值:两个变量连续进行三次异或运算,可以互相交换值,例如 x = x ^ y; y = y ^ x; x = x ^ y; 就可以实现 x 和 y 的交换,不需要额外的空间。
  • 加密解密:异或运算可以用于加密解密,因为异或运算具有自反性,即明文与密钥进行两次异或运算,就可以还原成明文。例如 text ^ key = cipherText; cipherText ^ key = text。
  • 数据备份:异或运算可以用于数据备份,因为异或运算具有归零律和恒等律。例如 x ^ y = z; 则 x ^ z = y; y ^ z = x。这样就可以利用一个备份文件 z 来恢复任意一个损坏的文件 x 或 y。
  • 算法题目:异或运算可以用于一些面试的算法题目,例如找出数组中缺失的数字、重复的数字、只出现一次的数字等。

标签:Map,HashMap,链表,异或,线程,数组,null
From: https://www.cnblogs.com/z-dk/p/17380204.html

相关文章

  • js基础---对象的序列化(JSON)与map
    序列化概念json工具类就是那个转换字符串的方法调用json静态方法,不需要new。注意事项将对象转换为json后再转换为对象,相当于做了一次深复制。当对象的字符串key属性满足不了需求时,可用map的对象属性作为keymap属性和方法map与数组之间的转换......
  • Mybatis-Plus基本CRUD——通用Mapper
    BaseMapper接口MyBatis-Plus中的基本CRUD在内置的BaseMapper中都已得到了实现,我们可以直接使用,接口如下:/***Mapper继承该接口后,无需编写mapper.xml文件,即可获得CRUD功能*<p>这个Mapper支持id泛型</p>**@authorhubin*@since2016-01-23*/publicinter......
  • springboot集成下,mybatis的mapper代理对象究竟是如何生成的
    springboot集成下,mybatis的mapper代理对象究竟是如何生成的 前情回顾Mybatis源码解析-mapper代理对象的生成,你有想过吗,我们讲到了mybatis操作数据库的流程:先创建SqlSessionFactory,然后创建SqlSession,然后再创建获取mapper代理对象,最后利用mapper代理对象完成数据库......
  • hashmap oop in golang
    packagemainimport("fmt")constHASH_BUCKET_SIZE=3//1023typehash_nodestruct{keyinterface{}valinterface{}next*hash_node}typeHASH_BUCKET[HASH_BUCKET_SIZE]*hash_nodefunchash(keyinterface{})int{h......
  • C++实现一个线程安全的map
    本文是使用ChatCPT生成的,最终的代码使用起来没问题。代码是通过两轮对话完善的,后面把对话合并后跑不出理想效果就没尝试了。第一轮对话请求c++11实现一个线程安全的map,使用方法与std::map保持一致,实现[]运算符回复以下是一个简单的线程安全的map实现,可以使用[]运算符来访问和......
  • golang hashmap
    packagemainimport("fmt")constHASH_BUCKET_SIZE=3//1023typehash_nodestruct{keyinterface{}valinterface{}next*hash_node}//hashbuckettosavenodeaddressvarhash_bucket[HASH_BUCKET_SIZE]*hash_nodefunc......
  • RUL预测常用数据集--C-MAPSS Dataset介绍
    C-MAPSS是针对航空发动机剩余寿命预测的数据集。该数据集由NASA(美国国家航空航天局)发布,包含了四个不同类型的航空发动机的传感器数据,以及相应的故障模式和剩余寿命数据,如表1所示。表1InformationoftheC-MAPSSdataset.DatasetFD001FD002FD003FD004Engineunit......
  • HashMap设置初始容量一直都用错了?
    1背景今天在代码审查的时候,发现一位离职的同事留下了这样一串代码:Map<String,String>map=newHashMap<>((int)(list.size()/0.75F+1));第一反应是:又在炫技,又在搞这些花里胡哨的东西。但是看到0.75的我却陷入了沉思,稍微深入了解过Map的应该都知道,Map中有个属性,叫做负载因......
  • 【jmap】jmap命令详情
    简介1、jmap能够打印给定Java进程、核心文件或远程DEBUG服务器的共享对象内存映射或堆内存的详细信息。2、如果给定的进程运行在64位虚拟机上,则必须指定-J-d64选项,例如jmap-J-d64-heappid。3、jmap可能在未来的JDK版本中删除。可用于内存溢出,泄露等情况的内存分析使用......
  • 简单说说HashMap和LinkedHashMap的区别
    HashMap和LinkedHashMap的区别我们知道HashMap的变量顺序是不可预测的,这意味着便利的输出顺序并不一定和HashMap的插入顺序是一致的。这个特性通常会对我们的工作造成一定的困扰。为了实现这个功能,我们可以使用LinkedHashMap。LinkedHashMap详解先看下LinkedHashMap的定义:pu......