前言:

几乎所有java程序员都用过hashMap,但会用不一定会说。

近年来hashMap是非常常见的面试题,如何为自己的回答加分?需要从理解开始。

“你用过hashMap吗?”,“怎么用的?”,“为什么要用?”

这些问题是面试中经常碰到的,相信大部分的猿哥都能回答,“用过”,然后回答hashMap的一些特性,如HashMap是以键值对存储,可以接受null键值和值,很快但不安全等等。但是后面的问题来了:

“你知道HashMap的工作原理吗?”,“他的get()方法是怎么实现的?”

到这里有些猿哥可能说不出来,有些知道原理但不能自信的表达。

正题:

hashMap原理,为什么说不仅仅是hashMap,因为我们需要从其他的知识点切入。

(1)不仅仅是hashMap:==和equals

在 Java 中比较两个对象是否相等主要是通过 ==号,比较的是他们的内存地址。而equals()方法则是继承自Object类中的,默认也是通过==来实现的。所以在没有重写Object的equals()方法的前提下,比较两个对象使用==和equals将会返回同样的结果。

前面了解到==比较的是内存地址,那么我只是想比较值是否相同该怎么办?

这里我们用常用String类来举例,例如比较如下两个字符串是否相同String a = "Hello" 和 String b = new String("Hello"),这里的相同有两种情形,是要比较 a 和 b 是否是同一个对象(内存地址是否相同),还是比较它们的内容是否相等?这个具体需要怎么区分呢?

如果使用 == 那么就是比较它们在内存中是否是同一个对象,但是 String 对象的默认父类也是 Object,所以默认的equals方法比较的也是内存地址,所以我们要重写 equals方法,正如 String 源码中所写的那样。

public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

这样当我们 a == b时是判断 a 和 b 是否是同一个对象,a.equals(b)则是比较 a 和 b 的内容是否相同,这应该很好理解。当然java中还有很多类都重写的equals()方法,这里不赘述。

(2)不仅仅是hashMap:hashCode()

简单了解:hashCode()方法返回对象的哈希码值,它的实现主要是为了提高哈希表(例如java.util.Hashtable提供的哈希表)的性能。Object类中默认定义了该方法为不同的对象返回不同的整形值,有人说这个值就是由类的内存地址转换而来,但是也有人证实它仅仅只是一个随机数,whatever!我们为什么要知道类的内存地址呢,是吧?

Object 类的 hashCode方法有一个通用约定,从注释中可以看出:

/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
* <p>
* The general contract of {@code hashCode} is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the {@code hashCode} method
* must consistently return the same integer, provided no information
* used in {@code equals} comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
* <li>If two objects are equal according to the {@code equals(Object)}
* method, then calling the {@code hashCode} method on each of
* the two objects must produce the same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the {@code hashCode} method on each of the
* two objects must produce distinct integer results. However, the
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hash tables.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class {@code Object} does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the
* Java&trade; programming language.)
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
public native int hashCode();

大概意思就是:

<li>在应用程序的执行期间,只要对象的 equals 方法的比较操作所用到的信息没有被修改,那么对同一个对象的多次调用,hashCode 方法都必须始终返回同一个值。

<li>如果两个对象根据 equals 方法比较是相等的,那么调用这两个对象中的 hashCode 方法都必须产生同样的整数结果。

<li>如果两个对象根据 equals 方法比较是不相等的,那么调用者两个对象中的 hashCode 方法,则不一定要求 hashCode 方法必须产生不同的结果。但是给不相等的对象产生不同的整数散列值,是有可能提高散列表(hash table)的性能。

所以,为了满足上面第二点,当我们重写对象equals()方法时也需要重写 hashCode()方法?

重点了解:为什么重写对象equals()方法时也需要重写 hashCode()方法?举例!

我们自定义一个学生类并重写equals()方法,但不重写hashCode()方法,然后比较两个对象,如下代码

public class Student {

    private String name;

    private String age;

