hashCode()与equals()区别
这两个方法均是超类Object自带的成员方法。Object类是所有Java类的祖先。每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。在不明确给出超类的情况下,Java会自动把Object作为要定义类的超类。可以使用类型为Object的变量指向任意类型的对象。Object类有一个默认构造方法pubilc Object(),在构造子类实例时,都会先调用这个默认构造方法。Object类的变量只能用作各种值的通用持有者。要对他们进行任何专门的操作,都需要知道它们的原始类型并进行类型转换。例如:
Object obj = new MyObject();
MyObject x = (MyObject)obj;
Java语言规范要求equals方法具有下面的特点:
自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
对于任何非空引用值 x,x.equals(null) 都应返回 false。
equals()是对两个对象的地址值进行的比较(即比较引用是否相同)。
hashCode()是一个本地方法,它的实现是根据本地机器相关的。
JVM每new一个Object,它都会将这个Object丢到一个Hash哈希表中去,这样的话,下次做Object的比较或者取这个对象的时候,它会根据对象的hashcode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。具体过程是这样:
1. new Object(),JVM根据这个对象的Hashcode值,放入到对应的Hash表对应的Key上,如果不同的对象产生了相同的hash值,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同hashcode的对象放到这个单链表上去,串在一起。
2. 比较两个对象的时候,首先根据他们的hashcode去hash表中找他的对象,当两个对象的hashcode相同,那么就是说他们这两个对象放在Hash表中的同一个key上,那么他们一定在这个key上的链表上。那么此时就只能根据Object的equal方法来比较这个对象是否equal。当两个对象的hashcode不同的话,肯定他们不能equals.
list是可以重复的,set是不可以重复的。那么set存储数据的时候是怎样判断存进的数据是否已经存在。使用equals()方法呢,还是hashcode()方法。
假如用equals(),那么存储一个元素就要跟已存在的所有元素比较一遍,比如已存入100个元素,那么存101个元素的时候,就要调用equals方法100次。
但如果用hashcode()方法的话,他就利用了hash算法来存储数据的。
这样的话每存一个数据就调用一次hashcode()方法,得到一个hashcode值及存入的位置。如果该位置不存在数据那么就直接存入,否则调用一次equals()方法,不相同则存,相同不存。这样下来整个存储下来不需要调用几次equals方法,虽然多了几次hashcode方法,但相对于前面来讲效率高了不少。
因为Object的equal方法默认是两个对象的引用的比较,意思就是指向同一内存,地址则相等,否则不相等;如果你现在需要利用对象里面的值来判断是否相等,则重载equal方法。
JDK中,String、Math等封装类都对Object中的equals()方法进行了重写。
java.lnag.Object中对hashCode的约定:
1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。
Java中的Collection有两类,一类是List,一类是Set。List内的元素是有序的,元素可以重复。Set元素无序,但元素不可重复。要想保证元素不重复,两个元素是否重复应该依据什么来判断呢?用Object.equals方法。但若每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说若集合中已有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。于是Java采用了哈希表的原理。
我们知道,数组和向量都可以存储对象,但对象的存储位置是随机的,也就是说对象本身与其存储位置之间没有必然的联系。当要查找一个对象时,只能以某种顺序(如顺序查找或二分查找)与各个元素进行比较,当数组或向量中的元素数量很多时,查找的效率会明显地降低。
一种有效的存储方式,是不与其他元素进行比较,一次存取便能得到所需要的记录。这就需要在对象的存储位置和对象的关键属性(设为 k)之间建立一个特定的对应关系(设为 f),使每个对象与一个唯一的存储位置相对应。在查找时,只要根据待查对象的关键属性 k 计算f(k)的值即可。如果此对象在集合中,则必定在存储位置 f(k)上,因此不需要与集合中的其他元素进行比较。称这种对应关系 f 为哈希(hash)方法,按照这种思想建立的表为哈希表。
HashSet底层是用了HashMap。
我们在此需要理解几个概念:
1.哈希算法,是一类算法:这类算法接受任意长度的二进制输入值,对输入值做换算(如切碎),最终给出固定长度的二进制输出值。 所以,Hash算法不是某个固定的算法,它代表的是一类算法。以更好理解的方式来说,Hash算法是摘要算法,也就是说,从不同的输入中,通过一些计算摘取出来一段输出数据。 给密码加密用的MD5 就是一种Hash算法 。 那么,具体来说,Hash/摘要/散列/切碎算法都有哪些用处呢? (1)信息安全领域: Hash算法可用作加密算法。如文件校验,通过对文件摘要,可以得到文件的“数字指纹”,你下载的任何副本的“数字指纹”只要与官方给出的“数字指纹”一致,那么就可以知道这是未经篡改的。例如著名的MD5 。 (2)数据结构领域: Hash算法 通常还可用作快速查找。根据Hash函数,我们可以实现一种叫做哈希表(Hash Table)的数据结构。这种结构可以实现对数据进行快速的存取。 2.哈希表(Hash Table)是一种数据结构。 相对来说,线性表、树 这些数据结构中,记录 在结构 中的相对位置是随机的,和记录的关键字之间不存在确定关系,因此,在数据结构中查找时需要进行一系列和关键字的比较。这一类查找方法建立在“比较”的基础上。在顺序查找时,比较的结果为“=”与“≠”2种可能;在折半查找、二叉排序树查找和B-树查找时,比较的结果为“<”“=”“>”3种可能。查找的效率依赖于查找过程中所进行的比较次数。 理想的情况是希望不经过任何比较,一次存取便能得到所查记录,那就必须在记录的存储位置和它的关键字之间建立一个确定的关系,使每个关键字和结构中一个唯一的存储位置相对应。因而在查找时,只要根据这个对应关系找到给定值即可。若结构中存在关键字和相等的记录,则必定在该存储位置上,反之在这个位置上没有记录。由此,不需要比较便可直接取得所查记录。在此,我们称这个对应关系为哈希(Hash)函数 ,按这个思想建立的表为哈希表 。 哈希函数有两个特点: (1)灵活。哈希函数是一个映像,因此哈希函数的设定很灵活,只要使得任何关键字由此所得的哈希函数值都落在表长允许的范围之内即可。 (2)冲突。对不同的关键字可能得到同一哈希地址,这种现象称为冲突(collision)。冲突只能尽量地少,而不能完全避免。因为,哈希函数是从关键字集合到地址集合的映像。而通常关键字集合比较大,它的元素包括所有可能的关键字,而地址集合的元素仅为哈希表中的地址值。因此,在实现哈希表这种数据结构的时候不仅要设定一个“好”的哈希函数,而且要设定一种处理冲突的方法。 由此得出哈希表的概念:根据设定的Hash函数和处理冲突的方法,将一组关键字映象 到一个有限的连续的地址集(区间)上,并以关键字在地址集中的象 作为记录在表中的存储位置,这样的表便称为Hash表。 3.哈希函数,是支撑哈希表的一类函数。 常用的Hash函数 构造方法有: (1)直接寻址法。取k 或k 的某个线性函数为Hash地址 。特点:由于直接地址法相当于有多少个关键字就必须有多少个相应地址去对应,所以不会产生冲突,也正因为此,所以实际中很少使用这种构造方法。 (2)数字分析法。就是找出关键字 的规律,尽可能用差异数据来构造Hash地址。特点:需要提前知道所有可能的关键字,才能分析运用此种方法,所以不太常用。 (3)平方取中法。先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。例:我们把英文字母在字母表中的位置序号作为该英文字母的内部编码。例如K的内部编码为11,E的内部编码为05,Y的内部编码为25,A的内部编码为01, B的内部编码为02。由此组成关键字“KEYA”的内部代码为11052501,同理我们可以得到关键字“KYAB”、“AKEY”、“BKEY”的内部编码。之后对关键字进行平方运算后,取出第7到第9位作为该关键字哈希地址。特点:较常用。 (4)折叠法。将关键字分割成位数相同的几部分(最后一部分位数可以不同),然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。 (5)随机数法。选择一个随机函数,取关键字的随机函数值作为Hash地址 ,通常用于关键字长度不同的场合。特点:通常,关键字长度不相等时,采用此法构建Hash函数 较为合适。 (6)除留取余法。取关键字被某个不大于Hash表 长m 的数p 除后所得的余数为Hash地址 。特点:这是最简单也是最常用的Hash函数构造方法。可以直接取模,也可以在平方取中法、折叠法之后再取模。值得注意的是,在使用除留取余法 时,对p 的选择很重要,如果p 选的不好会容易产生同义词 。由经验得知:p 最好选择不大于表长m的一个质数 、或者不包含小于20的质因数的合数。 如何处理冲突是哈希造表不可缺少的一个方面。现在完整的描述一下处理冲突: 冲突是指由关键字得到的哈希地址的位置上已存有记录,则“处理冲突”就是为该关键字的记录找到另一个“空”的哈希地址。 在处理冲突的过程中可能得到一个地址序列。即在处理哈希地址的冲突时,若得到的另一个哈希地址仍然发生冲突,则再求下一个地址,若仍然冲突,再求,依次类推,直至不发生冲突为止,则为记录在表中的地址。 处理冲突通常有以下4种方法: (1)开放定址法。 若 为哈希函数,则为哈希表表长;。若为增量序列,则有3种取法:线性探测再散列;二次探测再散列;伪随机探测再散列。 (2)再哈希法。 均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数地址,直到冲突不再发生,这种方法不易产生聚集 ,但增加了计算时间。 (3)链地址法。 将所有关键字为同义词的记录存储在同一线性表中,即在Hash 出来的哈希地址中不直接存Key ,而是存储一个Key 的链表 ,当发生冲突 时,将同义的Key 加入链表。 (4)公共溢出区。 可以建立一个公共溢出区,用来存放有冲突的Key 。比如设立另一个哈希表,专门用来存放出现冲突的同义词。 4.Map是映射、地图的意思,在Java中Map表示一种把K映射到V的数据类型; HashMap是Java中用哈希数据结构实现的Map。 数据结构表达的是:用什么样的结构,组织一类数据。数据结构分为逻辑结构和物理结构: (1)基本的逻辑结构有:集合、线性结构、树形结构、图。 (2)物理结构:顺序存储、链式存储。 Hash表 是一种逻辑数据结构,HashMap是Java中的一种数据类型(结构类型),它通过代码实现了Hash表 这种数据结构,并在此结构上定义了一系列操作。 HashMap是基于数组来实现哈希表的,数组就好比内存储空间,数组的index就好比内存的地址。 HashMap的每个记录就是一个Entry<K, V>对象,数组中存储的就是这些对象。 HashMap的哈希函数 = 计算出hashCode + 计算出数组的index。 HashMap解决冲突:使用链地址法,每个Entry对象都有一个引用next来指向链表的下一个Entry。 HashMap的装填因子:默认为0.75。【装填因子标志哈希表的装满程度。装填因子越小,发生冲突的可能性就越小;越大,代表着表中已填入的元素越多,再填入元素时发生冲突的可能性就越大,那么在查找时,给定值需要比较的关键字的个数就越多】