你所不知道的Java之HashCode

之所以写HashCode,是因为平时我们总听到它。但你真的了解hashcode吗?它会在哪里使用?它应该怎样写?

相信阅读完本文,能让你看到不一样的hashcode。

使用hashcode的目的在于:使用一个对象查找另一个对象。对于使用散列的数据结构,如 HashSet、HashMap、LinkedHashSet、LinkedHashMap ,如果没有很好的覆写键的hashcode()和equals()方法,那么将无法正确的处理键。

请对以下代码中 Person 覆写hashcode()方法,看看会发生什么?

// 覆写hashcode

@Override

public int hashCode() {

return age;

}

@Test

public void testHashCode() {

Set people = new HashSet();

Person person = null;

for (int i = 0; i < 3 ; i++) {

person = new Person("name-" + i, i);

people.add(person);

}

person.age = 100;

System.out.println(people.contains(person));

people.add(person);

System.out.println(people.size());

}

运行结果并不是预期的 true 和 3 ,而是 false 和 4 !改变 person.age 后HashSet无法找到 person 这个对象了,可见覆写hahcode对HashSet的存储和查询造成了影响。

那么hashcode是如何影响HashSet的存储和查询呢?又会造成怎样的影响呢?

HashSet的内部使用HashMap实现,所有放入HashSet中的集合元素都会转为HashMap的key来保存。HashMap使用散列表来存储,也就是数组+链表+红黑树(JDK1.8增加了红黑树部分)。

存储结构简图如下:

HashMap存储结构简图

数组的默认长度为16,数组里每个元素存储的是一个链表的头结点。组成链表的结点结构如下:

static class Node implements Map.Entry {

final int hash;

final K key;

V value;

Node next;

...

}

每一个Node都保存了一个hash----键对象的hashcode,如果键没有按照任何特定顺序保存,查找时通过equals()逐一与每一个数组元素进行比较,那么时间复杂度为O(n),数组长度越大,效率越低。

所以瓶颈在于键的查询速度,如何通过键来快速的定位到存储位置呢?

HashMap将键的hash值与数组下标建立映射,通过键对象的hash函数生成一个值,以此作为数组的下标,这样我们就可以通过键来快速的定位到存储位置了。如果hash函数设计的完美的话,数组的每个位置只有较少的值,那么在O(1)的时间我们就可以找到需要的元素,从而不需要去遍历链表。这样就大大提高了查询速度。

那么HashMap根据hashcode是如何得到数组下标呢?可以拆分为以下几步:

第一步: h = key.hashCode()

第二步: h ^ (h >>> 16)

第三步: (length - 1) & hash

分析

第一步是得到key的hashcode值;

第二步是将键的hashcode的高16位异或低16位(高位运算),这样即使数组table的length比较小的时候,也能保证高低Bit都参与到Hash的计算中,同时不会有太大的开销;

第三步是hash值和数组长度进行取模运算,这样元素的分布相对来说比较均匀。当length总是2的n次方时, h & (length-1) 运算等价于对length取模,这样模运算转化为位移运算速度更快。

但是,HashMap默认数组初始化容量大小为16。当数组长度远小于键的数量时,不同的键可能会产生相同的数组下标,也就是发生了哈希冲突!

对于哈希冲突有开放定址法、链地址法、公共溢出区法等解决方案。

开放定址法就是一旦发生冲突,就寻找下一个空的散列地址。过程可用下式描述:

f i (key) = (f(key) + d i ) mod m (d i =1,2,3,...,m-1)

例如键集合为 {12,67,56,16,25,37,22,29,15,47,48,34} ,表长 n = 12 ,取 f(key) = key mod 12 。

前5个计算都没有冲突,直接存入。如表所示

当 key = 37 时, f(37) = 1 ,与25的位置冲突。应用公式 f(37) = (f(37) + 1) mod 12 = 2 ,所以37存入数组下标为2的位置。如表所示

到了 key = 48 ,与12所在的0冲突了。继续往下找,发现一直到 f(48) = (f(48) + 6) mod 12 = 6 时才有空位。如表所示

所以在解决冲突的时候还会出现48和37冲突的情况,也就是出现了 堆积 ,无论是查找还是存入效率大大降低。

链地址法解决冲突的做法是:如果哈希表空间为 [0~m-1] ,设置一个由m个指针分量组成的一维数组 Array[m] , 凡哈希地址为i的数据元素都插入到头指针为 Array[i] 的链表中。

