好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Java源码解析HashMap的keySet()方法

hashmap的 keyset() 方法比较简单,作用是获取hashmap中的key的集合。虽然这个方法十分简单,似乎没有什么可供分析的,但真正看了源码,发现自己还是有很多不懂的地方。下面是keyset的代码。

?

1

2

3

4

5

6

7

8

public set<k> keyset() {

   set<k> ks = keyset;

   if (ks == null ) {

     ks = new keyset();

     keyset = ks;

   }

   return ks;

}

从代码中了解到,第一次调用keyset方法时,keyset属性是null,然后进行了初始化,再将keyset属性返回。也就是说,hashmap里并不会随着put和remove的进行也维护一个keyset集合,而是在第一次调用keyset方法时,才给keyset属性初始化。

按照自己以往的理解,以为keyset返回的是一个集合,集合里面保存了hashmap的所有的key。因为有了中先入为主的印象,所以读源码时,才感觉源码很奇怪。从源码中可以看到,初始化时,只是创建了一个keyset类的对象,并没有把hashmap的key都加入进来,方法就返回了。除了自己以往的理解外,还有一个现象,让我坚信这时hashmap的key已经加入到keyset了,那就是在调试代码过程中ide给出的调试信息。如下图。从图中可以看出,创建完成keyset()后,调试信息就已经可以显示出,ks中有2个元素了。这个信息更加坚定了自己之前的理解。

那么,hashmap的key是什么时候加入到keyset集合中的呢?顺着这个思路,我进行了一步一步的分析。自己看了keyset类的构造函数,发现只有默认构造函数。那么我想,如果没有在keyset构造函数里把hashmap的key加入进来,那么就有可能是在keyset的父类的构造函数中加入进来的。然后,自己找遍了keyset类的父类的构造函数,发现都是空实现,并没有任何加入hashmap的key的操作。这到底是怎么回事呢?

其实hashmap的key并没有加入到keyset集合中,而是在遍历的时候,使用迭代器对key进行的遍历。这是结论。下面我们看一下原因和过程。

首先看一下keyset类的代码,如下图。可以看到,keyset类中的迭代器函数,返回的是一个keyiterator类的对象。它的next方法返回的是hashiterator的nextnode的key。也就是说,当使用迭代器遍历set内的元素时,keyset类的迭代器,会保证能够依次获取到hashmap的节点的key值,这就是我们遍历keyset的过程的实质。

?

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

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

final class keyset extends abstractset<k> {

   public final int size()         { return size; }

   public final void clear()        { hashmap. this .clear(); }

   public final iterator<k> iterator()   { return new keyiterator(); }

   public final boolean contains(object o) { return containskey(o); }

   public final boolean remove(object key) {

     return removenode(hash(key), key, null , false , true ) != null ;

   }

   public final spliterator<k> spliterator() {

     return new keyspliterator<>(hashmap. this , 0 , - 1 , 0 , 0 );

   }

   public final void foreach(consumer<? super k> action) {

     node<k,v>[] tab;

     if (action == null )

       throw new nullpointerexception();

     if (size > 0 && (tab = table) != null ) {

       int mc = modcount;

       for ( int i = 0 ; i < tab.length; ++i) {

         for (node<k,v> e = tab[i]; e != null ; e = e.next)

           action.accept(e.key);

       }

       if (modcount != mc)

         throw new concurrentmodificationexception();

     }

   }

}

final class keyiterator extends hashiterator

   implements iterator<k> {

   public final k next() { return nextnode().key; }

}

abstract class hashiterator {

   node<k,v> next;    // next entry to return

   node<k,v> current;   // current entry

   int expectedmodcount; // for fast-fail

   int index;       // current slot

   hashiterator() {

     expectedmodcount = modcount;

     node<k,v>[] t = table;

     current = next = null ;

     index = 0 ;

     if (t != null && size > 0 ) { // advance to first entry

       do {} while (index < t.length && (next = t[index++]) == null );

     }

   }

   public final boolean hasnext() {

     return next != null ;

   }

   final node<k,v> nextnode() {

     node<k,v>[] t;

     node<k,v> e = next;

     if (modcount != expectedmodcount)

       throw new concurrentmodificationexception();

     if (e == null )

       throw new nosuchelementexception();

     if ((next = (current = e).next) == null && (t = table) != null ) {

       do {} while (index < t.length && (next = t[index++]) == null );

     }

     return e;

   }

   public final void remove() {

     node<k,v> p = current;

     if (p == null )

       throw new illegalstateexception();

     if (modcount != expectedmodcount)

       throw new concurrentmodificationexception();

     current = null ;

     k key = p.key;

     removenode(hash(key), key, null , false , false );

     expectedmodcount = modcount;

   }

}

那么,这里我们可以思考这么一个问题。通过hashmap的keyset获取到keyset后,难道只能用迭代器遍历吗?keyset方法不把hashmap的key都加入到set中,那么调用者使用for(int i = 0; i < size; i ++)的方式遍历时,岂不是无法遍历set中的key了吗?是的,确实是的。keyset确实没有把key加入到set中,另外,它不用担心调用者用for(int i = 0; i < size; i ++)的方式遍历时获取不到key,因为set根本就没有set.get(i)这样类似的方法,要想遍历set,只能用迭代器,或者使用foreach方式(本质还是迭代器)。

这里还有个问题需要解释,就是在调试代码时,既然key没有加入到set中,那么ide如何显示出set中有2个元素这样的信息的?原来,ide显示对象信息时,会调用对象的tostring方法。而集合的tostring方法就是显示出集合中的元素个数。

这里再思考一步,如果我们在集合的tostring方法加上断点,那么ide显示对象信息时,会不先停下来?答案是看情况。记得早些年间使用eclipse调试代码时,在tostring方法加上断点后,显示对象信息时确实会停下来。然而我现在使用的是ide是idea,idea在这一点上做了优化。如果是ide显示对象信息调用的tostring方法,那么tostring方法的断点会被跳过,即不生效,但会给出一条提示信息,如下图。如果程序员主动调用对象的tostring方法,那么,tostring方法的断点会生效,可以正常断点调试。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。如果你想了解更多相关内容请查看下面相关链接

原文链接:https://blog.csdn.net/li_canhui/article/details/85051250

查看更多关于Java源码解析HashMap的keySet()方法的详细内容...

  阅读:11次