java-散列和散列码

来源:互联网 发布:sql replace替换多个 编辑:程序博客网 时间:2024/06/05 06:49

正在看《Thinking in java》学习散列码相关的知识,在这总结一下吧。

先来看一个例子

class Student{    protected int id;//当前类的成员与继承该类的类能访问.    public Student(int id) {        this.id = id;    }    @Override    public String toString() {        return "Student #" + id;    }}class Score{    private static Random random = new Random(47);    private int score = random.nextInt(100);    @Override    public String toString() {        return String.valueOf(score);    }}public class HashCodeTest {    public static <T extends Student> void studScore(Class<T> type) throws Exception{        Constructor<T> stud = type.getConstructor(int.class);        //利用反射机制来实例化及使用Student类和任何从Student派生出来的类        Map<Student, Score> map = new HashMap<>();        for (int i = 0; i < 10; i++) {            map.put(stud.newInstance(i), new Score());        }        System.out.println("map" + map);        Student student = stud.newInstance(3);//使用id为3的Student作为键        System.out.println("寻找学生: " + student);        if (map.containsKey(student)) {            System.out.println(map.get(student));        }else {            System.out.println("此学生不存在");        }    }    public static void main(String[] args) throws Exception{        studScore(Student.class);    }}

输出

map{Student #1=55, Student #4=61, Student #5=29, Student #3=61, Student #8=22, Student #7=0, Student #0=58, Student #2=93, Student #9=7, Student #6=68}寻找学生: Student #3此学生不存在

但是结果它无法找到id为3的键。问题出在Student是自动继承自基类Object,这里是使用Object的hashCode()方法生成散列码,默认是使用对象的地址计算散列码。第一个Student(3)的实例和第二个的散列码是不同的。所以无法找到。

所以需要恰当的覆盖hashCode()方法。并且同时覆盖equals方法。因为HashMap使用equals()判断当前的键是否与表中存在的键相同

equals()满足下列条件:
1)自反性 对于任意x, x.equals(x)必定返回true。
2)对称性 对于任意x,y,x.equals(y)与y.equals(x)的返回值相同。
3)传递性 对于任意x,y,z,如果x.equals(y)与y.equals(z)返回值相同,那么x.equals(z)返回值也相同。
4)一致性 对于任意的x,y,无论x.equals(y)执行多少次,返回值要么是true,要么为false。
5)对于任意x != null, x.equals(null)返回false。

所以要使用自己的类作为HashMap的键,必须同时覆盖hashCode()和equals().

class Student2 extends Student{    public Student2(int id) {        super(id);    }    @Override    public int hashCode() {        return id;//返回id作为散列码    }    @Override    public boolean equals(Object obj) {        return obj instanceof Student2 && (id == ((Student2)obj).id);        //instanceof检查了此对象是否为null,然后基于每个对象中实际的id进行比较    }}public class HashCode2 {    public static void main(String[] args) throws Exception {        HashCodeTest.studScore(Student2.class);    }}

输出

map{Student #0=58, Student #1=55, Student #2=93, Student #3=61, Student #4=61, Student #5=29, Student #6=68, Student #7=0, Student #8=22, Student #9=7}寻找学生: Student #361

为速度而散列

线性查询是最慢的查询方法
散列将键保存在某处,以便能够很快找到。存储一组元素最快的数据结构是数组,所以使用它来表示键的信息。
数组并不保存键本身,而是通过键对象生成一个数字,将其作为数组的下标,这个数字就是散列码。
冲突有外部链接处理,数组并不直接保存值,而是保存值得list。然后对list中的值使用equals()方法进行线性的查询。(这部分的查询会比较慢)

这里写图片描述

散列表的“槽位”(slot)通常被称为桶位(bucket),Java的散列函数使用2的整数次方(求余和除法是最慢的操作,使用2的整数次方长度的散列码,可以用掩码代替除法,可以减少get()中%操作的开销)。

覆盖hashCode()
无论何时,对同一个对象调用hashCode()都应该生成同样的值。且不能依赖易变的数据。
也不应该使hashCode()依赖于唯一性的对象信息。

以String类为例,如果程序中有多个String对象,都包含相同的字符串序列,那么这些String对象都映射到同一块内存区域。
这里写图片描述

public class StringTest {    public static void main(String[] args) {        String str1 = new String("hello");        String str2 = new String("hello");        System.out.println(str1.hashCode());        System.out.println(str2.hashCode());        System.out.println(str1.equals(str2));        System.out.println(str1 == str2);        //双等号就是比较的栈里面的内容,原始数据类型和地址都是放在栈里面的。而equals则是根据地址拿到堆里面的内容进行比较。    }}

输出

9916232299162322truefalse

对String而言,hashCode()明显是基于String的内容的。根据对象的内容生成散列码,散列码不一定是独一无二,但是通过hashCod()和equals()必须能够完全确定对象的身份

来看看String的源码

public int hashCode() {        int h = hash;        if (h == 0 && value.length > 0) {            char val[] = value;            for (int i = 0; i < value.length; i++) {                h = 31 * h + val[i];            }            hash = h;        }        return h;    }   //String类中的hashCode计算方法还是比较简单的,就是以31为权,每一位为字符的ASCII值进行运算.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;    }
0 0