它的基本思想是:为每个Hash值建立一个单链表,当发生冲突时,将记录插入到链表中。如图所示:

链地址法

链表的好处表现在:

remove操作时效率高,只维护指针的变化即可,无需进行移位操作

重新散列时,原来散落在同一个槽中的元素可能会被散落在不同的地方,对于数组需要进行移位操作,而链表只需维护指针。

但是,这也带来了需要遍历单链表的性能损耗。

公共溢出法就是我们为所有冲突的键单独放一个公共的溢出区存放。

例如前面例子中 {37,48,34} 有冲突,将他们存入溢出表。如图所示。

公共溢出法

在查找时,先与基本表进行比对,如果相等则查找成功,如果不等则在溢出表中进行顺序查找。公共溢出法适用于冲突数据很少的情况。

HashMap解决冲突采取的是链地址法。整体流程图(暂不考虑扩容)如下:

HashMap存储流程简图

理解了hashcode和哈希冲突即解决方案后,我们如何设计自己的hashcode()

方法呢?

Effective Java一书中对覆写hashcode()给出以下指导:

给int变量result赋予某个非零常量值

为对象内每个有意义的域f计算一个int散列码c

域类型计算

booleanc = (f ? 0 : 1)

byte、char、short、intc = (int)f

longc = (int)(f ^ (f >>> 32))

floatc = Float.floatToIntBits(f)

doublelong l = Double.doubleToIntLongBits(f)

c = (int)(l ^ (l >>> 32))

Objectc = f.hashcode()

数组每个元素应用上述规则

booleanc = (f ? 0 : 1)

booleanc = (f ? 0 : 1)

合并计算得到散列码 result = 37 * result + c

现代IDE通过点击右键上下文菜单可以自动生成hashcode方法,比如通过IDEA生成的hashcode如下:

@Override

public int hashCode() {

int result = name != null ? name.hashCode() : 0;

result = 31 * result + age;

return result;

}

但是在企业级代码中,最好使用第三方库如 Apache commons 来生成hashocde方法。使用第三方库的优势是可以反复验证尝试代码。下面代码显示了如何使用 Apache Commons hash code 为一个自定义类构建生成hashcode。

public int hashCode(){

HashCodeBuilder builder = new HashCodeBuilder();

builder.append(mostSignificantMemberVariable);

........................

builder.append(leastSignificantMemberVariable);

return builder.toHashCode();

}

如代码所示,最重要的签名成员变量应该首先传递然后跟随的是没那么重要的成员变量。

总结

通过上述分析,我们设计hashcode()应该注意的是:

无论何时,对同一个对象调用hashcode()都应该生成同样的值。

hashcode()尽量使用对象内有意义的识别信息。

好的hashcode()应该产生分布均匀的散列值。


如果你是一名程序员,如果你刚好又是Java程序员,恰巧刚好你的技术又遇到了瓶颈但是你又拒绝平庸,期待蜕变,想进入一线互联网公司或者给自己涨薪

我这里刚好有一套自己保存的Java进阶学习资料。包含了Spring框架、Mybatis框架SpringBoot框架、SpringMVC框架、SpringCloud微服务、Dubbo框架、Redis缓存、RabbitMq消息、JVM调优、Tomcat容器、MySQL数据库

之前的两千人群满了 这个是新群Java高级进阶群:963,944.895,免费发送的哟

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容

  • 以下内容为作者辛苦原创,版权归作者所有,如转载演绎请在“光变”微信公众号留言申请,转载文章请在开始处显著标明出处。...
    光变阅读 7,443评论 0 9
  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 4,261评论 0 16
  • 昨天看小伙伴们谈理财,想到了自家的情况。我和爱人都是最普通的工薪阶层,收入普普通通,却也没有什么经济压力。两个人都...
    绽蕊向阳阅读 192评论 1 2
  • 有一种孤独是《十一种孤独》没有写的,许多人说好一起上路,结果却在路口分道扬镳,可悲的是连分享都成了奢望,刚刚张嘴却...
    慢渡阅读 293评论 0 1
  • 2018年5月份加入画书营,作为公号编辑的我跟很多小伙伴都保持着较为密切的沟通和交流,遇到了很多志同道合的朋友,在...
    Angela_f4b9阅读 293评论 0 2