浅析Java中的hashcode方式转载

原创
小哥 3年前 (2022-10-28) 阅读数 201 #JAVA
文章标签 javajava教程

哈希表的数据结构对大多数人来说肯定并不陌生,它将在许多地方使用。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海子 与博客园共享。欢迎转载,但未经作者同意,您必须保留此声明,并在文章页面明显位置给出原始连接,否则您保留追究法律责任的权利。

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除

热门