【转】Java学习---HashMap和HashSet的内部工作机制

【原文】https://www.toutiao.com/i6593863882484220430/

HashMap和HashSet的内部工作机制

HashMap 和 HashSet 内部是如何工作的?散列函数(hashing function)是什么?

HashMap 不仅是一个常用的数据结构,在面试中也是热门话题。

Q1. HashMap 如何存储数据?

A1. 以键/值对(key/value)形式存储。你可以使用键(key)来存、取值。

Q2. HashMap 查询时间的复杂度是怎样的?

A2. 是O(n) = O(k * n)。如果 hashCode() 方法能向下面讨论的那样把数据分散到桶(bucket)中,那么平均是O(1)。

Q3. HashMap 内部是如何存储数据的?

A3. HashMap 使用后台数组(backing array)作为桶,并使用链表(linked list)存储键/值对。

桶的后台数组:如下所示

1)使用键(key)和值(value)将一个对象放入 map 中时,会隐式调用 hashCode() 方法,返回哈希值(hash code value),比如 123。两个不同的键能够返回一样的哈希值。良好的哈希算法(hashing algorithm)能够将数值分散开。在上面的例子中,我们假设 (“John”,01/01/1956) 的键和 (“Peter”, 01/01/1995) 的键返回相同的哈希值,都是123

2)当返回一个 hashCode,例如是 123,初始的 HashMap 容量为 10,它如何知道存储到后台数组(backing array)的哪个索引(index)呢?HashMap 内部会调用 hash(int ) 和 indexFor(int h, int length) 方法。这被称为哈希函数(hashing function)。

简要解释下这个函数:

hashCode() % capacity

123 % 10 = 3

456 % 10 = 6

这表示,“hashCode = 123”存储在备份数组的索引3上。

容量为 10 的情况下,你可能得到的数字在 09 之间。

一旦 HashMap 达到容量的 75%,也就是哈希因子(hash factor)默认值 0.75,后台数组(backing array)的容量就会加倍,发生重散列(rehashing)为新的 20 的容量重新分配桶。

hashCode() % capacity

123 % 20 = 3

456 % 20 = 16

上面重散列的取模方法有一个缺陷。如果 hashCode 是负数会怎样?负索引可不是你想要的。因此,一个改进的哈希公式会移出符号位,然后再用取模(即 %)运算符计算剩余部分。

(123 & 0x7FFFFFFF) % 20 = 3

(456 & 0x7FFFFFFF) % 20 = 16

这确保你得到的索引值为正数。如果你查看 Java 8 的 HashMap 源码,它的实现使用以下方法:

a). 通过只抽取重要的低位,来防止不良离散值(poorer hashes)。

b). 根据哈希码hashCode)和容量capacity),来决定索引(index)。

实际的名称值对(name value pairs)作为一个键/值对存储在 LinkedList 中。

如上图所示,键/值对以链表形式存储。两个不同的键可以产生一样的 hashCode,例如123,并存储在同一个 bucket 中,理解这点至关重要。例如,上面例子中的 “John, 01/01/1956” 和 “Peter, 01/01/1995“ 。你如何只检索 “John, 01/01/1956” 呢?此时你的 key 所属类的 equals() 方法会被调用。它遍历 bucket 为 “123” 的 LinkedList 中的每个条目,使用 equals() 方法找到并检索出键为 “John, 01/01/1956” 的条目。这就是在你的类中实现 hashCode()equals() 方法重要性的原因。如果你使用一个现有的包装类,如 Integer 或 String 作为键,它们已经实现了这两个方法。如果你使用自己写的类作为键,如 “John, 01/01/1956” 这样含有名字和出生日期属性的“MyKey”,你有责任正确地实现这些方法。

Q5. 为什么恰当地设置 HashMap 的初始容量(initial capacity)是最佳实践?

A5. 这样可以减少重散列的发生。

Q6. HashSet 内部如何存储数据?

A6. HashSet 内部使用 HashMap 。它将元素存储为键和值。(译者注:HashSet 把存储的值作为 key)

Q7. 为 Object 实现了一个糟糕的 hashcode() 会有什么影响?

