求java里面的Hash<Map>的用法和基本解释,谢谢

求java里面的Hash<Map>的用法和基本解释,谢谢,第1张

HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类。虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至 HashSet 本身就采用 HashMap 来实现的。

通过 HashMap、HashSet 的源代码分析其 Hash 存储机制

实际上,HashSet 和 HashMap 之间有很多相似之处,对于 HashSet 而言,系统采用 Hash 算法决定集合元素的存储位置,这样可以保证能快速存、取集合元素;对于 HashMap 而言,系统 key-value 当成一个整体进行处理,系统总是根据 Hash 算法来计算 key-value 的存储位置,这样可以保证能快速存、取 Map 的 key-value 对。

在介绍集合存储之前需要指出一点:虽然集合号称存储的是 Java 对象,但实际上并不会真正将 Java 对象放入 Set 集合中,只是在 Set 集合中保留这些对象的引用而言。也就是说:Java 集合实际上是多个引用变量所组成的集合,这些引用变量指向实际的 Java 对象。

集合和引用

就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并不是真正的把 Java 对象放入数组中,只是把对象的引用放入数组中,每个数组元素都是一个引用变量。

HashMap 的存储实现

当程序试图将多个 key-value 放入 HashMap 中时,以如下代码片段为例:

Java代码

HashMap<String , Double> map = new HashMap<String , Double>();

mapput("语文" , 800);

mapput("数学" , 890);

mapput("英语" , 782);

HashMap 采用一种所谓的“Hash 算法”来决定每个元素的存储位置。

当程序执行 mapput("语文" , 800); 时,系统将调用"语文"的 hashCode() 方法得到其 hashCode 值——每个 Java 对象都有 hashCode() 方法,都可通过该方法获得它的 hashCode 值。得到这个对象的 hashCode 值之后,系统会根据该 hashCode 值来决定该元素的存储位置。

我们可以看 HashMap 类的 put(K key , V value) 方法的源代码:

Java代码

public V put(K key, V value)

{

// 如果 key 为 null,调用 putForNullKey 方法进行处理

if (key == null)

return putForNullKey(value);

// 根据 key 的 keyCode 计算 Hash 值

int hash = hash(keyhashCode());

// 搜索指定 hash 值在对应 table 中的索引

int i = indexFor(hash, tablelength);

// 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素

for (Entry<K,V> e = table[i]; e != null; e = enext)

{

Object k;

// 找到指定 key 与需要放入的 key 相等(hash 值相同

// 通过 equals 比较放回 true)

if (ehash == hash && ((k = ekey) == key

|| keyequals(k)))

{

V oldValue = evalue;

evalue = value;

erecordAccess(this);

return oldValue;

}

}

// 如果 i 索引处的 Entry 为 null,表明此处还没有 Entry

modCount++;

// 将 key、value 添加到 i 索引处

addEntry(hash, key, value, i);

return null;

}

上面程序中用到了一个重要的内部接口:MapEntry,每个 MapEntry 其实就是一个 key-value 对。从上面程序中可以看出:当系统决定存储 HashMap 中的 key-value 对时,完全没有考虑 Entry 中的 value,仅仅只是根据 key 来计算并决定每个 Entry 的存储位置。这也说明了前面的结论:我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。

上面方法提供了一个根据 hashCode() 返回值来计算 Hash 码的方法:hash(),这个方法是一个纯粹的数学计算,其方法如下:

Java代码

static int hash(int h)

{

h ^= (h >>> 20) ^ (h >>> 12);

return h ^ (h >>> 7) ^ (h >>> 4);

}

对于任意给定的对象,只要它的 hashCode() 返回值相同,那么程序调用 hash(int h) 方法所计算得到的 Hash 码值总是相同的。接下来程序会调用 indexFor(int h, int length) 方法来计算该对象应该保存在 table 数组的哪个索引处。indexFor(int h, int length) 方法的代码如下:

Java代码

static int indexFor(int h, int length)

{

return h & (length-1);

}

这个方法非常巧妙,它总是通过 h &(tablelength -1) 来得到该对象的保存位置——而 HashMap 底层数组的长度总是 2 的 n 次方,这一点可参看后面关于 HashMap 构造器的介绍。

