从头认识java-15.7 Map(4)-介绍HashMap的工作原理-hash碰撞(经常作为面试题)

这一章节我们来讨论一下hash碰撞。

1.什么是hash碰撞?

就是两个对象的key的hashcode是一样的,这个时候怎么get他的value呢?

答案是通过equals遍历table那个位置上面的Entry链表。

2.例子

正常的例子:

package com.ray.ch14;

import java.util.HashMap;

public class Test {
	public static void main(String[] args) {
		HashMap<Person, Dog> map = new HashMap<Person, Dog>();
		Person person_1 = new Person();
		person_1.setHeight(180);
		person_1.setId(1);
		person_1.setName("person_1");
		Person person_2 = new Person();
		person_2.setHeight(180);
		person_2.setId(2);
		person_2.setName("person_1");
		Dog dog_1 = new Dog();
		dog_1.setId(1);
		dog_1.setName("dog_1");
		Dog dog_2 = new Dog();
		dog_2.setId(2);
		dog_2.setName("dog_2");
		map.put(person_1, dog_1);
		map.put(person_2, dog_2);
		System.out.println("--" + map.get(person_1).getName());
		System.out.println("--" + map.get(person_2).getName());
	}
}

class Dog {
	private int id = 0;
	private String name = "";

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public int hashCode() {
		System.out.println("dog‘s hashCode() invoked");
		return id;
	}

	@Override
	public boolean equals(Object obj) {
		System.out.println("dog‘s equals invokes");
		return super.equals(obj);
	}
}

class Person {
	private int id = 0;
	private String name = "";
	private int height = 0;

	@Override
	public int hashCode() {
		System.out.println("person id:" + id + ",hashCode() invoked,"
				+ "hashcode:" + this.name.hashCode() + this.height);
		return super.hashCode();
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}

	@Override
	public String toString() {
		return "id:" + id + "; Name:" + this.name + "; height:" + this.height;
	}

	@Override
	public boolean equals(Object obj) {
		System.out.println("id:" + id + ", equals invokes");
		return super.equals(obj);
	}
}

输出:

person id:1,hashCode() invoked,hashcode:443164103180
person id:2,hashCode() invoked,hashcode:443164103180
person id:1,hashCode() invoked,hashcode:443164103180
--dog_1
person id:2,hashCode() invoked,hashcode:443164103180
--dog_2

解释:

(1)上面建立两个类,然后分别在hashCode和equal方法里面加上输出语句

(2)通过输出可以看到,其实我们重写的equals方法是没有被调用的,我们只需要通过hashcode就可以定位相应的对象

hash碰撞的代码:

package com.ray.ch14;

import java.util.HashMap;

public class Test {
	public static void main(String[] args) {
		HashMap<Person, Dog> map = new HashMap<Person, Dog>();
		Person person_1 = new Person();
		person_1.setHeight(180);
		person_1.setId(1);
		person_1.setName("person_1");
		Person person_2 = new Person();
		person_2.setHeight(180);
		person_2.setId(2);
		person_2.setName("person_1");
		Dog dog_1 = new Dog();
		dog_1.setId(1);
		dog_1.setName("dog_1");
		Dog dog_2 = new Dog();
		dog_2.setId(2);
		dog_2.setName("dog_2");
		map.put(person_1, dog_1);
		map.put(person_2, dog_2);
		System.out.println("--" + map.get(person_1).getName());
		System.out.println("--" + map.get(person_2).getName());
	}
}

class Dog {
	private int id = 0;
	private String name = "";

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public int hashCode() {
		System.out.println("dog‘s hashCode() invoked");
		return id;
	}

	@Override
	public boolean equals(Object obj) {
		System.out.println("dog‘s equals invokes");
		return super.equals(obj);
	}
}

class Person {
	private int id = 0;
	private String name = "";
	private int height = 0;

	@Override
	public int hashCode() {
		System.out.println("person id:" + id + ",hashCode() invoked,"
				+ "hashcode:" + this.name.hashCode() + this.height);
		return this.name.hashCode() + this.height;// 重写的地方
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}

	@Override
	public String toString() {
		return "id:" + id + "; Name:" + this.name + "; height:" + this.height;
	}

	@Override
	public boolean equals(Object obj) {
		System.out.println("id:" + id + ", equals invokes");
		return super.equals(obj);
	}
}

输出:

person id:1,hashCode() invoked,hashcode:443164103180
person id:2,hashCode() invoked,hashcode:443164103180
id:2, equals invokes
person id:1,hashCode() invoked,hashcode:443164103180
id:1, equals invokes
--dog_1
person id:2,hashCode() invoked,hashcode:443164103180
--dog_2

解释:

(1)我们重写了Person,也就是key的hashCode方法,人为的产生hash碰撞现象

(2)从输出可以看出,上面的代码需要用到equals方法

回归put和get的源码;

下面是put的源码:

 public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//注意的地方
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

