前言:

几乎所有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. vue知识点----element UI+vue关于日期范围选择的操作,picker-options属性的使用

    需求场景如下: 指定起止日期,后选的将会受到先选的限制 不同的日期选择器,不过也存在关联关系 实现方法不难,利用了 change 事件,动态改变 picker-options 中的 disableDa ...

  2. DC-3靶机

    仅供个人娱乐 靶机信息 下载地址:http://www.five86.com/downloads/DC-3.zip 一.主机扫描 arp-scan -l nmap -A -p 1-65535 -sV ...

  3. 图解 HTTP 连接管理

    熟悉我的小伙伴都知道,我之前肝了本<HTTP 核心总结>的 PDF,这本 PDF 是取自我 HTTP 系列文章的汇总,然而我写的 HTTP 相关内容都是一年前了,我回头看了一下这本 PDF ...

  4. 【Android】真机调试新姿势:无线连接

    由于工作需要,需要无线连接手机调试,特意百度了一下 在进行Android开发时,一般我们都是用usb线把手机和电脑连接起来进行调试工作.但如果你觉得这样不够酷的话,可以尝试一下无线连接,颇简单,GO! ...

  5. 如何从二维平面n个点中寻找距离最近两个点?

    如何理解分治算法 什么是分治算法?简单来说就是"分而治之",也就是将原问题划分成n个规模较小的,并且结构与原问题相似的子问题,然后去递归地解决这些子问题,最后再合并其结果,就得到原 ...

  6. 涨姿势啦!Java程序员装X必备词汇之对象标记Mark Word!

    大家好,我是庆哥Java,一个专注于干货分享的Java自学者! 写在前面 如果你已经知道什么是Mark Word,那我也希望你都好好阅读下本篇文章,因为你有可能发现不一样的切入点来帮助你更加深入的了解 ...

  7. ssrf漏洞随笔

    一.ssrf漏洞定义 SSRF漏洞:SSRF是一 种由攻击者构造请求,由服务端发起请求的安全漏洞.一般情况下,ssrf攻击的目标是外网无法访问的内部系统 SSRF漏洞( 服务器端请求伪造 )也被称为跨 ...

  8. 这才是做了五年Android开发该有的样子!

    程序员工作五年后一般怎样了? 五年程序员生涯对身体上的摧残就不说了,来讲讲一般会有怎样的状态吧! 优秀的一般是这样:有着明确的职业目标与规划,热爱技术,五年的工作沉淀,技术能力得到了飞速提升,每天依然 ...

  9. CentOS帮助类语法

    目录 一.man获取帮助信息 二.help获得shell内置命令的帮助信息 三.history查看所有命令历史 补充:Linux常用快捷键 一.man获取帮助信息 基本语法:man [命令或配置文件] ...

  10. 能够进行多段文本匹配的NFA改良算法

    下面的代码基于NFA算法实现了在多段字符串中匹配正则表达式,对比NFA算法可以看到它将pc由局部变量提升为类成员,以保存中间匹配状态,另外在匹配成功后将pc恢复到null状态.实际使用中,此类还应该增 ...