当 length 总是 2 的倍数时,h & (length-1) 将是一个非常巧妙的设计:假设 h=5,length=16, 那么 h & length - 1 将得到 5;如果 h=6,length=16, 那么 h & length - 1 将得到 6 ……如果 h=15,length=16, 那么 h & length - 1 将得到 15;但是当 h=16 时 , length=16 时,那么 h & length - 1 将得到 0 了;当 h=17 时 , length=16 时,那么 h & length - 1 将得到 1 了……这样保证计算得到的索引值总是位于 table 数组的索引之内。

根据上面 put 方法的源代码可以看出,当程序试图将一个 key-value 对放入 HashMap 中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但 key 不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部——具体说明继续看 addEntry() 方法的说明。

当向 HashMap 中添加 key-value 对,由其 key 的 hashCode() 返回值决定该 key-value 对(就是 Entry 对象)的存储位置。当两个 Entry 对象的 key 的 hashCode() 返回值相同时,将由 key 通过 eqauls() 比较值决定是采用覆盖行为(返回 true),还是产生 Entry 链(返回 false)。

上面程序中还调用了 addEntry(hash, key, value, i); 代码,其中 addEntry 是 HashMap 提供的一个包访问权限的方法,该方法仅用于添加一个 key-value 对。下面是该方法的代码:

Java代码

void addEntry(int hash, K key, V value, int bucketIndex)

{

// 获取指定 bucketIndex 索引处的 Entry

Entry<K,V> e = table[bucketIndex]; // ①

// 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry

table[bucketIndex] = new Entry<K,V>(hash, key, value, e);

// 如果 Map 中的 key-value 对的数量超过了极限

if (size++ >= threshold)

// 把 table 对象的长度扩充到 2 倍。

resize(2 tablelength); // ②

}

上面方法的代码很简单,但其中包含了一个非常优雅的设计:系统总是将新添加的 Entry 对象放入 table 数组的 bucketIndex 索引处——如果 bucketIndex 索引处已经有了一个 Entry 对象,那新添加的 Entry 对象指向原有的 Entry 对象(产生一个 Entry 链),如果 bucketIndex 索引处没有 Entry 对象,也就是上面程序①号代码的 e 变量是 null,也就是新放入的 Entry 对象指向 null,也就是没有产生 Entry 链。

JDK 源码

在 JDK 安装目录下可以找到一个 srczip 压缩文件,该文件里包含了 Java 基础类库的所有源文件。只要读者有学习兴趣,随时可以打开这份压缩文件来阅读 Java 类库的源代码,这对提高读者的编程能力是非常有帮助的。需要指出的是:srczip 中包含的源代码并没有包含像上文中的中文注释,这些注释是笔者自己添加进去的。

Hash 算法的性能选项

根据上面代码可以看出,在同一个 bucket 存储 Entry 链的情况下,新放入的 Entry 总是位于 bucket 中,而最早放入该 bucket 中的 Entry 则位于这个 Entry 链的最末端。

上面程序中还有这样两个变量:

size:该变量保存了该 HashMap 中所包含的 key-value 对的数量。

threshold:该变量包含了 HashMap 能容纳的 key-value 对的极限,它的值等于 HashMap 的容量乘以负载因子(load factor)。

从上面程序中②号代码可以看出,当 size++ >= threshold 时,HashMap 会自动调用 resize 方法扩充 HashMap 的容量。每扩充一次,HashMap 的容量就增大一倍。

上面程序中使用的 table 其实就是一个普通数组,每个数组都有一个固定的长度,这个数组的长度就是 HashMap 的容量。HashMap 包含如下几个构造器:

HashMap():构建一个初始容量为 16,负载因子为 075 的 HashMap。

HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 075 的 HashMap。

HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。

当创建一个 HashMap 时,系统会自动创建一个 table 数组来保存 HashMap 中的 Entry,下面是 HashMap 中一个构造器的代码:

Java代码

// 以指定初始化容量、负载因子创建 HashMap

public HashMap(int initialCapacity, float loadFactor)

{

// 初始容量不能为负数

if (initialCapacity < 0)

throw new IllegalArgumentException(

"Illegal initial capacity: " +

initialCapacity);

// 如果初始容量大于最大容量,让出示容量

if (initialCapacity > MAXIMUM_CAPACITY)

initialCapacity = MAXIMUM_CAPACITY;

// 负载因子必须大于 0 的数值

if (loadFactor <= 0 || FloatisNaN(loadFactor))

throw new IllegalArgumentException(

loadFactor);

// 计算出大于 initialCapacity 的最小的 2 的 n 次方值。

int capacity = 1;

while (capacity < initialCapacity)

capacity <<= 1;

thisloadFactor = loadFactor;

// 设置容量极限等于容量 负载因子

threshold = (int)(capacity loadFactor);

// 初始化 table 数组

table = new Entry[capacity]; // ①

init();

}