下面是get的源码:

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))//注意的地方
                return e.value;
        }
        return null;
    }

大家请注意我上面注释“注意的地方”:

(1)如果是平常没有hash碰撞的时候,前面的两个hash比较再加上key的地址的比较即可,然后后出现“短路”现象,使得后的句子不再执行。

(2)但是在出现hash碰撞的情况下,前面两个条件都成立,然后必须使用最后的equals来判断对象的相等。

3.hash碰撞出现的情景?

(1)一般会出现在大的数据情况之下

(2)hashcode的生成方法唯一性较弱(比如上面的人为的生产hashcode)

总结:这一章节主要通过介绍hash碰撞再一次深入了解HashMap的工作原理。

这一章节就到这里,谢谢。

-----------------------------------

目录

时间: 2025-01-17 09:04:33

从头认识java-15.7 Map(4)-介绍HashMap的工作原理-hash碰撞(经常作为面试题)的相关文章

从头认识java-15.7 Map(2)-介绍HashMap的工作原理-put方法

这一章节我们来介绍HashMap的工作原理. 1.HashMap的工作原理图 下图引用自:http://www.admin10000.com/document/3322.html 2.HashMap初始化的时候我们可以这样理解:一个数组,每一个位置存储的是一个链表,链表里面的每一个元素才是我们记录的元素 3.下面我们来看put的源码: public V put(K key, V value) { if (key == null) return putForNullKey(value); int

从头认识java-15.7 Map(3)-介绍HashMap的工作原理-get方法

接着上一章节,我们来讨论一下get方法. 1.还是利用上一章节的图 下图引用自:http://www.admin10000.com/document/3322.html 我们简单说一下步骤,就是通过hashcode先找到table上面的位置,然后遍历位置上的链表 2.get方法的源码: public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (

Java中的数据结构有哪些?HashMap的工作原理是什么?

Java中常用数据结构 常用的数据结构有哈希表,线性表,链表,java.util包中有三个重要的接口:List,Set,Map常用来实现基本的数据结构 HashMap的工作原理 HashMap基于hashing原理,我们通过put(key,value)和get(key)方法存储和获取对象元素,当我们将key值传递给put()方法时,会自动调用对象元素的hashcode方法计算hashcode,然后根据hashcode确定对象元素具体存储的位置: 获取对象时,我们根据键对象的equals方法找到具

Java HashMap的工作原理(转载)

原文地址:http://www.importnew.com/10620.html 面试的时候经常会遇见诸如:"java中的HashMap是怎么工作的","HashMap的get和put内部的工作原理"这样的问题.本文将用一个简单的例子来解释下HashMap内部的工作原理.首先我们从一个例子开始,而不仅仅是从理论上,这样,有助于更好地理解,然后,我们来看下get和put到底是怎样工作的. 我们来看个非常简单的例子.有一个"国家"(Country)类

Java HashMap的工作原理

面试的时候经常会遇见诸如:"java中的HashMap是怎么工作的","HashMap的get和put内部的工作原理"这样的问题.本文将用一个简单的例子来解释下HashMap内部的工作原理.首先我们从一个例子开始,而不仅仅是从理论上,这样,有助于更好地理解,然后,我们来看下get和put到底是怎样工作的. 我们来看个非常简单的例子.有一个"国家"(Country)类,我们将要用Country对象作为key,它的首都的名字(String类型)作为v

[Java] SSH框架笔记_SSH三大框架的工作原理及流程

Hibernate工作原理及为什么要用? 原理:1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件2.由hibernate.cfg.xml中的<mapping resource="com/xx/User.hbm.xml"/>读取并解析映射信息3.通过config.buildSessionFactory();//创建SessionFactory4.sessionFactory.openSession();//打

Java三大器之过滤器(Filter)的工作原理和代码演示

一.Filter简介 Filter也称之为过滤器,它是Servlet技术中最激动人心的技术之一,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp,Servlet, 静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能.例如实现URL级别的权限访问控制.过滤敏感词汇.压缩响应信息等一些高级功能. Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter.通过Fi

JAVA多线程并发变量控制方法之volatile修饰工作原理

在JAVA中,每个线程都有一块属于自己的工作内存区,该内存区会保存一份从主内存拷贝过来的公共变量值.不加volatile修身的变量在每个线程中的值修改一般都是独立的.及如下图所示. 1.开始执行时A.B线程从主内存区load变量i=0.此时值是一样的. 2.当B线程将变量i=1时,此时并不会立刻写入i=1到主内区,所以A线程工作区i还是等于0. 3.如果i变量用Volatile修饰后,B线程改变变量i的值,会立刻返回写回主内存区中.如下图所示. package list; public clas

Java三大器之监听器(Listener)的工作原理和代码演示

现在来说说Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁.主要作用是:做一些初始化的内容添加工作.设置一些基本的内容.比如一些参数或者是一些固定的对象等等.首先来看一下ServletContextListener接口的源代码: public abstract interface ServletContextListener ext