HashMap 是典型的key 对应 value的接口 , 里面是数组加链表, 当key 的hash值冲突时 用链地址法解决冲突,在同一个相同下标的table中用链表的形式连接起来
在这里就可以产生问题:
- key的hash值是怎么来的?
- 这样的hash方式有什么好处?
答: 在jdk1.7 中
static int indexFor(int h, int length) {
return h & (length-1);
}
要明确的是目的是为了使table里的数据分布的更均匀
与运算是转化为二进制数 再相与( 都为1 则输出1 见0为0)
比如 128& 129
128 二进制:10000000
129 二进制:10000001
结果为
10000000 128
可以看出与偶数相与时,一定会是偶数
HashMap的初始大小和扩容都是以2的次方来进行的,换句话说length-1换成二进制永远是全部为1,比如容量为16,则length-1为1111,所以相与的数可能是偶数或者奇数,这样hash后的值在table中尽量的分散,前面就有提到hash的目的就是为了使分布更均匀 减少冲突
而在jdk1.8中
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h =key.hashCode()) ^ (h >>> 16);
}
因为对自己改造过的哈希大量冲突时的红黑树有信心,所以简单一点,只是把高16异或下来(异或的操作是相同为0 不同为1)
额外提一点,Java的链表节点数超过8个时,会将链表转化为红黑树,当hash命中很低时,效率比Java7高很多,有兴趣可以看看源码,写的很好。
学习了多年的java的人对hashCode和equals方法都不是很清楚。
总的来说, Java中的集合(Collection)有两类,一类是List,另一类是Set。 前者集合内的元素是有序的,元素可以重复; 后者元素无序,但元素不可重复 。那么这里就有一个比较严重的问题了:如何判断两个对象相等呢? 这就是Object.equals方法了。 但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。
也就是说,每次添加对象都需要与集合中的已有对象进行一次比较。这显然会大大降低效率。
于是, Java采用了哈希表的原理。 哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。可以这样理解, hashCode方法实际上返回的就是对象存储的物理地址 (实际可能并不是)。
这样一来, 当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;[color=red]如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。 所以这里存在一个冲突解决的问题。[/color]
这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。 所以,Java对于eqauls方法和hashCode方法是这样规定的:
1、如果两个对象相同,那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同 ;
上面说的对象相同指的是用eqauls方法比较。
所以像String,Math,Integer等一些类重写了equals方法后都会同样的重写hashCode方法。
为什么要重写equals呢,因为这些类想要比较的是内容而不是对象的存储地址 (存在疑问) 。