上面代码中粗体字代码包含了一个简洁的代码实现:找出大于 initialCapacity 的、最小的 2 的 n 次方值,并将其作为 HashMap 的实际容量(由 capacity 变量保存)。例如给定 initialCapacity 为 10,那么该 HashMap 的实际容量就是 16。

程序①号代码处可以看到:table 的实质就是一个数组,一个长度为 capacity 的数组。

对于 HashMap 及其子类而言,它们采用 Hash 算法来决定集合中元素的存储位置。当系统开始初始化 HashMap 时,系统会创建一个长度为 capacity 的 Entry 数组,这个数组里可以存储元素的位置被称为“桶(bucket)”,每个 bucket 都有其指定索引,系统可以根据其索引快速访问该 bucket 里存储的元素。

无论何时,HashMap 的每个“桶”只存储一个元素(也就是一个 Entry),由于 Entry 对象可以包含一个引用变量(就是 Entry 构造器的的最后一个参数)用于指向下一个 Entry,因此可能出现的情况是:HashMap 的 bucket 中只有一个 Entry,但这个 Entry 指向另一个 Entry ——这就形成了一个 Entry 链。如图 1 所示:

图 1 HashMap 的存储示意

HashMap 的读取实现

当 HashMap 的每个 bucket 里存储的 Entry 只是单个 Entry ——也就是没有通过指针产生 Entry 链时,此时的 HashMap 具有最好的性能:当程序通过 key 取出对应 value 时,系统只要先计算出该 key 的 hashCode() 返回值,在根据该 hashCode 返回值找出该 key 在 table 数组中的索引,然后取出该索引处的 Entry,最后返回该 key 对应的 value 即可。看 HashMap 类的 get(K key) 方法代码:

Java代码

public V get(Object key)

{

// 如果 key 是 null,调用 getForNullKey 取出对应的 value

if (key == null)

return getForNullKey();

// 根据该 key 的 hashCode 值计算它的 hash 码

int hash = hash(keyhashCode());

// 直接取出 table 数组中指定索引处的值,

for (Entry<K,V> e = table[indexFor(hash, tablelength)];

e != null;

// 搜索该 Entry 链的下一个 Entr

e = enext) // ①

{

Object k;

// 如果该 Entry 的 key 与被搜索 key 相同

if (ehash == hash && ((k = ekey) == key

|| keyequals(k)))

return evalue;

}

return null;

}

从上面代码中可以看出,如果 HashMap 的每个 bucket 里只有一个 Entry 时,HashMap 可以根据索引、快速地取出该 bucket 里的 Entry;在发生“Hash 冲突”的情况下,单个 bucket 里存储的不是一个 Entry,而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),那系统必须循环到最后才能找到该元素。

归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据 Hash 算法来决定其存储位置;当需要取出一个 Entry 时,也会根据 Hash 算法找到其存储位置,直接取出该 Entry。由此可见:HashMap 之所以能快速存、取它所包含的 Entry,完全类似于现实生活中母亲从小教我们的:不同的东西要放在不同的位置,需要时才能快速找到它。

