系统运维系列 之HashMap底层实现原理和应用

Posted 琅晓琳

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了系统运维系列 之HashMap底层实现原理和应用相关的知识,希望对你有一定的参考价值。

1 介绍

1HashMapJava中最常用的集合类框架,也是Java语言中非常典型的数据结构;
(2HashMap是基于哈希表的Map接口的非同步实现。元素以键值对的形式存放,并且允许null键和null值,因为key值唯一(不能重复),因此,null键只有一个;
(3HashMap不保证元素存储的顺序;
(4HashMap是线程不安全的。

2 HashMap的默认负载因子是0.75,而不是0.5或者是整数1呢?

临界值(threshold) = 负载因子(loadFactor) * 容量(capacity)
loadFactor是负载因子,表示HashMap满的程度,默认值为0.75f,也就是说默认情况下,当HashMap中元素个数达到了容量的3/4的时候就会进行自动扩容。

//code
void addEntry(int hash, K key, V value, int bucketIndex) 
	if ((size >= threshold) && (null != table[bucketIndex])) 
		resize(2 * table.length);
		hash = (null != key) ? hash(key) : 0;
		bucketIndex = indexFor(hash, table.length); 
	
	createEntry(hash, key, value, bucketIndex);


HashMap其实是底层基于哈希函数实现的,但是哈希函数都有如下一个基本特性:根据同一哈希函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同。
两个不同的输入值,根据同一散列函数计算出的散列值相同的现象叫做碰撞。

为了避免哈希碰撞,HashMap需要在合适的时候进行扩容;默认的负载因子(0.75)在时间和空间成本之间提供了很好的权衡。更高的值减少了空间开销,但增加了查找成本。

//总结:1)负载因子不能太大,不然会导致大量的哈希冲突或者哈希碰撞;也不能太小,那样会浪费空间;
(2)阈值(threshold) = 负载因子(loadFactor) x 容量(capacity) 根据HashMap的扩容机制,他会保证容量(capacity)的值永远都是2的幂;为了保证负载因子x容量的结果是一个整数,这个值是0.75(4/3)比较合理,因为这个数和任何2的次幂乘积结果都是整数。

3 jdk8中HashMap为什么要引入红黑树?

1)解决jdk1.8以前hash冲突所导致的链化严重的问题,因为链表结构的查询效率是非常低的,他不像数组,能通过索引快速找到想要的值,链表只能挨个遍历,当hash冲突非常严重的时候,链表过长的情况下,就会严重影响查询性能;
(2)本身散列列表最理想的查询效率为O(1),当时链化后链化特别严重,他就会导致查询退化为O(n)为了解决这个问题所以jdk8中的HashMap添加了红黑树来解决这个问题,当链表长度>=8的时候链表就会变成红黑树。

4 HashMap和Hashtable计算hash的方法不同?

1HashMap计算hash:对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取模。
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
static int hash(int h) 
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
 
 static int indexFor(int h, int length) 
        return h & (length-1);
 2Hashtable计算hash:直接使用key的hashcode对table数组的长度直接进行取模。
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

5 HashMap 多线程操作导致死循环问题?

1)当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小;
(2)在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。

HashMap可以通过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap);

Hashtablesynchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁;
ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性。

6 HashMap如何保存两个key相同的数据?

IdentityhashMap类利用哈希表实现Map接口,比较键(和值)时使用引用相等性代替对象相等性,也就是说key(value)比较的时候只比较两个key是否引用同一个对象,比较的是对象的地址。

一般Key的选择应为String等类型的数据,尽量不要使用引用数据类型。

参考资料:
https://blog.csdn.net/fengxi_tan/article/details/106629280 HashMap底层实现原理
https://blog.csdn.net/qq_29909965/article/details/78358066 hashmap出现重复key的情况
https://blog.csdn.net/qq_33732195/article/details/108121285?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-108121285-blog-78358066.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-108121285-blog-78358066.pc_relevant_default&utm_relevant_index=2 HashMap如何保存两个key相同的数据

以上是关于系统运维系列 之HashMap底层实现原理和应用的主要内容,如果未能解决你的问题,请参考以下文章

系统运维系列 之实现servlet上传下载文件(java应用)

系统运维系列 之实现Ftp上传下载文件(java应用)

系统运维系列 之Socket和ServerSocket的简单介绍(java应用)

系统运维系列 之异常抛出后代码执行问题(java应用)

系统运维系列 之其它流概述及其分类(java应用)

系统运维系列 之堆栈理解(java应用)