江门网站推广策划,官方网站开发招标须知,网站正在维护中啥意思,北京网站设计制作费用什么是HashMap
#xff08;1#xff09;HashMap 是基于 Map 接口的非同步实现#xff0c;线程不安全#xff0c;是为了快速存取而设计的#xff1b;它采用 key-value 键值对的形式存放元素#xff08;并封装成 Node 对象#xff09;#xff0c;允许使用 null 键和 nul…什么是HashMap
1HashMap 是基于 Map 接口的非同步实现线程不安全是为了快速存取而设计的它采用 key-value 键值对的形式存放元素并封装成 Node 对象允许使用 null 键和 null 值但只允许存在一个键为 null并且存放在 Node[0] 的位置不过允许存在多个 value 为 null 的情况。
2在 JDK7 及之前的版本HashMap 的数据结构可以看成“数组链表”在 JDK8 及之后的版本数据结构可以看成数组链表红黑树也就是说 HashMap 底层采用数组实现数组的每个位置都存储一个单向链表当链表的长度超过一定的阈值时就会转换成红黑树。转换的目的是当链表中元素较多时也能保证HashMap的存取效率
在 Java 8 中HashMap 在内部使用了一个数组来存储键值对。当 HashMap 中的元素个数超过了一个阈值默认为容量的0.75倍并且某个桶数组索引位置中的元素个数超过了一个阈值默认为8这个桶中的链表会转换成红黑树。这种转换是为了提高在链表长度较长时的查找、插入和删除操作的性能因为红黑树的时间复杂度是 O(log n)而链表的时间复杂度是 O(n)。通过将链表转换为红黑树可以显著减少在大型哈希表中的查找时间。需要注意的是并非所有的链表都会在达到阈值时转换为红黑树。转换条件是链表的长度超过了一个阈值默认为8。这是因为链表的长度较小时使用链表进行查找操作仍然具有较好的性能。当哈希表中的元素数量减少时红黑树也会在特定条件下重新转换为链表以节省内存空间。总而言之当 HashMap 中的某个桶中的链表长度超过阈值时默认为8该链表会被转换为红黑树以提高查找、插入和删除操作的性能。当元素数量减少时红黑树也会在特定条件下重新转换为链表。这种转换是为了在特定情况下优化 HashMap 的性能。3HashMap 有两个影响性能的关键参数“初始容量”和“加载因子”
容量 capacity就是哈希表中数组的数量默认初始容量是16容量必须是2的N次幂这是为了提高计算机的执行效率。
加载因子 loadfactor在 HashMap 扩容之前容量可以达到多满的程度默认值为 0.75
扩容阈值 threshold capacity * loadfactor4采用 Fail-Fast 机制底层通过一个 modCount 值记录修改的次数对 HashMap 的修改操作都会增加这个值。迭代器在初始过程中会将这个值赋给 exceptedModCount 在迭代的过程中如果发现 modCount 和 exceptedModCount 的值不一致代表有其他线程修改了Map就立刻抛出异常。
Hashmap扩容
在 Java 中HashMap 的扩容操作是在添加元素时完成的。当添加元素后如果当前 HashMap 的元素数量超过了负载因子默认为 0.75与容量的乘积时就会触发扩容操作。
扩容操作会创建一个新的数组并将原来的所有元素重新分配到新的数组中。在重新分配元素时需要重新计算每个元素在新数组中的位置这是通过对元素的哈希值进行重新计算得到的。如果计算出来的新位置上已经有元素存在那么就会发生冲突需要解决冲突。解决冲突的方式和初始创建 HashMap 时一样使用链表或红黑树进行存储。
需要注意的是扩容操作比较耗费时间因为需要重新计算每个元素在新数组中的位置并且需要遍历原来的链表或红黑树来重新分配元素。因此为了提高性能可以通过在创建 HashMap 时设置初始容量来减少扩容的次数。
另外HashMap 的扩容操作是线程不安全的。如果多个线程同时进行扩容操作可能会导致数据丢失或者数据不一致等问题。因此在多线程环境下使用 HashMap 时需要进行正确的同步处理或者使用线程安全的 ConcurrentHashMap。
综上所述HashMap 的扩容操作是在添加元素时完成的当元素数量超过负载因子乘以容量时就会触发扩容。在扩容时需要重新计算每个元素在新数组中的位置并且需要重新分配元素解决冲突这个过程比较耗费时间。在多线程环境下使用 HashMap 时需要进行正确的同步处理或者使用线程安全的 ConcurrentHashMap。Hash冲突
在 HashMap 中当发生hash冲突时解决方式是采用拉链法也就是将所有哈希值相同的记录都放在同一个链表中除此之外解决hash冲突的方式有
开放地址法线性探测再散列、二次探测再散列、伪随机探测再散列当冲突发生时在散列表中形成一个探测序列沿此序列逐个单元地查找直到找到给定的关键字或者碰到一个开放的地址为止即该地址单元为空。如果是插入的情况在探查到开放的地址则可将待插入的新结点存入该地址单元如果是查找的情况探查到开放的地址则表明表中无待查的关键字即查找失败。
再哈希法产生冲突时使用另外的哈希函数计算出一个新的哈希地址、直到冲突不再发生
建立一个公共溢出区把冲突的记录都放在另一个存储空间不放在表里面。ConcurrentHashMap 原理JDK8
1、ConcurrentHashMap 的实现原理 在 JDK8 及以上的版本中ConcurrentHashMap 的底层数据结构依然采用“数组链表红黑树”但是在实现线程安全性方面抛弃了 JDK7 版本的 Segment分段锁的概念而是采用了 synchronized CAS 算法来保证线程安全。在ConcurrentHashMap中大量使用 Unsafe.compareAndSwapXXX 的方法这类方法是利用一个CAS算法实现无锁化的修改值操作可以大大减少使用加锁造成的性能消耗。这个算法的基本思想就是不断比较当前内存中的变量值和你预期变量值是否相等如果相等则接受修改的值否则拒绝你的而操作。因为当前线程中的值已经不是最新的值你的修改很可能会覆盖掉其他线程修改的结果。
ConcurrentHashMap扩容
ConcurrentHashMap 在扩容方面和 HashMap 有些不同。在 ConcurrentHashMap 中同样是在添加元素时进行扩容操作但是扩容操作是由多个线程同时进行的。这是因为 ConcurrentHashMap 使用了分段锁Segment将整个哈希表分成多个小的哈希表段每个小的哈希表段都有一个独立的锁。
在 ConcurrentHashMap 中当需要扩容时不会对整个哈希表进行扩容而是对其中的一个小的哈希表段进行扩容。扩容过程仅需要对当前哈希表段上的锁进行加锁其他哈希表段的数据可以继续被访问和修改这样就实现了并发扩容。
另外为了避免在扩容期间出现数据不一致的情况ConcurrentHashMap 在扩容时使用了一种叫做“分裂”的技术即将当前哈希表段中的所有元素复制到新的哈希表段中而不是像 HashMap 那样重新计算每个元素在新数组中的位置。在复制元素时如果某个元素需要移动到新的哈希表段中就需要在新哈希表段中重新计算该元素在新数组中的位置并将该元素放置到正确的位置上。这个过程中需要进行同步处理以保证数据的一致性。
在 ConcurrentHashMap 中分段锁和分裂技术的使用使得扩容操作可以并发地进行并且保证了在扩容期间数据的一致性。这些特性使得 ConcurrentHashMap 在高并发环境下具有优秀的性能表现。
综上所述ConcurrentHashMap 在扩容方面和 HashMap 有些不同采用了分段锁和分裂技术使得扩容操作可以并发地进行并且保证了数据的一致性。这些特性使得 ConcurrentHashMap 在高并发环境下有着优秀的性能表现。区别
HashMap
HashMap 是 Java 中最常用的非线程安全的哈希表实现。它允许键和值为 null并且允许存在重复的值但不允许重复的键。在单线程环境下HashMap 的性能很好可以快速进行插入、查找和删除操作。
然而在多线程环境下由于 HashMap 不是线程安全的当多个线程同时对其进行修改时可能会导致数据不一致、循环等问题。
ConcurrentHashMap
ConcurrentHashMap 是 Java 中线程安全的哈希表实现。它保证了在多线程环境下的安全性并且相比 HashMap 在多线程环境下有着更好的性能表现。ConcurrentHashMap 通过使用分段锁Segment来实现并发访问。每个 Segment 其实就是一个小的 HashMap它只锁定当前操作的那一部分数据而不是整个表。
ConcurrentHashMap 在读取操作上不需要加锁因此能够实现并发的高效读取。在写入操作上只有涉及到的 Segment 被锁定而其他部分仍然可以被并发访问这样就大大提高了并发写入的性能。
参考链接Java集合篇HashMap 与 ConcurrentHashMap 原理总结