本文共 14157 字,大约阅读时间需要 47 分钟。
Java 中的 Map 接口 是和 同一等级的集合根接口,它 表示一个键值对 (key-value) 的映射。
一个 Map 中,任意一个 key 都有唯一确定的 value 与其对应,这个 key-value 的映射就是 map。
Map 中元素的顺序取决于迭代器迭代时的顺序,有的实现类保证了元素输入输出时的顺序,比如说 TreeMap;有的实现类则是无序的,比如 HashMap。
Map 接口提供了三种角度来分析 Map:
KeySet 是一个 Map 中键(key)的集合,以 Set 的形式保存,不允许重复,因此键存储的对象需要重写 equals() 和 hashCode() 方法。
在上图就是保存 AA, BB, CC, DD… 等键的集合。
可以通过 Map.keySet() 方法获得。
Values 是一个 Map 中值 (value) 的集合,以 Collection 的形式保存,因此可以重复。
在上图就是保存 90,90,56,78… 等值的集合。
通过 Map.values() 方法获得。
Entry 是 Map 接口中的静态内部接口,表示一个键值对的映射,例如上图中 AA-90 这一组映射关系。
Entry 具有上图中的方法:
通过 Map.entrySet() 方法获得的是一组 Entry 的集合,保存在 Set 中,所以 Map 中的 Entry 也不能重复。
public Set> entrySet();
根据 Map 提供的三种视图,可以有三种 map 遍历方式 :
Set set = map.keySet(); for (Object key : set) { System.out.println(map.get(key)); }
Collection values = map.values(); Iterator iterator = values.iterator(); while (iterator.hasNext()){ System.out.println("value " + iterator.next()); }
Set entrySet = map.entrySet(); for (Object o : entrySet) { Map.Entry entry = (Map.Entry) o; System.out.println(entry); //key=value System.out.println(entry.getKey() + " / " + entry.getValue()); }
public class LambdaMap {
private Map<String, Object> map = new HashMap<>();
@Before
public void initData() { map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); map.put("key4", 4); map.put("key5", 5); map.put("key5", 'h'); } /** * 遍历Map的方式一 * 通过Map.keySet遍历key和value */ @Test public void testErgodicWayOne() { System.out.println("---------------------Before JAVA8 ------------------------------"); for (String key : map.keySet()) { System.out.println("map.get(" + key + ") = " + map.get(key)); } System.out.println("---------------------JAVA8 ------------------------------"); map.keySet().forEach(key -> System.out.println("map.get(" + key + ") = " + map.get(key))); }/**
* 遍历Map第二种 * 通过Map.entrySet使用Iterator遍历key和value */ @Test public void testErgodicWayTwo() { System.out.println("---------------------Before JAVA8 ------------------------------"); Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, Object> entry = iterator.next(); System.out.println("key:value = " + entry.getKey() + ":" + entry.getValue()); } System.out.println("---------------------JAVA8 ------------------------------"); map.entrySet().iterator().forEachRemaining(item -> System.out.println("key:value=" + item.getKey() + ":" + item.getValue())); }/**
* 遍历Map第三种 * 通过Map.entrySet遍历key和value,在大容量时推荐使用 */ @Test public void testErgodicWayThree() { System.out.println("---------------------Before JAVA8 ------------------------------"); for (Map.Entry<String, Object> entry : map.entrySet()) { System.out.println("key:value = " + entry.getKey() + ":" + entry.getValue()); } System.out.println("---------------------JAVA8 ------------------------------"); map.entrySet().forEach(entry -> System.out.println("key:value = " + entry.getKey() + ":" + entry.getValue())); }/**
* 遍历Map第四种 * 通过Map.values()遍历所有的value,但不能遍历key */ @Test public void testErgodicWayFour() { System.out.println("---------------------Before JAVA8 ------------------------------"); for (Object value : map.values()) { System.out.println("map.value = " + value); } System.out.println("---------------------JAVA8 ------------------------------"); map.values().forEach(System.out::println); // 等价于map.values().forEach(value -> System.out.println(value)); }/**
* 遍历Map第五种 * 通过k,v遍历,Java8 */ @Test public void testErgodicWayFive() { System.out.println("---------------------Only JAVA8 ------------------------------"); map.forEach((k, v) -> System.out.println("key:value = " + k + ":" + v)); } }Map 的实现类主要有 4 种:
其中后三个的区别很类似 :
Map 的每个实现类都应该实现 2 个构造方法:
第二种构造方法允许我们复制一个 map。
虽然没有强制要求,但自定义 Map 实现类时最好都这样来。
Map 有以下特点:
注意:
可以使用 Map 作为 Map 的值,但禁止使用 Map 作为 Map 的键。因为在这么复杂的 Map 中,equals() 方法和 hashCode() 比较难定义。另一方面,你应该尽量避免使用“可变”的类作为 Map 的键。如果你将一个对象作为键值并保存在 Map 中,之后又改变了其状态,那么 Map 就会产生混乱,你所保存的值可能丢失。
AbstractMap 是 的实现类之一,也是 HashMap, TreeMap, ConcurrentHashMap 等类的父类。
AbstractMap 提供了 Map 的基本实现,使得我们以后要实现一个 Map 不用从头开始,只需要继承 AbstractMap, 然后按需求实现/重写对应方法即可。
AbstarctMap 中唯一的抽象方法:
public abstract Set> entrySet();
当我们要实现一个 不可变的 Map 时,只需要继承这个类,然后实现 entrySet()
方法,这个方法返回一个保存所有 key-value 映射的 set。 通常这个 Set 不支持 add(), remove() 方法,Set 对应的迭代器也不支持 remove() 方法。
如果想要实现一个 可变的 Map,我们需要在上述操作外,重写 put() 方法,因为 默认不支持 put 操作:
public V put(K key, V value) { throw new UnsupportedOperationException();}
而且 entrySet() 返回的 Set 的迭代器,也得实现 remove() 方法,因为 AbstractMap 中的 删除相关操作都需要调用该迭代器的 remove() 方法。
正如其他集合推荐的那样,比如 ,实现类最好提供两种构造方法:
transient volatile SetkeySet;transient volatile Collection values;
有两个成员变量:
他们都是 transient, volatile, 分别表示不可序列化、并发环境下变量的修改能够保证线程可见性。
需要注意的是 volatile 只能保证可见性,不能保证原子性,需要保证操作是原子性操作,才能保证使用 volatile 关键字的程序在并发时能够正确执行。
AbstractMap 中实现了许多方法,实现类会根据自己不同的要求选择性的覆盖一些。
接下来根据看看 AbstractMap 中的方法。
public V put(K key, V value) { throw new UnsupportedOperationException();}public void putAll(Map m) { for (Map.Entry e : m.entrySet()) put(e.getKey(), e.getValue());}
可以看到默认是不支持添加操作的,实现类需要重写 put() 方法。
public V remove(Object key) { //获取保存 Map.Entry 集合的迭代器 Iterator> i = entrySet().iterator(); Entry correctEntry = null; //遍历查找,当某个 Entry 的 key 和 指定 key 一致时结束 if (key==null) { while (correctEntry==null && i.hasNext()) { Entry e = i.next(); if (e.getKey()==null) correctEntry = e; } } else { while (correctEntry==null && i.hasNext()) { Entry e = i.next(); if (key.equals(e.getKey())) correctEntry = e; } } //找到了,返回要删除的值 V oldValue = null; if (correctEntry !=null) { oldValue = correctEntry.getValue(); //调用迭代器的 remove 方法 i.remove(); } return oldValue;}//调用 Set.clear() 方法清除public void clear() { entrySet().clear();}
//时间复杂度为 O(n)//许多实现类都重写了这个方法public V get(Object key) { //使用 Set 迭代器进行遍历,根据 key 查找 Iterator> i = entrySet().iterator(); if (key==null) { while (i.hasNext()) { Entry e = i.next(); if (e.getKey()==null) return e.getValue(); } } else { while (i.hasNext()) { Entry e = i.next(); if (key.equals(e.getKey())) return e.getValue(); } } return null;}
//是否存在指定的 key//时间复杂度为 O(n)//许多实现类都重写了这个方法public boolean containsKey(Object key) { //还是迭代器遍历,查找 key,跟 get() 很像啊 Iterator> i = entrySet().iterator(); if (key==null) { while (i.hasNext()) { Entry e = i.next(); //getKey() if (e.getKey()==null) return true; } } else { while (i.hasNext()) { Entry e = i.next(); if (key.equals(e.getKey())) return true; } } return false;}//查询是否存在指定的值public boolean containsValue(Object value) { Iterator > i = entrySet().iterator(); if (value==null) { while (i.hasNext()) { Entry e = i.next(); //getValue() if (e.getValue()==null) return true; } } else { while (i.hasNext()) { Entry e = i.next(); if (value.equals(e.getValue())) return true; } } return false;}public int size() { //使用 Set.size() 获取元素个数 return entrySet().size();}public boolean isEmpty() { return size() == 0;}
//内部用来测试 SimpleEntry, SimpleImmutableEntry 是否相等的方法private static boolean eq(Object o1, Object o2) { return o1 == null ? o2 == null : o1.equals(o2);}//判断指定的对象是否和当前 Map 一致//为什么参数不是泛型而是 对象呢//据说是创建这个方法时还没有泛型 - -public boolean equals(Object o) { //引用指向同一个对象 if (o == this) return true; //必须是 Map 的实现类 if (!(o instanceof Map)) return false; //强转为 Map Map m = (Map ) o; //元素个数必须一致 if (m.size() != size()) return false; try { //还是需要一个个遍历,对比 Iterator> i = entrySet().iterator(); while (i.hasNext()) { //对比每个 Entry 的 key 和 value Entry e = i.next(); K key = e.getKey(); V value = e.getValue(); if (value == null) { //对比 key, value if (!(m.get(key)==null && m.containsKey(key))) return false; } else { if (!value.equals(m.get(key))) return false; } } } catch (ClassCastException unused) { return false; } catch (NullPointerException unused) { return false; } return true;}//整个 map 的 hashCode() public int hashCode() { int h = 0; //是所有 Entry 哈希值的和 Iterator > i = entrySet().iterator(); while (i.hasNext()) h += i.next().hashCode(); return h;}
获取所有的键:
public SetkeySet() { //如果成员变量 keySet 为 null,创建个空的 AbstractSet if (keySet == null) { keySet = new AbstractSet () { public Iterator iterator() { return new Iterator () { private Iterator > i = entrySet().iterator(); public boolean hasNext() { return i.hasNext(); } public K next() { return i.next().getKey(); } public void remove() { i.remove(); } }; } public int size() { return AbstractMap.this.size(); } public boolean isEmpty() { return AbstractMap.this.isEmpty(); } public void clear() { AbstractMap.this.clear(); } public boolean contains(Object k) { return AbstractMap.this.containsKey(k); } }; } return keySet;}
获取所有的值:
public Collectionvalues() { if (values == null) { //没有就创建个空的 AbstractCollection 返回 values = new AbstractCollection () { public Iterator iterator() { return new Iterator () { private Iterator > i = entrySet().iterator(); public boolean hasNext() { return i.hasNext(); } public V next() { return i.next().getValue(); } public void remove() { i.remove(); } }; } public int size() { return AbstractMap.this.size(); } public boolean isEmpty() { return AbstractMap.this.isEmpty(); } public void clear() { AbstractMap.this.clear(); } public boolean contains(Object v) { return AbstractMap.this.containsValue(v); } }; } return values;}
获取所有键值对,需要子类实现:
public abstract Set> entrySet();
正如 中有内部类 Map.Entry 一样, AbstractMap 也有两个内部类:
SimpleImmutableEntry,不可变的键值对,实现了 Map.Entry < K,V> 接口:
public static class SimpleImmutableEntryimplements Entry , java.io.Serializable{ private static final long serialVersionUID = 7138329143949025153L; //key-value private final K key; private final V value; //构造函数,传入 key 和 value public SimpleImmutableEntry(K key, V value) { this.key = key; this.value = value; } //构造函数2,传入一个 Entry,赋值给本地的 key 和 value public SimpleImmutableEntry(Entry entry) { this.key = entry.getKey(); this.value = entry.getValue(); } //返回 键 public K getKey() { return key; } //返回 值 public V getValue() { return value; } //修改值,不可修改的 Entry 默认不支持这个操作 public V setValue(V value) { throw new UnsupportedOperationException(); } //比较指定 Entry 和本地是否相等 //要求顺序,key-value 必须全相等 //只要是 Map 的实现类即可,不同实现也可以相等 public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry )o; return eq(key, e.getKey()) && eq(value, e.getValue()); } //哈希值 //是键的哈希与值的哈希的 异或 public int hashCode() { return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } //返回一个 String public String toString() { return key + "=" + value; }}
SimpleEntry, 可变的键值对:
public static class SimpleEntryimplements Entry , java.io.Serializable{ private static final long serialVersionUID = -8499721149061103585L; private final K key; private V value; public SimpleEntry(K key, V value) { this.key = key; this.value = value; } public SimpleEntry(Entry entry) { this.key = entry.getKey(); this.value = entry.getValue(); } public K getKey() { return key; } public V getValue() { return value; } //支持 修改值 public V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry )o; return eq(key, e.getKey()) && eq(value, e.getValue()); } public int hashCode() { return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } public String toString() { return key + "=" + value; }}
SimpleEntry 与 SimpleImmutableEntry 唯一的区别就是支持 setValue() 操作。
AbstractMap 是一个基础实现类,实现了 Map 的主要方法,默认不支持修改。
常用的几种 Map, 比如 HashMap, TreeMap, LinkedHashMap 都继承自它。