从头认识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的工作原理。


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

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

目录




你可能感兴趣的:(java)