Map集合的两种遍历方式
通过查看Map集合的API发现没有iterator方法,那么双列集合如何迭代呢?
一、Map集合的遍历之键找值
基本思路:
* 先获取所有键的集合
* 遍历键的集合,获取到每一个键
* 根据键找值
注意--这里我们查看API可以发现Map接口中有一个方法为:
Set<K> keySet():获取集合中所有键的集合
可以看到keySet方法返回了一个Set集合,而Set集合中有迭代器iterator,这样就可以运用Set集合的特性来达到对Map集合的遍历
示例
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Map_Iterator {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("张三", 23);
map.put("李四", 24);
map.put("王五", 25);
map.put("赵六", 26);
Set<String> keySet = map.keySet(); // 获取所有键的集合
Iterator<String> it = keySet.iterator(); // 获取迭代器
while (it.hasNext()) { // 判断集合中是否有元素
String key = it.next(); // 获取每一个键
Integer value = map.get(key); // 根据键获取值
System.out.println(key + "=" + value);
}
}
}
上面也可以使用增强for循环遍历
import java.util.HashMap;
import java.util.Map;
public class Map_Iterator {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("张三", 23);
map.put("李四", 24);
map.put("王五", 25);
map.put("赵六", 26);
for(String key : map.keySet()) { //map.keySet()是所有键的集合
System.out.println(key + "=" + map.get(key));
//这里的map.get方法是HashMap中的get
}
}
}
二、Map集合的遍历之键值对对象找键和值
键值对对象找键和值的思路:
* 获取所有键值对对象的集合
* 遍历键值对对象的集合,获取到每一个键值对对象
* 根据键值对对象找键和值
注意--这里我们查看API可以发现Map接口中有一个方法为:
Set<Map.Entry<K,V>> entrySet():返回此映射中包含的映射关系的Set视图
Map.Entry(注意这个.)说明Entry是Map的内部接口,将键和值封装成了Entry对象,并存储在Set集合中
示例
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Map_Iterator2{
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("张三", 23);
map.put("李四", 24);
map.put("王五", 25);
map.put("赵六", 26);
//Map.Entry说明Entry是Map的内部接口,将键和值封装成了Entry对象,并存储在Set集合中
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
//获取每一个对象
Iterator<Map.Entry<String, Integer>> it = entrySet.iterator();
while(it.hasNext()) {
//获取每一个Entry对象
Map.Entry<String, Integer> en = it.next(); //父类引用指向子类对象
//Entry<String, Integer> en = it.next(); //这样写等同于上面,这里的Entry也是Map中的Entry接口
String key = en.getKey(); //根据键值对对象获取键
Integer value = en.getValue(); //根据键值对对象获取值
System.out.println(key + "=" + value);
}
}
}
产生的疑问:?
上面代码中Map.Entry<String, Integer> en = it.next(); 是说父类引用指向子类对象,为什么?
接下来我们来分析一下上述程序的具体实现过程:
先看一下HashMap的底层的一些变量:
transient Node<K,V>[] table; //存储数据的Node数组
transient Set<java.util.Map.Entry<K,V>> entrySet;
transient int size; //map中存放数据的个数,不等于table.length
transient int modCount; //修改的次数,防止
int threshold; //临界值
final float loadFactor; //扩展因子,一般情况下threshold=table.length*loadFactor;
//构造一个空的HashMap时,只有loadFactor被赋值为默认的0.75。代码如下:
public HashMapMmc(){
this.loadFactor=DEFAULT_LOAD_FACTOR;
}
第一步Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
上面这一步map调用的entrySet() 方法其实是HashMap中重写了Map接口的 entrySet() 方法的entrySet().源码如下:
transient Set<Map.Entry<K,V>> entrySet;
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
...
从源码可以看出这个临时变量entrySet是等于null的,也就是说每次都是new EntrySet();接下来看下面的EntrySet类:
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> 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);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
程序走到了:Iterator<Map.Entry<String, Integer>> it = entrySet.iterator();
而我们可以从源码看到,EntrySet类中重写了iterator方法。返回的是一个new EntryIterator(),接下来看EntryIterator类
final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() {
return nextNode();
}
}
可以看到EntryIterator类继承了HashIterator,而且EntryIterator中有一个next()方法,返回一个nextNode(); 这个返回值其实是在HashIterator中;再看它的源码:
transient Node<K,V>[] table;
transient int modCount;
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;
}
}
可以看出这个HashIterator迭代器的默认构造器中,会初始化一个next的变量,这个变量是在table数组中取得,索引是从0递增的,即先入先出原则。构造初期会从0开始找有值的索引位置,找到后将这个Node赋值给next;然后要遍历的时候是调用nextNode()方法,这个方法是先判断next.next是否为空,如果为空继续往上找有值的索引位置,如果不为空就找next.next。这样就能都遍历出来了,是从索引0到table.length去一个个寻找遍历的。
此时程序经历了while(it.hasNext()) { Map.Entry<String, Integer> en = it.next();
已经遍历完了HashMap集合;这里的 it.next() 返回了 nextNode(); 方法,而nextNode()返回了一个e;Node<K,V> e = next;
Node为HashMap集合的一个静态内部类,它实现了Map.Entry接口;
源码如下:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
所以程序中String key = en.getKey(); Integer value = en.getValue();
调用的就是Node类中的方法;所以就可以说Map.Entry<String, Integer> en = it.next();
是父类引用指向子类对象.
上述遍历代码也可以用增强for循环实现
代码如下:
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
public class Map_Iterator2{
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("张三", 23);
map.put("李四", 24);
map.put("王五", 25);
map.put("赵六", 26);
for (Map.Entry<String, Integer> en : map.entrySet()) {
System.out.println(en.getKey() + "=" + en.getValue());
}
}
}