    public Student(String name,String age){
this.name = name;
this.age = age;
} @Override
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof Student) {
Student anotherStudent = (Student) anObject; if (this.getName() == anotherStudent.getName()
|| this.getAge() == anotherStudent.getAge())
return true;
}
return false;
}
public static void main(String[] args){
Student student1 = new Student("小明", "12岁");
Student student2 = new Student("小明", "12岁"); System.out.println("equals结果:" + student1.equals(student2));
System.out.println("对象1的散列值:" + student1.hashCode() + ",对象2的散列值:" + student2.hashCode());
}
equals结果:true
对象1的散列值:1555009629,对象2的散列值:41359092 Process finished with exit code 0

很明显,结果违背了第二条约定。试想String类如果只是重写equals方法而不重写hashCode方法会发生什么?在接下来的hashMap中回答这个问题。

HashMap的工作原理

HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,然后找到bucket位置来储存值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。

当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。

上图便是我们通常说的hashMap是以数组+链表的形式存储数据,其中每一个方框代表一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体,以此来解决Hash冲突的问题。所以在bucket中即存储了value也存储了key。

当我们用get()方法获取值时,首先调用key对象的hashCode方法找到bucket位置,如果bucket上存储了多个对象,则会遍历链表通过equals方法找到对应的key所对应的value返回。

如果String类只是重写equals方法而不重写hashCode方法

那么当使用new String("s") 作为 Key 然后 put 一个值,但是再根据 new String("s") 去 Get 的时候就会得到一个null的结果,因为他们的hashCode不相同,显然这不是我们想看到的,所以String对象重写equals()方法时也需要重写 hashCode()方法。同时这也是为什么我们经常用String作为key的原因,因为String类是final的,这样可以保证它的hashCode不会改变。

总结:

当我们理解了==,equals以及hashCode后,理解hashMap的get()方法的原理应该是分分钟的事了。而对于put()方法,我们需要理解它是通过计算key的hashCode将Entry实体存储到对应bucket位置,实体包括hashCode,key,value,next。如果同一个位置发生碰撞就存储在链表的下一个节点。

当然很多面试中还会涉及比如比较hashMap和hashTable之类的,这边简单说一个小技巧方便大家理解,像hashMap和hashTable的区别,ArrayList和Vector的区别的问题记住一点,常用的一般是快的,而快的原因是因为它是异步的,而异步就会有线程安全问题。所以hashMap和ArrayList效率相对高,异步且线程不安全。hashTable和Vector相率相对低,同步且线程安全。

好了,说到这里希望本文能让大家对java中的 ==,equals,hashCode,hashMap有一个比较全面的理解,在今后面试过程中能够从理解出发,自信表达。

