对java中的hashcode()函数和HashMap的理解
的有关信息介绍如下:哈希算法对于计算机专业的同学们来说并不陌生,在大学的数据结构中就已经学习了,只不过后来渐渐遗忘了。然后在参加工作后,发现很少接触,也就没怎么认真的去研究。直到有一天,在项目中发现有人重写了hashCode和equals方法(equals和hashCode一起重写,原因请继续看下文),才想到hashCode是什么玩意,是时候该学习一下了。
大学里学过,哈希算法,散列、碰撞等概念,现在是忘得差不多了,忘了也就对了,希望能向张无忌一样,忘完了却能运用自如,无所不为哈哈。哈希算法的作用是帮助我们快速查找,怎样才能快速查找,当然就得按照哈希算法的原理把数据存储在相应的数据结构中,在java中,HashMap就是使用哈希原理实现的数据结构。
首先,先看一下jdk(jdk1.8)源码中,Object对象的hashCode方法,此方法是一个native方法,底层调用时,调用的是非java语言代码。(其算法的原理是使用对象的物理存储地址(internal address)生成一个整数,看下面jdk源码注释。注意,这里的hashCode不要和重写的hashCode相混淆,这个是native方法)。
再来看jdk源码中对hashCode的注释,用我粗糙的英语翻译一下。
(1)hashCode是给调用这个方法的对象生产一个哈希值。hashCode为 受益于哈希表的数据结构 提供支持(好别扭:意思就是,hashCode将会在哈希表中使用),例如HashMap。
(2)hashCode遵循的约定:①在一个应用程序运行过程中,对于某一对象,equals方法比较所用到的信息未发生改变时,多次调用此对象的hashCode方法,应该返回相同的值(言外之意就是,在程序运行过程中,程序修改了对象的某个属性,结果这个属性参与了equals方法的比较,那么此时hashCode的输出结果应该和上次调用不同);对于同一程序多次运行则没有必要保持相同。②当调用equals(Object)方法比较两个对象相等时,则这两个对象的hashCode返回值也相同。③当两个对象equals比较不相同时,两个对象的hashCode返回值可以相同。④当然了,程序猿必须要明白给不同的对象提供不同得哈希值有利于哈希表的性能。
(3)尽管如此,在Object对象中定义的hashCode,对于不同的对象总是返回不同的哈希值。这是因为此处的hashCode不是用java语言实现的,他是一个native方法,他的实现算法是:把此对象的内部地址转换成了整数。
为什么重写了equals方法,也要重写hashCode?看了3中的约定,我想大家应该明白问什么了,因为这是一种约定,为了保证equals相同的对象,其hashCode的返回值也相同。(感觉等于没说,但是我们要知道这是一种约定,在重写时一定要和自己的业务联系起来,小心谨慎)
然后我们来看一下HashMap,因为HashMap中使用到了hashCode。研究了一下jdk源码,发现HashMap其实是一个数组链表(这是jdk1.6,jdk1.8又引入了红黑树,当链表的长度达到8时,就会转换成红黑树;当其长度小于6时,就会转换成链表;这么做的原因就是为了提高查询的效率),存数据的时候先根据hascode计算一个数组索引,然后根据equals方法存储在链表的相应位置。同样取数据的时候也是先根据hashCode确定链表所在的数组索引位置,然后根据equals方法确定链表中的某个对象。当然了,我这边描述的随意了些,但大概就是这个意思。
先看HashMap中的get方法:根据传过来的hash参数值,确定数组的索引,并取出索引位置链表的第一个节点赋值给first;如果(fisrt节点的hash和传过来的对象的hash相同)并且(first节点的key和传过来对象的key引用了同一个对象 或者 使用equals方法比较时他们相同),则此时first就是我们要找的节点。后面代码的意思就是,如果第一个节点不是要找的几点,就按照这个链表依次向下找,直到找到为止。
再看HashMap中的put方法:根据hash找到一个索引位置,如果此位置没有链表,则新建一个节点,待存储的数据就放在这里。
否则:如果索引位置存在节点,先看这个链表的第一个节点是否就是待存储数据的节点,如果是,则说明这个key已经存在了,就会触发后面的覆盖操作(替换value值)。
如果第一个节点不是的:先判断是否是树节点类型,如果是,就按照红黑树的算法去查找。如果不是,则遍历链表判断这个节点是否存在,存在则替换value值,否则,在链表的末尾新建一个节点,把带存储信息放在这个节里。
现在应该对hashCode有个认识了,我的总结是:我开发了java有两年多了,没用到过hashCode,应该涉及到这种底层的数据结构时,才用得到,而我们所要知道的是,使用hashCode是方便我们进行快速查找的。还有一个问题没说,就是他快在哪里,快就快在,他帮我们确定了待查找数据在数组链表中的索引位置,也就是帮我们缩小了查找范围,如果就一个链,那岂不是每次都得从头到尾一个个扫描,很费时间,这也就是选择数组链表的原因吧。这事hashCode的作用,接下来在每个链中查找就是靠的equals方法了,比较key和链表中节点的key是否相同,相同则就找到了我们需要的value。
哈希算法原理的应用很多,比如有一种情况:数据库分表,当一个表中的记录数特别大的时候,已经超出了一张表的容量,我们此时可以选择数据库分表,最简单的策略就是哈希分表,假设分成5张表,就可以用表的ID除以5取余数,然后根据余数分别放置在不同的表中。这样做,即解决了问题,可以使我们的数据均匀的分布在不同的表中,当然了在查询的时候就要注意了,根据ID取余后才能确定数据在哪一张表里。
接下来看一个小例子
你有没有猜到结果