当创建 HashMap 时,有一个默认的负载因子(load factor),其默认值为 075,这是时间和空间成本上一种折衷:增大负载因子可以减少 Hash 表(就是那个 Entry 数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的的 *** 作(HashMap 的 get() 与 put() 方法都要用到查询);减小负载因子会提高数据查询的性能,但会增加 Hash 表所占用的内存空间。

掌握了上面知识之后,我们可以在创建 HashMap 时根据实际需要适当地调整 load factor 的值;如果程序比较关心空间开销、内存比较紧张,可以适当地增加负载因子;如果程序比较关心时间开销,内存比较宽裕则可以适当的减少负载因子。通常情况下,程序员无需改变负载因子的值。

如果开始就知道 HashMap 会保存多个 key-value 对,可以在创建时就使用较大的初始化容量,如果 HashMap 中 Entry 的数量一直不会超过极限容量(capacity load factor),HashMap 就无需调用 resize() 方法重新分配 table 数组,从而保证较好的性能。当然,开始就将初始容量设置太高可能会浪费空间(系统需要创建一个长度为 capacity 的 Entry 数组),因此创建 HashMap 时初始化容量设置也需要小心对待。

哈希表是一种键值映射的数据结构。哈希表中,数据以数组格式存储,其中每个数据值都有自己唯一的索引值,索引值通过哈希表的哈希函数计算得到。

下面两步将键哈希值转化成哈希表的索引值。

有限长度下的哈希表,冲突不可避免。解决冲突的两种方法, 拉链法 开放寻址

拉链法

将冲突位置的元素构造成链表。添加数据发生冲突时,将元素追加到链表。如下图,当添加 "Sandra Dee"时,计算出索引值为152与“John Smith” 发生冲突,然后将它追加到链表。

开放寻址

以当前冲突位置为起点,按照一定规则探测空位置把元素插进去。比较简单的方式是 线性探测 ,它会按照固定的间隔(通常是1)循环进行查找。

如下图,“Sandra Dee”添加时与 “John Smith” 相冲突,通过探测空位置插入到153,然后添加“Ted Baker”发现与“Sandra Dee”相冲突,往后探测154空位置插入。

负载因子

负载因子的值是 条目数 占用 哈希桶 比例,当负载因子超过理想值时,哈希表会进行扩容。比如哈希表理想值 075,初始容量 16,当条目超过 12 后哈希表会进行扩容 重新哈希 。06 和 075 是通常合理的负载因子。

影响哈希表性能的两个主要因素

下图展示了随着负载因子增加,缓存丢失的数量开始上升,08后开始迅速攀升。

关于 HashMap 解读一下它的 hash 方法和冲突树化两个地方。

关于hash()

取key的hashCode值,然后将高16位与低16位进行异或、最后取模运算。

高16位与低16位进行异或是为了 加大低位的随机性

关于随机性,网上有个测试例子:他随机选取了352个字符串,测试不同长度数组下的碰撞概率。

结果显示,当HashMap数组长度为 2^9 = 512 的时候,直接取hashCode冲突103次,进行高低异或后冲突92次。

冲突树化

HashMap解决冲突使用 拉链法 。jdk18 中,当一个桶链表节点超过 TREEIFY_THRESHOLD=8 后,链表会转换为红黑树,当桶中节点移除或重新哈希少于 UNTREEIFY_THRESHOLD=6 时,红黑树会转变为普通的链表。

链表取元素是从头结点一直遍历到对应的结点,时间复杂度是O(N) ,红黑树基于二叉树结构,时间复杂度为O(logN) ,所以当元素个数过多时,用红黑树存储可以提高搜索的效率。但是单个树节点需要占用的空间大约是普通节点的两倍,所以使用树和链表是时空权衡的结果。

树化阀值为什么是 8 ?

HashMap 文档有这么一段描述。大体意思是,哈希桶上的链表节点数量呈现 泊松分布

什么是 泊松分布

泊松分布就是描述某段时间内,事件具体的发生概率。柏松分布可以通过平均数估算出某个事件的出现概率。

比如,一个程序员每天平均写3个Bug,表示为 。由此还可以得到下面:

假设HashMap有 条数据,负载因子为 ,那么HashMap长度最小值为 ,最大约为 (容量必须是 2 的幂) 所以平均值是 ,每个桶的平均节点数量为

HashMap 默认负载因子为 075,所以每个桶的平均节点数量 05,代入柏松公式得到下面数据

树化 是哈希极度糟糕下不得已而为之的做法,而一个桶出现 8 个节点的概率不到千万分之一,所以将TREEIFY_THRESHOLD=8 。

哈希表是一种键值映射的数据结构。解决冲突有两种方法 拉链法 开放寻址 。合理设置负载因子和初始容量避免过多的扩容 *** 作和缓存丢失。 理解HashMap的 hash 方法和冲突树化。

public static void main(String[] args) {

int array[]={5, 10, 10, 5, 2, 5, 3, 5, 10, 5, 2, 5, 5, 10, 1, 5, 1};

Arrayssort(array);// 给数组排序

int count=0;

int tmp=array[0];

Map map=new HashMap();

for(int i=0; i < arraylength; i++) {

if(tmp != array[i]) {

tmp=array[i];

count=1;

} else {

count++;

}

mapput(array[i], count);

}

map=sortByValue(map);

Set<Integer> key = mapkeySet();

for (Iterator it = keyiterator(); ithasNext();) {

Integer s = (Integer) itnext();

Systemoutprintln(s+"出现了"+mapget(s));

}

}

public static Map sortByValue(Map map) {

List list=new LinkedList(mapentrySet());

Collectionssort(list, new Comparator() {

// 将链表按照值得从小到大进行排序

public int compare(Object o1, Object o2) {

return ((Comparable)((MapEntry)(o2))getValue())compareTo(((MapEntry)(o1))getValue());

}

});

Map result=new LinkedHashMap();

for(Iterator it=listiterator(); ithasNext();) {

MapEntry entry=(MapEntry)itnext();

resultput(entrygetKey(), entrygetValue());

}

return result;

}

key-value:HashMap是key-value形式存储的数据结构,key、value都可以为null,但是key只可以有一个null,因为Map的key是不允许重复的。

默认容量:如果在new HashMap的时候没有设置默认值,那么默认容量就是16,如果设置了默认值,那么默认容量就是传入值的向上最接近2的幂等的值。

例如:HashMap map = new HashMap(3); 默认容量就是4;

hash冲突:hashMap是通过链地址法解决hash冲突,多次hash降低hash冲突。

线程安全:hashMap在并发的时候会有数据丢失,因为多线程去运行,put的时候值会被覆盖,扩容的时候也是不安全的,并发的时候链表会形成死循环。所以hashMap是线程不安全的。

数据结构:hashMap底层数据结构是数组+链表或者数组+红黑树。链表长度大于等于8并且数组长度大于等于64时,链表会转换为红黑树,链表长度小于6时,红黑树会转换为链表。链表长度限制为小于6,也是为了避免链表和红黑树之间频繁转换。

自动扩容:hashMap的扩容因子是075,当HashMap中的元素个数超过数组大小的075倍时,就会调用resize()方法进行数组扩容,每次扩容的容量都是之前容量的2倍。

首次put时会触发扩容(算是初始化),然后存入数据,然后判断是否需要扩容。

不是首次put,则不再初始化,直接存入数据,然后判断是否需要扩容。

Collections中提供了一个方法synchronizedMap()

为了避免HashMap的线程安全问题引出了ConcurrentHashMap。

ConcurrentHashMap是由数组、单向链表、红黑树组成的,默认长度是16。key-value都不支持null。

当数组长度大于64并且链表长度大于等于8时,单向链表会转为红黑树,链表长度小于6,红黑树会退化成单向链表。

hach冲突:ConcurrentHashMap是通过链地址法来解决hash冲突的。

并发安全的主要实现是通过对指定的Node节点加锁,来保证数据更新的安全性。

ConcurrentHashMap在性能优化方面主要体现在两点

CAS全称为Compare and swap 比较替换。

假设有三个 *** 作数,内存值V,旧的预期值A,要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值A修改为B并返回true,否则什么都不做。当然cas要与volatile变量配合使用,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,用于是一个不会变的值A,只要某次CAS *** 作失败,永远都不可能成功。

17采用数组+单链表,扩容采用头插法,并发情况下链表闭环,采用分段锁,采用的锁是Reentrantlock。

18采用数组+红黑树,扩容采用尾插法,采用Node节点,采用的锁是Synchronized+node。

fail-fast:是多线程并发 *** 作集合时的一种失败处理机制,就是最快的实际能把错误抛出而不是让程序执行。

人人都有一个进大厂的梦,希望以上的内容能够对你我这样的人有所帮助。

HashMap,中文名哈希映射,HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。HashMap数组每一个元素的初始值都是Null。

HashMap是基于哈希表的 Map 接口的实现。此实现提供所有可选的映射 *** 作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

扩展资料:

因为HashMap的长度是有限的,当插入的Entry越来越多时,再完美的Hash函数也难免会出现index冲突的情况。

HashMap数组的每一个元素不止是一个Entry对象,也是一个链表的头节点。每一个Entry对象通过Next指针指向它的下一个Entry节点。当新来的Entry映射到冲突的数组位置时,只需要插入到对应的链表即可。

参考资料来源:

百度百科-Hashmap

基于hashing的原理,jdk8后采用数组+链表+红黑树的数据结构。我们通过put和get存储和获取对象。当我们给put()方法传递键和值时,先对键做一个hashCode()的计算来得到它在bucket数组中的位置来存储Entry对象。当获取对象时,通过get获取到bucket的位置,再通过键对象的equals()方法找到正确的键值对,然后在返回值对象。

当数组table的size达到阙值时即++size > load factor capacity 时,也是在putVal函数中。

扩容需要重新分配一个新数组,新数组是老数组的2倍长,然后遍历整个老结构,把所有的元素挨个重新hash分配到新结构中去。

对key的hashCode进行hashing,与运算计算下标获取bucket位置,如果在桶的首位上就可以找到就直接返回,否则在树中找或者链表中遍历找,如果有hash冲突,则利用equals方法去遍历链表查找节点。

对key的hashCode做hash *** 作,与高16位做异或运算。

还有平方取中法,除留余数法,伪随机数法。

因为数组位置的确定用的是与运算,仅仅最后四位有效,设计者将key的哈希值与高16为做异或运算使得在做&运算确定数组的插入位置时,此时的低位实际是高位与低位的结合,增加了随机性,减少了哈希碰撞的次数。

会产生哈希碰撞,若key值相同则替换旧值,不然链接到链表后面,链表长度超过阙值8就转为红黑树存储。

HashCode相同,通过equals比较内容获取值对象。

超过阙值会进行扩容 *** 作,概括的讲就是扩容后的数组大小是原数组的2倍,将原来的元素重新hashing放入到新的散列表中去。

相同点:都是存储key-value键值对的

不同点:

loadFactor表示HashMap的拥挤程度,影响hash *** 作到同一个数组位置的概率。默认loadFactor等于075,当HashMap里面容纳的元素已经达到HashMap数组长度的75%时,表示HashMap太挤了,需要扩容,在HashMap的构造器中可以定制loadFactor。

JDK 18 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 18 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。但是链表大于8的概率是非常非常低的。

选择Integer,String这种不可变的类型,像对String的一切 *** 作都是新建一个String对象,对新的对象进行拼接分割等,这些类已经很规范的覆写了hashCode()以及equals()方法。作为不可变类天生是线程安全的。如果要使用自定义类做为Key,就需要重写hashCode()以及equals()方法。红黑树在做比较的时候使用的是SystemidentityHashCode()方法,是不需要做特殊处理的。

更多内容戳这里(整理好的各种文集)

HashMap是基于哈希表的Map接口的非同步实现。实现HashMap对数据的 *** 作,允许有一个null键,多个null值。

HashMap底层就是一个数组结构,数组中的每一项又是一个链表。数组+链表结构,新建一个HashMap的时候,就会初始化一个数组。Entry就是数组中的元素,每个Entry其实就是一个key-value的键值对,它持有一个指向下一个元素的引用,这就构成了链表,HashMap底层将key-value当成一个整体来处理,这个整体就是一个Entry对象。HashMap底层采用一个Entry数组来保存所有的key-value键值对,当需要存储一个Entry对象时,会根据hash算法来决定在其数组中的位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry对象时,也会根据hash算法找到其在数组中的存储位置, 在根据equals方法从该位置上的链表中取出Entry;

put: (key-value)方法是HashMap中最重要的方法,使用HashMap最主要使用的就是put,get两个方法。

判断键值对数组table[i]是否为空或者为null,否则执行resize()进行扩容;

根据键值key计算hash值得到插入的数组索引 i ,如果table[i] == null ,直接新建节点添加即可,转入6,如果table[i] 不为空,则转向3;

判断table[i] 的首个元素是否和key一样,如果相同(hashCode和equals)直接覆盖value,否则转向4;

判断table[i] 是否为treeNode,即table[i]是否为红黑树,如果是红黑树,则直接插入键值对,否则转向5;

遍历table[i] , 判断链表长度是否大于8,大于8的话把链表转换成红黑树 ,进行插入 *** 作,否则进行链表插入 *** 作;便利时遇到相同key直接覆盖value;

插入成功后,判断实际存在的键值对数量size是否超过了threshold,如果超过,则扩容;

也可参考HashSetput过程:

>

以上就是关于求java里面的Hash<Map>的用法和基本解释,谢谢全部的内容,包括:求java里面的Hash<Map>的用法和基本解释,谢谢、理解HashMap底层树化和Hash方法、怎么从一个数组中找出重复最多的元素,并统计重复个数等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/9497553.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-29
下一篇2023-04-29

发表评论

登录后才能评论

评论列表(0条)

    保存