A7. 不同的对象调用 hashCode() 方法应该返回不同的值。如果不同的对象返回相同的值,会导致更多的键/值对存储在同一个 bucket 中。这会降低 HashMap 和 HashSet 的性能

原文地址:https://www.cnblogs.com/ftl1012/p/9569103.html

时间: 2025-01-17 07:31:30

【转】Java学习---HashMap和HashSet的内部工作机制的相关文章

java学习-----jvm的内存分配及运行机制

VM运行时数据区域: 根据<Java虚拟机规范(第二版)>的规定,JVM包括下列几个运行时区域: 我们思考几个问题: 1.jVM是怎么运行的? 2.JVM运行时内存是怎么分配的? 3.我们写的java代码(类,对象,方法,常量,变量等等)最终存放在哪个区? VM运行时数据区域: 1.程序计数器(program Counter Register):   是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的

Java容器 HashMap与HashSet的学习

Java学习中,看到HashMap,HashSet类,本着不止要停留在用的层面( 很多公司面试都要问底层 ),学习了JDK源码,记录下笔记. 源码来自jdk1.7下的src.zip HashMap是一种键值对类型,它提供一种Key-Value对应保存的数据结构,实现了Map接口,其中key的值唯一,即一个key某一时刻只能映射到唯一的值. 看其中几个成员(没列全) static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 sta

Java学习——HashMap

遍历 Map map = new HashMap(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object key = entry.getKey(); Object val = entry.getValue(); 例 import java.util.HashMap; import java.util.Iterat

Java学习-HashMap练习

1.使用如下键值对,初始化一个HashMap:adc - 物理英雄apc - 魔法英雄t - 坦克对这个HashMap进行反转,key变成value,value变成key 注:keySet()可以获取所有的key, values()可以获取所有的value 1 package Collection; 2 3 import java.util.ArrayList; 4 import java.util.HashMap; 5 import java.util.List; 6 7 public cla

java遍历hashMap、hashSet、Hashtable

一.遍历HashMap Map<Integer, String> map = new HashMap<Integer, String>(); 方法一:效率高 for(Entry<Integer, String> entry:map.entrySet()){ System.out.println(entry.getKey()+"="+entry.getValue()); } 方法二: for (Integer key : map.keySet()) {

JAVA学习第六十四课 — 反射机制

   Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制,简单说:能动态获取类中的信息(类中所有),就是java的反射,也可以理解为对类的解剖 反射机制的基本应用场景:    比如一个程序的应用程序(TomCat),为了提高其扩展性,会对外暴露一个接口,在外部定义一个类实现这个接口,但是在应用程序内部无法new对象,所以应用程序会提供一个配置

Java学习----你可以知道对象的工作结果(获取方法的返回值)

1.写返回类型 2.return 返回值 3.定义变量接受返回值 public class App2 { public String [] print(String msg, int num) { for (int i = 0; i < num; i++) { System.out.println(msg); } return new String[]{"aaa","bbb"}; } public static void main(String[] args)

java学习笔记(3):java的工作原理及相关基础

一.运行机制 如上图所示,图中内容即为Java的运行机制: 1.我们一开始所编写的代码文件存储格式为(如text.java)文件,这就是源程序文件 2.在Java编辑器的作用下,也就是就行了编译,形成字节码文件(如text.class)文件,其实,这个Java编译器是我们所说的Java虚拟机,其中,在命令行的处理方式如下: 要在当前文件目录下打开命令行进行编译 3.之后class文件经过类装载器.字节码校验器.解释器的处理,就变成了1010010……的二进制形式了,通过操作系统平台将结果显示在屏

JAVA学习第三十六课(常用对象API)- 集合框架(四)— Set集合:HashSet集合演示

随着Java学习的深入,感觉大一时搞了一年的ACM,简直是明智之举,Java里很多数据结构.算法类的东西,理解起来就轻松多了 Set集合下有两大子类开发常用 HashSet集合 .TreeSet集合 Set集合的元素是不重复且无序 一.HashSet集合 API文档解释:此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持.它不保证 set 的迭代顺序:特别是它不保证该顺序恒久不变.此类允许使用null 元素. 此类为基本操作提供了稳定性能,注意,此实现不是同步的. 由上可