Java HashMap工作原理:不仅仅是HashMap的更多相关文章

  1. Java HashMap工作原理及实现

    Java HashMap工作原理及实现 2016/03/20 | 分类: 基础技术 | 0 条评论 | 标签: HASHMAP 分享到:3 原文出处: Yikun 1. 概述 从本文你可以学习到: 什 ...

  2. HashMap工作原理的介绍!

    HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道HashTable和HashMap之间的区别,那么为何这道面试题如此 ...

  3. Java LinkedHashMap工作原理及实现

    Java LinkedHashMap工作原理及实现 原文出处: Yikun 1. 概述 在理解了#7 介绍的HashMap后,我们来学习LinkedHashMap的工作原理及实现.首先还是类似的,我们 ...

  4. Java虚拟机工作原理详解 (一)

    一.类加载器 首先来看一下java程序的执行过程. 从这个框图很容易大体上了解java程序工作原理.首先,你写好java代码,保存到硬盘当中.然后你在命令行中输入 javac YourClassNam ...

  5. Java虚拟机工作原理详解

    原文地址:http://blog.csdn.net/bingduanlbd/article/details/8363734 一.类加载器 首先来看一下java程序的执行过程. 从这个框图很容易大体上了 ...

  6. Java虚拟机工作原理具体解释

    一.类载入器 首先来看一下java程序的运行过程. 从这个框图非常easy大体上了解java程序工作原理.首先,你写好java代码,保存到硬盘其中.然后你在命令行中输入 javac YourClass ...

  7. Java虚拟机工作原理

    Java虚拟机工作原理 首先我想从宏观上介绍一下Java虚拟机的工作原理.从最初的我们编写的Java源文件(.java文件)是如何一步步执行的,如下图所示,首先Java源文件经过前端编译器(javac ...

  8. java复习要点(一)------- java语言的特点、java的工作原理、配置环境变量、java命令的使用

    一.java语言的特点: (1)简单并面向对象 (2)鲁棒并安全: java语言在编译及运行程序时,都要进行严格的检查,防止不匹配问题的发生.如果引用一个非法类型,或执行一个非法类型操作,java减肥 ...

  9. 全面解读Java NIO工作原理(4)

    全面解读Java NIO工作原理(4) 2011-12-14 10:31 Rollen Holt Rollen Holt的博客 我要评论(0) 字号:T | T JDK 1.4 中引入的新输入输出 ( ...

  10. 全面解读Java NIO工作原理(3)

    全面解读Java NIO工作原理(3) 2011-12-14 10:31 Rollen Holt Rollen Holt的博客 我要评论(0) 字号:T | T JDK 1.4 中引入的新输入输出 ( ...

随机推荐

  1. SQL之case when then用法_之二

    select CustomerNo, Name, Sex, Birthday, IDType, IDNo, validityday, case (null ) when '1' then '高级VIP ...

  2. jsp-->js-->jsp之间的关系

    jsp和js通过form.submit();发送request请求createdIdSave.jsp 在CreatedIdSave.jsp中进行BL的增删改查操作,在jsp中将值保存到页面的scrip ...

  3. 为什么大家都在用WebRTC?

    WebRTC代表网络实时通信.它是一种非常令人兴奋,强大且具有高度破坏性的尖端技术和标准.自从WebRTC诞生以来,80%的浏览器都开始支持它.有数据显示,2017年~2021年期间,WebRTC市场 ...

  4. 双击映射坚果云网盘并打开的AHK源代码

    双击映射坚果云网盘并打开的AHK源代码 #SingleInstance,force ;当此脚本已经运行时自动替换旧实例再次运行.#Persistent ;让脚本持久运行(即直到用户关闭或遇到 Exit ...

  5. 背单词(AC自动机+线段树+dp+dfs序)

    G. 背单词 内存限制:256 MiB 时间限制:1000 ms 标准输入输出 题目类型:传统 评测方式:文本比较   题目描述 给定一张包含N个单词的表,每个单词有个价值W.要求从中选出一个子序列使 ...

  6. Top10 应用都在用的Android最新知识

    相信各位对Jetpack并不陌生,自从 Google 在 2018 年推出 Jetpack 之后,它就成为了 Android 未来发展的指向标. 作为一名安卓工程师,我们以前会经常吐槽Android碎 ...

  7. Python3中dict字典的相关操作函数

    字典对象的内建函数 1. clear() 清空字典. 例: >>> a = {1:3, 2:4} >>> a.clear() >>> a {} 2 ...

  8. jupyter notebook使用python虚拟环境

    jupyter指定环境启动 背景 系统环境中安装有jupyter及相关库,因项目需要,新建了一个torch相关的虚拟环境并安装了对应的内容, 此时,想通过系统中的jupyter启动一个可以应用虚拟环境 ...

  9. CNVD-2021-14536 锐捷 RG-UAC 统一上网行为管理审计系统信息泄露漏洞

    漏洞简介 锐捷 RG-UAC 统一上网行为管理审计系统存在信息泄露,攻击者通过网页源代码可间接获取管理用户账号密码,登录管理后台. 漏洞复现 fofa搜索以下关键字 title="RG-UA ...

  10. 能说会道爱办公——“别人家的”Chrome插件到底怎么做

    根据相关数据显示,谷歌的Chrome浏览器目前已达近七成的市场占有率,成为浏览器的"霸主".大家选择Chrome,除了是因为性能的优越以及强大的兼容性之外,Chrome充足的扩展插 ...