java中重载HashMap/HashMap中的hashcode以及equals方法
分割线部分内容为对HashMap的笼统介绍,如果已经理解了可自行跳过。
———————分割———————
不知道HashMap的话一般也不太清楚Set类对象了。
HashSet就是一种集合,根据初中我们学过的集合的特点就可以知道,集合中的元素具有不可重复性、无序性以及可列举性,那么java中的HashSet就是这样的一种数据存储结构了。至于为什么加个Hash看后面的HashMao部分就大概知道了。
对于HashMap部分就是存储键值对 比如你的学号作为索引,后面的姓名作为键值存储就像数据库中的主键和非主键之间的关系一样。注意这里HashMap的底层实现数据结构为数组+链表/数组+红黑树(一种自平衡二叉树,保证查找效率为logn罢了)。这里需要要说一下 为什么又两种存储结构呢?(数组+链表 数组+红黑树)
首先说明一下数组的用途就是;对于所有的哈希结果作为位置索引该位置对应的数值域中存储的解释对应里面德 内容。其实就是表示哈希值啦。里面的指针域说明了该域中存在的数据对象的细节。如果知道分桶的话就知道什么意思了吧
其次对于链表来说如果一个桶里面的数据对象不多的话没什么必要去再构建一个炒鸡浪费空间的红黑树直接用线性链表存储就好了,这样查找效率也不会很低。但是随着存储的数据对象越来越多还用链表的话就查找效率直线下降了。这时候我们可以转化昵称一个自平衡查找树将其查找时间复杂度降为logn.。那么什么时候进行链表到红黑树的转化内?!我们一般设定链表长度超过8的时候就可转换了!大概明白就行了讲多了就不想看了。;80%是像我一样在手机上刷csdn的学生党吧。
最先说明HashMap是一种存储数据结构!
那么为什么有这种数据结构呢?
从顺序表来看:
对于一个顺序表来说如果要查找元素,就要从第一个元素逐个查找。对于需要去除重复的存储来说(比如Set集合)每次存取一个元素之前,就需要将列表中的每个元素对比一遍这样做的效率很低我们要优化!
那么如何优化呢?减少时间复杂度就从加空间辅助数据结构下手最好像,如果打ACM的话前期可以这样想到后面就大概怎么设计一个较好的算法了。
从数据库设计的索引设计部分我们可以类比给存储的数据加一个索引不就好了?!对于听说过底层有红黑树的来说,就大概可以这样理解,红黑树可以进行自平衡操作其最大深度不会超过log(n) 如果用哈夫曼树那种的话优化了时间复杂度等于没什么变化了啊!但是对于红黑树的构建需要花点时间对于后期的算法时间效率提升还是很可观的啦!
那么这里哈希是要干什么呢?
这样想如果我们需要进行索引的数据超大,但是他就只分布相对较小的空间中,那么我们可以通过规约处理将该数据进行规范化,将其单射到一个较小的空间中, 对于后期的对比以及查找来说时间效率的提升还是很好的呢。那么我们就使用哈希函数将原来的索引字段值进行离散化,得到离散结果。这样做的好处是:1.可以i将较大空间上的小空间映射到小空间熵,降低了查找比较所需要的时间。2.同时将该字段进行离散化使得对于可能在相同被索引值的对象可以区分开,同时可以平衡每个键值中的数据分布情况,最好的就是每个哈希值中的数量均相等。这个跟对于不同的数据值来说可以自行优化hash的计算方法。 通过计算出来的hash值来确定数组中的存储位置。减少相同值的碰撞次数。
常用的一这样红哈希算法:BDKR哈希算法
//这里的过程性算法实现我用的是c++算法
//大四保研完了来实习让用java但是实在写的不舒服写个C++放松一下
//为什么我要写算法 头大 摸鱼
/*
*BKDR Hash Function
*written by 吃不胖的jason
Date 2020-11-16
*/
unsigned int BKDRHash(char * str){
unsigned int seed=110;
unsigned in hash=0;
while(*str){
hash*seed+(*str++);
}
return (hash&0x7FFFFFFF);
}
——————————分割线————————————
重写hashCode()的原因
当我们使用基本数据类型当作hashmap的键值的时候由于所有的类都是Object其中有hashCode方法,从而实现了对基本数据类型的hash操作。但是如果我们又一个自己封装的类,这里需要指定对该自定义类中的某一个字段作为索引的时候自带的就不好用了我们需要自行指定到底使用哪一个字段作为索引值。
下面是自行封装的;一个类作为示例进行演示
//后面的为java代码哈 自己敲的思想和流程没什么问题可能会爆红自行体会
public class Student{
private String num;
private String name;
public Student(String num,String name){
this.num=num;
this.name=name;
}
public static boid main(String[] args){
Student stu1=new Student("20177710201",“张三”);
Student stu2=new Student("20177710202","jason");
Student stu3=new Student("20177710203","李四");
System.out.println("张三的hashcode",stu1.hashcode());
System.out.println(“jason的hashcode”,stu2.hashcode());
System.out.println("李四的hashcode",stu3.hashcode());
}
}
这里我们可以发现会有输出,但是后面并不是我们之前输入的2017771020x 而是一堆数字。
这里我们可以了解到在Java的继承体系中,Object类是所有类的超类,Student继承了Objecty类,因此这里没有写hashCode()方法,那么调用Object的超类
如何重写hashCode()方法
/*
这个方法放在Student类里面!!!!!!!!!!!!!!!!!!!!!!
*/
@Override
public int hashCode(){
StringBuilder sb=new StringBuilder();
sb.append(num);
sb.append(name);
char[] charArr=sb.toString().toCharArray();
int hash=0;
for(char c: charArr){
hash=hash*110+c; //110为hash seed 自己指定,可以选优根据情况而定就可以了
}
}
//可以讲所有需要域计算的属性值合并成为一个字符串,然后转化成一个字符数据
char[] charArr=sb.toString().toCharASrray();
//然后遍历这个字符数组进行计算
小建议,从写hashCode()最好写在对象方法里面这样维护也方便,对于HashMap/HashSet可以自行调用!
Java中常用的哈希表
实例说明hashCode()方法,在Hashset和HashMap中的作用
还是之前的类!!!这回用HashSet举例吧
import java.util.HashSet;
import java.util.Set;
public class Student{
private String num;
private String name;
public Studeng(String num.String name){
this.num=num;
this.name=name;
}
@OVerride
public int hashCode(){
StringBuilder sb=new StringBuilder();
sb.append(name);
sb.append(num);
char[] charArr=sb.toStirng().toCharArray();
int hash=0;
for(char c:charArr){
hash=hash*110+c;
}
return hash;
}
public static void main(String[] args){
Student stu1=new Student("20177710201",“张三”);
Student stu2=new Student("20177710201","jason");//注意这里我改了!!!!!!!!这里的数字和上面一行的一样的了
Student stu3=new Student("20177710203","李四");
Set<Student> students=new HashSet<>();
students.add(stu1);
students.add(stu2);
students.add(stu3);
System.out.println(students.size());
}
}
尝试调试一下这个代码我们用大脑想一下stu1 和 stu2因该是在相同的位置的,并且他们的值是一样的,那么对于set来说应该只用保存一个对象到这个set中就可以了,因此我们想象输出的打印结果为2
但是实际上输出的是3 why?
看一下这个玩意是怎么进行加入操作的
public boolean add(E e){
return map.put(e,PRESENT)==null;
}
可以发现这里调用了map类对象的一个Put方法来存放元素,可以发现其实HashSet真正的实现是另外一个类,我们找到这个map
private transient HashMap<E,Object> map;
在HashSet前面声明的属性中可以看到这个HashSet是基于HashMap实现的,
public V put(K key,V value){
return putVal(hash(key),key,value,false,true);
}
这里真正存放元素的逻辑实在putVal()方法中,他会调用存入元素的hashCode()方法与已有的元素进行对比,以此来判断两个元素是否相同,如果不同就讲这个元素也存入表中。
equals()方法的作用
也就是说,使用jhashCode()方法确定元素在数据结构中存放的为值。二使用equals()来求恶人两个元素存放的位置发生冲突时,是应该讲两个元素都存如数据结构还是说需要存放其中一个。
如果equals方法判断两个二元素是一样的,那当然只需要存放其中一个即可,但如果equals()方法判断两个对象是不同的,那么当然啷个都需要存放到数据结构中。
重写equals()方法
@Override
public boolean equals(Object obj){
if(this==obj){
return true;
}
if(obj instanceof Student){
if((Stdent) obj).num.equals(this.num)&&((Student) obj).name.equals(this.name)){
return true;
}
}
}
首先判断是否是自己和自己比较,如果是那么肯定是相同的,因为是同一个对象。然后再诸葛比较对象的属性,如果属性值都相同那么就说明是相同或的对象。
在重写了equals()方法之后,重写在进行之前的测试,就可以发现结果是正确的了,在该集合中三个对象放弃放弃如其中的两个,还有一个因为重复而无法放入。
String 类的hashCode()方法和equals()方法
hashCode()方法重载
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;
}
重写equals()方法
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;
}