浅析Java中的hashcode方式转载
原创哈希表的数据结构对大多数人来说肯定并不陌生,它将在许多地方使用。hash表,以提高查找效率。在……里面Java的Object类中有一个方法。:
1
public
native
int
hashCode();
根据此方法的声明,该方法返回一个int类型的值,并且是局部方法,因此在。Object类中没有给出具体的实现。
为何Object这个类需要这样的方法吗?是干什么的呢?今天我们将详细讨论hashCode方法。
一.hashCode方法的作用
对于包含容器类型的编程语言,基本上都涉及到它们。hashCode。在Java以同样的方式,hashCode该方法的主要功能是正确使用基于散列的集合,其中包括。HashSet、HashMap以及HashTable。
为什么这样说我?考虑这样一种情况,在将对象插入到集合中时,如何判断该对象是否已存在于集合中?(注:集合中不允许重复元素)
也许大多数人都会想到打电话给equals方法逐一比较,该方法确实可行。但是,如果集合中已存在10,000条或更多数据,如果equals方法一一比较,效率必然是个问题。在这个时候hashCode体现了该方法的功能。当集合想要添加新对象时,该对象的hashCode方法,则获取相应的hashcode值,实际上是在HashMap在具体实施中table保存已保存的对象。hashcode值,如果table中没有该hashcode值,可以直接存储,不进行任何比较;hashcode值, 就这么定了equals方法与新元素相比,不存在相同的字词,不存在相同的散列其他地址,所以存在冲突解决问题,所以实际调用。equals用外行的话说,方法的数量大大减少了:Java中的hashCode该方法是映射与对象相关的信息(如对象的存储地址、对象的字段等)。根据某些规则转换为一个值,称为散列值。下面的代码是java.util.HashMap的中put该该方法的具体实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public
V put(K key, V value) {
if
(key ==
null
)
return
putForNullKey(value);
int
hash = hash(key.hashCode());
int
i = indexFor(hash, table.length);
for
(Entry<K,V> e = table[i]; e !=
null
; e = e.next) {
Object k;
if
(e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(
this
);
return
oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return
null
;
}
put该方法被使用HashMap从添加新元素。put该方法的具体实现是已知的,并将首先被调用。hashCode方法获取元素。hashCode值,然后查看table它是否存在于hashCode值(如果存在),呼叫equals方法重新确定元素是否存在,如果存在,则更新value值,否则新元素将添加到HashMap在……里面。从这里可以看出,hashCode已有方法可以减少equals方法,从而提高了程序效率。
如果对于hash表此数据结构的朋友不清楚,请参考这些博客帖子。;
http://www.cnblogs.com/jiewei915/archive/2010/08/09/1796042.html
http://www.cnblogs.com/dolphin0520/archive/2012/09/28/2700000.html
http://www.java3z.com/cwbwebhome/article/article8/83560.html?id=4649
一些朋友错误地认为,默认情况下,hashCode返回的是对象的存储地址。事实上,这种观点是不完整的。确实有一些JVM在实现时,直接返回对象的存储地址,但大多数情况并非如此。只能说存储地址可能具有某种相关性。下面是HotSpot JVM中生成hash哈希值的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static
inline
intptr_t
get_next_hash(Thread * Self, oop obj) {
intptr_t
value = 0 ;
if
(hashCode == 0) {
// This form uses an unguarded global Park-Miller RNG,
// so its possible for two threads to race and generate the same RNG.
// On MP system well have lots of RW access to a global, so the
// mechanism induces lots of coherency traffic.
value = os::random() ;
}
else
if
(hashCode == 1) {
// This variation has the property of being stable (idempotent)
// between STW operations. This can be useful in some of the 1-0
// synchronization schemes.
intptr_t
addrBits =
intptr_t
(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
}
else
if
(hashCode == 2) {
value = 1 ;
// for sensitivity testing
}
else
if
(hashCode == 3) {
value = ++GVars.hcSequence ;
}
else
if
(hashCode == 4) {
value =
intptr_t
(obj) ;
}
else
{
// Marsaglias xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- well
// likely make this the default in future releases.
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
}
value &= markOopDesc::hash_mask;
if
(value == 0) value = 0xBAD ;
assert
(value != markOopDesc::no_hash,
"invariant"
) ;
TEVENT (hashCode: GENERATE) ;
return
value;
}
该实现位于hotspot/src/share/vm/runtime/synchronizer.cpp文件下。
因此,有人会说,它可以直接基于hashcode该值是否确定两个对象是否相等?这绝对是不可能的,因为不同的对象可能会生成相同的hashcode价值。尽管它不能基于hashcode该值确定两个对象是否相等,但可以直接基于hashcode如果两个对象不相等,则该值确定这两个对象不相等。hashcode值不相等,则它一定是两个不同的对象。如果要确定两个对象是否真的相等,则必须通过。equals方法。
也就是说,对于两个对象,如果调用。equals通过该方法得到的结果是true,然后这两个对象hashcode这些值必须相等;
如果equals通过该方法得到的结果是false,然后这两个对象hashcode价值观不一定是不同的;
如果两个对象hashcode值不相等,则equals通过该方法获得的结果必须是false;
如果两个对象hashcode值是相等的,那么equals这种方法得到的结果是未知的。
二.equals方法和hashCode方法
在某些情况下,程序员在设计类时需要重写类。equals方法,如String类,但请务必注意,在重写equals方法的同时,必须重写它。hashCode方法。为什么这样说我?
下面是一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package
com.cxh.test1;
import
java.util.HashMap;
import
java.util.HashSet;
import
java.util.Set;
class
People{
private
String name;
private
int
age;
public
People(String name,
int
age) {
this
.name = name;
this
.age = age;
}
public
void
setAge(
int
age){
this
.age = age;
}
@Override
public
boolean
equals(Object obj) {
// TODO Auto-generated method stub
return
this
.name.equals(((People)obj).name) &&
this
.age== ((People)obj).age;
}
}
public
class
Main {
public
static
void
main(String[] args) {
People p1 =
new
People(
"Jack"
,
12
);
System.out.println(p1.hashCode());
HashMap<People, Integer> hashMap =
new
HashMap<People, Integer>();
hashMap.put(p1,
1
);
System.out.println(hashMap.get(
new
People(
"Jack"
,
12
)));
}
}
我只是在这里重写了一下。equals方法,也就是说,如果两个People对象,如果其名称和年龄相同,则被认为是同一个人。
这个代码的初衷是认为这个代码的输出是“1,但实际上它输出了null“。为什么?原因是要重写equals方法,同时忘记重写hashCode方法。
尽管通过重写equals该方法使得逻辑名称和年龄相同的两个对象被确定为相等的对象(跟随String类类似),但要知道在默认情况下,hashCode该方法是映射对象的存储地址。则上述代码的输出为“null“这并不奇怪,原因很简单,p1对象,这些对象和
System.out.println(hashMap.get(new People("Jack", 12)));这句中的new People("Jack", 12)生成两个对象,它们的存储地址肯定不同。下面是HashMap的get该该方法的具体实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
public
V get(Object key) {
if
(key ==
null
)
return
getForNullKey();
int
hash = hash(key.hashCode());
for
(Entry<K,V> e = table[indexFor(hash, table.length)];
e !=
null
;
e = e.next) {
Object k;
if
(e.hash == hash && ((k = e.key) == key || key.equals(k)))
return
e.value;
}
return
null
;
}
所以在hashmap进行get操作,因为生成的hashcdoe值是不同的(请注意,在某些情况下,上面的代码可能相同。hashcode值,但此概率很小,因为尽管这两个对象具有不同的存储地址,但有可能获得相同的存储地址。hashcode值),因此导致一个get方法中for循环不会执行,而是直接返回。null。
因此,如果您希望上面的代码输出为“1,非常简单,只需要重写hashCode方法,让equals方法和hashCode方法在逻辑上总是一致的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package
com.cxh.test1;
import
java.util.HashMap;
import
java.util.HashSet;
import
java.util.Set;
class
People{
private
String name;
private
int
age;
public
People(String name,
int
age) {
this
.name = name;
this
.age = age;
}
public
void
setAge(
int
age){
this
.age = age;
}
@Override
public
int
hashCode() {
// TODO Auto-generated method stub
return
name.hashCode()*
37
+age;
}
@Override
public
boolean
equals(Object obj) {
// TODO Auto-generated method stub
return
this
.name.equals(((People)obj).name) &&
this
.age== ((People)obj).age;
}
}
public
class
Main {
public
static
void
main(String[] args) {
People p1 =
new
People(
"Jack"
,
12
);
System.out.println(p1.hashCode());
HashMap<People, Integer> hashMap =
new
HashMap<People, Integer>();
hashMap.put(p1,
1
);
System.out.println(hashMap.get(
new
People(
"Jack"
,
12
)));
}
}
这样一来,输出结果就是“1”了。
下面这段话摘自Effective Java一书:
- 在程序执行期间,只要equals方法的比较操作中使用的信息没有被修改,所以多次调用同一个对象,hashCode该方法必须一致地返回相同的整数。
- 如果两个对象基于equals方法比较相等,则调用这两个对象。hashCode该方法必须返回相同的整数结果。
- 如果两个对象基于equals方法比较是不平等的,那么hashCode方法不一定返回不同的整数。
第二条和第三条很容易理解,但第一条经常被忽视。在“Java在《编程思想》一书中P495该页面也有一段与第一段类似的段落:
“设计hashCode()最重要的因素是,每当调用相同的对象时。hashCode()应该产生同样的价值。如果你在谈论一个物体put()添加进HashMap会产生一个hashCdoe值,而用get()当拿出来的时候,另一个hashCode值,则无法获取该对象。所以如果你的hashCode该方法依赖于对象中的变量数据,因此用户应小心,因为当此数据更改时,hashCode()方法将生成不同的哈希码“。
下面是一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package
com.cxh.test1;
import
java.util.HashMap;
import
java.util.HashSet;
import
java.util.Set;
class
People{
private
String name;
private
int
age;
public
People(String name,
int
age) {
this
.name = name;
this
.age = age;
}
public
void
setAge(
int
age){
this
.age = age;
}
@Override
public
int
hashCode() {
// TODO Auto-generated method stub
return
name.hashCode()*
37
+age;
}
@Override
public
boolean
equals(Object obj) {
// TODO Auto-generated method stub
return
this
.name.equals(((People)obj).name) &&
this
.age== ((People)obj).age;
}
}
public
class
Main {
public
static
void
main(String[] args) {
People p1 =
new
People(
"Jack"
,
12
);
System.out.println(p1.hashCode());
HashMap<People, Integer> hashMap =
new
HashMap<People, Integer>();
hashMap.put(p1,
1
);
p1.setAge(
13
);
System.out.println(hashMap.get(p1));
}
}
此代码输出的结果为“null想必每个人都应该清楚其中的原因。
因此,在设计中,hashCode方法和equals方法时,如果对象中的数据是易失性的,则最好。equals方法和hashCode不要在方法中依赖此字段。
以上为个人理解。如果有什么不对的地方,请批评改正。
作者: Matrix海子
出处: http://www.cnblogs.com/dolphin0520/
本博客未注明转载的文章均为作者所有。 Matrix海子 与博客园共享。欢迎转载,但未经作者同意,您必须保留此声明,并在文章页面明显位置给出原始连接,否则您保留追究法律责任的权利。
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除