1, General traversal of HashMap
HashMap has four types of traversal:
- Key set(): traverse the keys in the Map.
- Value s (): traverse the values in the Map.
- entrySet(): it can traverse the Key and Value in the Map at the same time.
- foreach: you can directly access key and value. (JDK8)
The Demo is as follows:
@Test public void testHashMap() { HashMap<Integer, String> map = new HashMap<Integer, String>() {{ put(1, "value1"); put(2, "value2"); put(3, "value3"); }}; System.out.println("--------------Print Key--------------"); for (Integer integer : map.keySet()) { System.out.println(integer); } System.out.println("--------------Print Value--------------"); for (String value : map.values()) { System.out.println(value); } System.out.println("--------------Print key+value--------------"); for (Map.Entry<Integer, String> entry : map.entrySet()) { System.out.println(entry.getKey() + "----" + entry.getValue()); } System.out.println("--------------foreach Form printing--------------"); map.forEach((key, value) -> { System.out.println(key + "----" + value); }); }
The results are as follows:
This paper focuses on the quadratic traversal of keySet.
2, Quadratic traversal of keySet
Before talking about the secondary traversal problem of keySet(), it is necessary to declare that the premise for the discussion of the secondary traversal problem is:
Use keySet() to traverse the Map to access Key+Value. You cannot get both key and value in one traversal.
2.1 foreach and iterators
Next, let's talk about iterators. Let's look at some of the above Codes:
@Test public void testHashMap() { HashMap<Integer, String> map = new HashMap<Integer, String>() {{ put(1, "value1"); put(2, "value2"); put(3, "value3"); }}; System.out.println("--------------Print Key+Value--------------"); for (Integer key: map.keySet()) { System.out.println(key + "----" + map.get(key)); } }
We should know that the for loop in the above code is actually the writing method of foreach. If you want to use foreach loop, you must implement the iteratable interface. The following two expressions are equivalent:
for(T type: collections){ // operation }
as well as
Iterator i = collections.iterator(); while(i.hasNext()){ T type = i.next(); // operation }
First, let's verify why keySet() can use foreach's writing method (essential iterator). Let's look at the source code of this method:
public Set<K> keySet() { // The first time I came in must be null Set<K> ks = keySet; if (ks == null) { // Initialize ks = new KeySet(); keySet = ks; } return ks; }
Let's take a look at the inheritance relationship of the internal class KeySet:
Very good. The Iterable interface is indeed implemented, so the iterator is actually used in the process of traversal. In order to facilitate the discussion of secondary traversal in this paper, we change the above writing method to: (equivalent code)
@Test public void testHashMap() { HashMap<Integer, String> map = new HashMap<Integer, String>() {{ put(1, "value1"); put(2, "value2"); put(3, "value3"); }}; Iterator<Integer> i = map.keySet().iterator(); while (i.hasNext()) { Integer key = i.next(); System.out.println(key + "----" + map.get(key)); } }
2.2 problem analysis
First, let's discuss it from the macro point of view of the code (that is, on the surface). The above code has two cycles:
First time: iterator < integer > I = Map. Keyset(). Iterator();, Loop through the Map and add the key to the iterator.
The second time: while (i.hasNext()): the purpose is to loop through the key and obtain the value from the map through the key.
So for the second traversal, you don't have to look at and ask more than you do. What is a while loop that isn't traversal? Here is a brief analysis of the iterator() method:
Remember above, ks = new KeySet(); Is this line of code initialized? Take a look at this KeySet:
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(); } } }
If the subclass overrides the method of the parent class, the final implementation will take the subclass. Therefore, let's take a look at the KeySet.iterator() method:
final class KeyIterator extends HashIterator implements Iterator<K> { public final K next() { return nextNode().key; } } ↓↓↓↓↓nextNode()↓↓↓↓↓↓ final Node<K,V> nextNode() { // ... omitted if ((next = (current = e).next) == null && (t = table) != null) { do {} while (index < t.length && (next = t[index++]) == null); } return e; }
You can find the do/while loop and take a look at the Debug process: it can be seen that the loop is indeed gone (I won't tell you what to do)
2.3 summary
When the key and value in the Map are obtained by traversing through keySet(), the second traversal will be carried out.
3, entrySet() has the advantage of traversing key and value
Let's start with the following code:
@Test public void testHashMap() { HashMap<Integer, String> map = new HashMap<Integer, String>() {{ put(1, "value1"); put(2, "value2"); put(3, "value3"); }}; Iterator<Map.Entry<Integer, String>> i = map.entrySet().iterator(); while (i.hasNext()) { Map.Entry<Integer, String> entry = i.next(); System.out.println(entry.getKey() + "----" + entry.getValue()); } }
We can find that the object in the iterator is an entry < integer, string >, which can be passed directly
entry.getKey() and entry.getValue() get the object (remember, this means that the key and value can be stored in the iterator at the same time during the first traversal)
To avoid misunderstanding, let's reiterate here that we only consider the steps of generating iterators.
- keySet() method: iterator < integer > I = map. keySet(). Iterator(); You can only get the key here, and you have to do another traversal to get the value.
- entrySet() method: iterator < map. Entry < integer, string > > I = map. entrySet(). Iterator(); You can get key and value at the same time.
Finally, I hope you will use entrySet() instead of keySet() when you encounter traversal to process key and value at the same time during subsequent development.