• 基础加强____【哈希表数据结构】【深入理解hashcode & equals】



    本文主要探讨一下HashMap & HashSet & Hashtable 等集合的哈希表数据结构

    了解hash()方法 与equals() 方法的关联,说一下本人的理解

    "哈希表"数据结构	(参考了SUN官方文档以及无数的网上资料做出的个人总结)
    
    	在集合框架中 HashSet Hashtable HashMap 都使用了哈希表的形式来存储数据;保证数据唯一的方法;hashCode() & equals(); 
    关于hashCode:
    	初学者可以粗略的将 hashCode 的值理解为内存地址值,但这不是绝对物理地址,它是经过哈希算法转成的 int 值;
    	哈希码并不是完全唯一的,它是一种算法,尽量使不同对象拥有不同的哈希码,作为身份的标识
    	hashCode()的几种算法
    	1)Object类的hashCode.返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。
    	2)String类的hashCode.根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串所在的堆空间相同,返回的哈希码也相同。
    	3)Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值,
    	例如Integer i1=new Integer(100),i1.hashCode的值就是100 。由此可见,2个一样大小的Integer对象,返回的哈希码也一样。
    	
    	Object 的哈希码是唯一的,而当对象所对应的类重写了hashCode()方法时,就不一定了。即使两个对象equals为false,也有可能产生相同的哈希码。
    		例如,字符串"BB"和"Aa"的euqals方法比较结果肯定不相等,但它们的hashCode方法返回值却相等。(都是2112)
    	但可以确定的是:如果a.equals(b)为 true,a和b必须有相同的哈希码。(规定)
    		除非你刻意修改参与hashCode计算的字段,如后文注意事项(2)中所述,会导致内存泄露。(将这种行为视为失误或恶意行为)
    
    哈希码的作用:
    	"哈希码就是对象的身份证"  "equals比较相当于匹配对象的DNA" 例如:确定一个人的身份,查身份证号显然要比对比DNA简单得多
    hashCode能够"提高哈希表集合的性能"-->快速地定位对象
    	hashCode算法提高查找的效率,这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,
    		每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域。
    	如果仅使用 equals()方法保证对象唯一性。每增加一个元素就检查一次,那么当元素很多时,就相当繁琐了。
    		也就是说,如果集合中现在已经有10000个元素,那么第10001个元素加入集合时,它就要调用10000次equals方法。这显然会大大降低效率。
    	在哈希表结构的集合中;在添加元素时先调用这个元素的hashCode方法;能够通过哈希码快速定位对象的物理位置;
    		如果这个位置上没有元素,它就可以直接存储在这个位置上;如果这个位置上已经有元素了,那么就使用equals("DNA")来确认是否为同一个对象
    		相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。
    		这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次;执行效率有着明显区别
    
    	"注意事项"
    	(1)在定义集合是往往会自定义覆盖hashCode和equals方法,需要遵守equals的特性规则
    		equals与hashCode的定义必须一致,两个对象equals为true,就必须有相同的hashCode;反之则不成立"BB&Aa"
    			例如:如果定义的equals比较的是雇员ID,那么hashCode就需要散列ID,而不是雇员的姓名或住址
    		用不到哈希表可以不复写,除非你确认类的对象不会放入 HashSet,Hashtable,HashMap.等哈希表集合中。
    			即使暂时用不到哈希表,为其提供一个与equals一致的hashCode方法也没什么坏处,万一以后用到了呢?
    		所以,在定义元素类时,为了避免不必要的麻烦,复写equals方法时通常有必要复写hashCode方法,以保证判定结果的一致性。
    		而在SUN官方的文档中,直接规定"如果重定义equals方法,就必须冲定义hashCode方法,以便用户可以将对象插入到散列(哈希)表中"
    	
    	(2)当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,
    		否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,
    		即使在cantains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果。
    		这也会导致从HashSet集合中单独删除当前对象,从而造成"内存泄露"。
    		内存泄露的定义:分配了内存而没有释放,逐渐耗尽内存资源,导致系统崩溃。
    				现象:如(2)中所述的一种情况,对象一直占用内存空间,无法被垃圾回收机制回收。
    						IO流操作中如果读写完后不关闭流资源,久而久之也会导致内存泄露
    附:
    	既然说到equals,就片面地提一下 ArrayList 集合为什么增删比较慢
    	ArrayList 使用了数组结构,使用角标操作使其具备了容器类中最快的查询速度
    		在添加元素时,需要扩展底层数组的长度所以比较慢(这里简单略过),重点说一下remove操作
    	查看JDK源码可以发现:
    	ArrayList 在删除元素时remove方法首先间接调用到了contains方法确认集合中是否含有这个元素,
    	contains方法使用indexOf()方法的返回值是否>=0来确认是否包含此元素,indexOf方法的原理就是:
    	使用for循环从0角标依次遍历集合中的元素,使用equals比较每个元素,直到为true时返回此元素的角标
    		方法调用次序为: remove()--(间接调用)——>contains()——>indexOf()--(for 循环遍历)——>equals()
    	这就意味着:有10000个元素的 ArrayList 集合在删除元素时,如果恰好要删除的元素位于最后一个角标上
    				就需要调用10000次equals方法来确认这个元素的位置来进行删除操作!
    		这也是我认为 ArrayList 集合删除元素效率低下的根本原因,(如有不妥欢迎指正)

    补充: ArrayList 查询较快是指按照数组角标查询,依赖于角标;而按值查询时最终只能依赖于equals()方法,

        remove操作确认该元素位置时就是依赖“值”来查询,享受不到角标查询的速度



  • 相关阅读:
    IOS OpenGL ES GPUImage 图像显示亮度最高的像素,其他为黑 GPUImageNonMaximumSuppressionFilte
    Email营销相关名词解释:PEM,UCE,Optin,Double OptIn,Optout
    Lombok 使用在 IDEA 中进行 JUnit 测试的时候提示 variable log 错误
    到底应不应该使用 lombok
    Hibernate 元数据模型(MetaModel)提示类没有找到错误
    Java 9 缩小字符串( Compact String)
    Java 虚拟机的概念是怎么来的
    Discourse 自定义头部链接(Custom Header Links)
    Java 6 压缩字符串(Compressed String)
    Java 缩小字符串( Compact String)和 压缩字符串(Compressed String)
  • 原文地址:https://www.cnblogs.com/Joure/p/4337196.html
Copyright © 2020-2023  润新知