博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入理解Map
阅读量:192 次
发布时间:2019-02-28

本文共 14157 字,大约阅读时间需要 47 分钟。

什么是 Map

Java 中的 Map 接口 是和  同一等级的集合根接口,它 表示一个键值对 (key-value) 的映射

一个 Map 中,任意一个 key 都有唯一确定的 value 与其对应,这个 key-value 的映射就是 map。

Map 中元素的顺序取决于迭代器迭代时的顺序,有的实现类保证了元素输入输出时的顺序,比如说 TreeMap;有的实现类则是无序的,比如 HashMap。

Map 的三个 collection 视图:

这里写图片描述

Map 接口提供了三种角度来分析 Map:

  • KeySet
  • Values
  • Entry

1.KeySet

KeySet 是一个 Map 中键(key)的集合,以 Set 的形式保存,不允许重复,因此键存储的对象需要重写 equals() 和 hashCode() 方法。

在上图就是保存 AA, BB, CC, DD… 等键的集合。

可以通过 Map.keySet() 方法获得。

2.Values

Values 是一个 Map 中值 (value) 的集合,以 Collection 的形式保存,因此可以重复。

在上图就是保存 90,90,56,78… 等值的集合。

通过 Map.values() 方法获得。

3.Entry

Entry 是 Map 接口中的静态内部接口,表示一个键值对的映射,例如上图中 AA-90 这一组映射关系。

这里写图片描述

Entry 具有上图中的方法:

  • getKey() , 获取这组映射中的键 key
  • getValue() , 获取这组映射中的值 value
  • setValue() , 修改这组映射中的值
  • hashCode() , 返回这个 Entry 的哈希值
  • equals() , 对比 key-value 是否相等

通过 Map.entrySet() 方法获得的是一组 Entry 的集合,保存在 Set 中,所以 Map 中的 Entry 也不能重复。

public Set
> entrySet();
  • Map 的三种遍历方式

根据 Map 提供的三种视图,可以有三种 map 遍历方式 :

1.使用 keySet 遍历:

Set set = map.keySet();    for (Object key : set) {        System.out.println(map.get(key));    }

2.使用 values 遍历:

Collection values = map.values();    Iterator iterator = values.iterator();    while (iterator.hasNext()){        System.out.println("value " + iterator.next());    }

3.使用 Entry 遍历

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());    }

练习遍历MAP

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 的实现类

这里写图片描述

Map 的实现类主要有 4 种:

  • Hashtable
    • 古老,线程安全
  • HashMap
    • 速度很快,但没有顺序
  • TreeMap
    • 有序的,效率比 HashMap 低
  • LinkedHashMap
    • 结合 HashMap 和 TreeMap 的优点,有序的同时效率也不错,仅比 HashMap 慢一点

其中后三个的区别很类似 :

  • HashSet
  • TreeSet
  • LinkedHashSet

Map 的每个实现类都应该实现 2 个构造方法:

  1. 无参构造方法,用于创建一个空的 map
  2. 参数是 Map 的构造方法,用于创建一个包含参数内容的新 map

第二种构造方法允许我们复制一个 map。

虽然没有强制要求,但自定义 Map 实现类时最好都这样来。

Map 有以下特点:

  • 没有重复的 key
  • 每个 key 只能对应一个 value, 多个 key 可以对应一个 value
  • key,value 都可以是任何引用类型的数据,包括 null
  • Map 取代了古老的 Dictionary 抽象类

注意:

可以使用 Map 作为 Map 的值,但禁止使用 Map 作为 Map 的键。因为在这么复杂的 Map 中,equals() 方法和 hashCode() 比较难定义。

另一方面,你应该尽量避免使用“可变”的类作为 Map 的键。如果你将一个对象作为键值并保存在 Map 中,之后又改变了其状态,那么 Map 就会产生混乱,你所保存的值可能丢失。

AbstractMap

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() 方法。

正如其他集合推荐的那样,比如  ,实现类最好提供两种构造方法:

  • 一种是不含参数的,返回一个空 map
  • 一种是以一个 map 为参数,返回一个和参数内容一样的 map

AbstractMap 的成员变量

transient volatile Set
keySet;transient volatile Collection
values;

有两个成员变量:

  • keySet, 保存 map 中所有键的 Set
  • values, 保存 map 中所有值的集合

他们都是 transient, volatile, 分别表示不可序列化、并发环境下变量的修改能够保证线程可见性。

需要注意的是 volatile 只能保证可见性,不能保证原子性,需要保证操作是原子性操作,才能保证使用 volatile 关键字的程序在并发时能够正确执行。

AbstractMap 的成员方法

AbstractMap 中实现了许多方法,实现类会根据自己不同的要求选择性的覆盖一些。

接下来根据看看 AbstractMap 中的方法。

1.添加

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() 方法。

2.删除

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();}

3.获取

//时间复杂度为 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;}

4.查询状态

//是否存在指定的 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;}

5.用于比较的 equals(), hashCode()

//内部用来测试 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;}

6.获取三个主要的视图

获取所有的键:

public Set
keySet() { //如果成员变量 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 Collection
values() { 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();

AbstractMap 中的内部类

正如  中有内部类 Map.Entry 一样, AbstractMap 也有两个内部类:

  • SimpleImmutableEntry, 表示一个不可变的键值对
  • SimpleEntry, 表示可变的键值对

SimpleImmutableEntry,不可变的键值对,实现了 Map.Entry < K,V> 接口:

public static class SimpleImmutableEntry
implements 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 SimpleEntry
implements 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 都继承自它。

你可能感兴趣的文章