1、什么是Map集合?

  • Map提供了一个通用的元素存储方法,用于存储元素对(也叫键值对),其中每个键映射到一个值。
  • Map是key-value的映射接口,不能包含重复的键,每个键最多只能映射到一个值。
  • Map提供三张Collection视图:键集、值集、键-值映射关系

 



2、Map集合继承架构图




java中的map构造 java中map详解_键值对


结构图详解:


(1) Map 是映射接口,Map中存储的内容是键值对(key-value)。


(2) AbstractMap 是继承于Map的抽象类,实现了Map中的大部分API。其它Map的实现类可以通过继承AbstractMap来减少重复编码。


(3) SortedMap 是继承于Map的接口。SortedMap中的内容是排序的键值对,排序的方法是通过比较器(Comparator)。


(4) NavigableMap 是继承于SortedMap的接口。相比于SortedMap,NavigableMap有一系列的导航方法;如"获取大于/等于某对象的键值对"、“获取小于/等于某对象的键值对”等等。


(5) TreeMap 继承于AbstractMap,且实现了NavigableMap接口;因此,TreeMap中的内容是“有序的键值对”!


(6) HashMap 继承于AbstractMap,但没实现NavigableMap接口;因此,HashMap的内容是“键值对,但不保证次序”!


(7) Hashtable 虽然不是继承于AbstractMap,但它继承于Dictionary(Dictionary也是键值对的接口),而且也实现Map接口;因此,Hashtable的内容也是“键值对,也不保证次序”。但和HashMap相比,Hashtable是线程安全的,而且它支持通过Enumeration去遍历。


(8) WeakHashMap 继承于AbstractMap。它和HashMap的键类型不同,WeakHashMap的键是“弱键”。


 


3、常用方法


 


clear()

从 Map 中删除所有映射

remove(Object key)

从 Map 中删除键和关联的值

put(Object key, Object value)

将指定值与指定键相关联

putAll(Map t)

将指定 Map 中的所有映射复制到此 map

entrySet()

返回 Map 中所包含映射的 Set 视图。Set 中的每个元素都是一个 Map.Entry 对象,可以使用 getKey() 和 getValue() 方法(还有一个 setValue() 方法)访问后者的键元素和值元素

keySet()

返回 Map 中所包含键的 Set 视图。删除 Set 中的元素还将删除 Map 中相应的映射(键和值)

values()

返回 map 中所包含值的 Collection 视图。删除 Collection 中的元素还将删除 Map 中相应的映射(键和值)

get(Object key)

返回与指定键关联的值

containsKey(Object key)

如果 Map 包含指定键的映射,则返回 true

containsValue(Object value)

如果此 Map 将一个或多个键映射到指定值,则返回 true

isEmpty()

如果 Map 不包含键-值映射,则返回 true

size()

返回 Map 中的键-值映射的数目

4、重要接口和抽象类


(1)Map接口


Map接口:是一个键值对映射接口,不能包含重复的键,每个键只能映射一个值,但是值可以重复;


public interface Map<K,V> {
    // Query Operations查询操作
    int size();
    boolean isEmpty();
    boolean containsKey(Object key);
    boolean containsValue(Object value);
    V get(Object key);

    // Modification Operations修改操作
    V put(K key, V value);
    V remove(Object key);

    // Bulk Operations批量操作
    void putAll(Map<? extends K, ? extends V> m);
    void clear();

    // Views视图
    Set<K> keySet();
    Collection<V> values();
    Set<Map.Entry<K, V>> entrySet();

    //内部接口
    interface Entry<K,V> {
        K getKey();
        V getValue();
        V setValue(V value);
        boolean equals(Object o);
        int hashCode();
        public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> c1.getKey().compareTo(c2.getKey());
        }
        public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> c1.getValue().compareTo(c2.getValue());
        }
        public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
            Objects.requireNonNull(cmp);
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
        }
        public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
            Objects.requireNonNull(cmp);
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
        }
    }

    // Comparison and hashing比较和哈希
    boolean equals(Object o);
    int hashCode();

    // Defaultable methods默认方法
    default V getOrDefault(Object key, V defaultValue) {
        V v;
        return (((v = get(key)) != null) || containsKey(key))
            ? v
            : defaultValue;
    }
    default void forEach(BiConsumer<? super K, ? super V> action) {
        Objects.requireNonNull(action);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
            action.accept(k, v);
        }
    }

说明:


[]Map提供接口分别用于返回 键集、值集或键-值映射关系集。


  •      entrySet()用于返回键-值集的Set集合
  •      keySet()用于返回键集的Set集合
  •      values()用户返回值集的Collection集合
  •      因为Map中不能包含重复的键;每个键最多只能映射到一个值。所以,键-值集、键集都是Set,值集时Collection。

[] Map提供了“键-值对”、“根据键获取值”、“删除键”、“获取容量大小”等方法。


 


(2)Map.Entry


interface Entry<K,V>{
   K getKey();
   V getValue();
   V setValue(V value);
   boolean equals(Object o);
   int hashCode();
}

Map.Entry是Map中内部的一个接口,Map.Entry是键值对,Map通过entrySet()获取Map.Entry的键值对集合,从而通过该集合实现对键值对的操作


 


(3)AbstractMap

  • Java中Map类型的数据结构有相当多,AbstractMap作为它们的骨架实现实现了Map接口部分方法,也就是说为它的子类各种Map提供了公共的方法,没有实现的方法各种Map可能有所不同。
  • 抽象类不能通过new关键字直接创建抽象类的实例,但它可以有构造方法。AbstractMap提供了一个protected修饰的无参构造方法,意味着只有它的子类才能访问(当然它本身就是一个抽象类,其他类也不能直接对其实例化),也就是说只有它的子类才能调用这个无参的构造方法。
  • 在Map接口中其内部定义了一个Entry接口,这个接口是Map映射的内部实现用于维护一个key-value键值对,key-value存储在这个Map.Entry中。AbstractMap对这个内部接口进行了实现,一共有两个:一个是可变的SimpleEntry和一个是不可变的SimpleImmutableEntry
  • public static class SimpleEntry<K,V> implements Entry<K,V>, java.io.Serializable
  • 实现了Map.Entry<K, V>接口,并且实现了Serializable(可被序列化)。
  • 它的方法比较简单都是取值存值的操作,对于key值的定义是一个final修饰意味着是一个不可变的引用。另外其setValue方法稍微特殊,存入value值返回的并不是存入的值,而是返回的以前的旧值。需要重点学习的是它重写的equals和hashCode方法。
public abstract class AbstractMap<K,V> implements Map<K,V> {
    protected AbstractMap() {
    }

    // Query Operations
    public int size() {
        return entrySet().size();
    }
    public boolean isEmpty() {
        return size() == 0;
    }
    public boolean containsValue(Object value) {
    }
    public boolean containsKey(Object key) {
    }
    public V get(Object key) {
    }

    // Modification Operations
    public V put(K key, V value) {
        throw new UnsupportedOperationException();
    }
    public V remove(Object key) {
    }

    // Bulk Operations
    public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }
    public void clear() {
        entrySet().clear();
    }

    // Views
    transient volatile Set<K>        keySet;
    transient volatile Collection<V> values;
    public Set<K> keySet() {
    }
    public Collection<V> values() {
    }
    public abstract Set<Entry<K,V>> entrySet();

    // Comparison and hashing
    public boolean equals(Object o) {
    }
    public int hashCode() {
    }
    public String toString() {
    }
    protected Object clone(){
    }
    private static boolean eq(Object o1, Object o2) {
        return o1 == null ? o2 == null : o1.equals(o2);
    }

    public static class SimpleEntry<K,V>
        implements Entry<K,V>, 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<? extends K, ? extends V> 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;
        }
    }

    public static class SimpleImmutableEntry<K,V>
        implements Entry<K,V>, java.io.Serializable
    {
        private static final long serialVersionUID = 7138329143949025153L;
        private final K key;
        private final V value;
        public SimpleImmutableEntry(K key, V value) {
            this.key   = key;
            this.value = value;
        }
        public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
            this.key   = entry.getKey();
            this.value = entry.getValue();
        }
        public K getKey() {
            return key;
        }
        public V getValue() {
            return value;
        }
        public V setValue(V value) {
            throw new UnsupportedOperationException();
        }
        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;
        }
    }
}

重点:


public static class SimpleEntry<K,V> implements Entry<K,V>, java.io.Serializabl

实现了Map.Entry<K, V>接口,并且实现了Serializable(可被序列化)。它的方法比较简单都是取值存值的操作,对于key值的定义是一个final修饰意味着是一个不可变的引用。另外其setValue方法稍微特殊,存入value值返回的并不是存入的值,而是返回的以前的旧值。需要重点学习的是它重写的equals和hashCode方法。


public boolean equals(Object o) {
    if (!(o instanceof Map.Entry))//判断参数是否是Map.Entry类型,要equals相等首先得是同一个类型
        return false;
    Map.Entry<?,?> e = (Map.Entry<?,?>)o;//将Object类型强转为Map.Entry类型,这里参数使用“?”而不是“K, V”是因为泛型在运行时类型会被擦除,编译器不知道具体的K,V是什么类型
    return eq(key, e.getKey()) && eq(value, e.getValue());//key和value分别调用eq方法进行判断,都返回ture时equals才相等。
}
private static boolean eq(Object o1, Object o2) {
     return o1 == null ? o2 == null : o1.equals(o2);    //这个三目运算符也很简单,只不过需要注意的是尽管这里o1、o2是Object类型,Object类型的equals方法是通过“==”比较的引用,所以不要认为这里有问题,因为在实际中,o1类型有可能是String,尽管被转为了Object,所以此时在调用equals方法时还是调用的String#equals方法。
}

public int hashCode() {
     return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());    //key和value的值不为null时,将它们的hashCode进行异或运算。 
}
public static class SimpleImmutableEntry<K,V> implements Entry<K,V>, java.io.Serializable SimpleImmutableEntry

定义为不可变的Entry,其实是事实不可变,因为它不提供setValue方法,在多个线程同时访问时自然不能通过setValue方法进行修改。它相比于SimpleEntry其key和value成员变量都被定义为了final类型。调用setValue方法将会抛出UnsupportedOperationException异常。它的equals和hashCode方法和SimpleEntry一致。


 


(4) SortedMap


public interface SortedMap<K,V> extends Map<K,V> { }
  • SortedMap是一个继承于Map接口的接口。它是一个有序的SortedMap键值映射。


  • SortedMap的排序方式有两种:自然排序 或者 用户指定比较器。 插入有序 SortedMap 的所有元素都必须实现 Comparable 接口(或者被指定的比较器所接受)。
  • 另外,所有SortedMap 实现类都应该提供 4 个“标准”构造方法:
  •  void(无参数)构造方法,它创建一个空的有序映射,按照键的自然顺序进行排序。
  •  带有一个 Comparator 类型参数的构造方法,它创建一个空的有序映射,根据指定的比较器进行排序。
  •  带有一个 Map 类型参数的构造方法,它创建一个新的有序映射,其键-值映射关系与参数相同,按照键的自然顺序进行排序。
  •  带有一个 SortedMap 类型参数的构造方法,它创建一个新的有序映射,其键-值映射关系和排序方法与输入的有序映射相同。无法保证强制实施此建议,因为接口不能包含构造方法。

 


(5) NavigableMap


public interface NavigableMap<K,V> extends SortedMap<K,V> { }

NavigableMap是继承于SortedMap的接口。它是一个可导航的键-值对集合,具有了为给定搜索目标报告最接近匹配项的导航方法。


NavigableMap分别提供了获取“键”、“键-值对”、“键集”、“键-值对集”的相关方法。


 


NavigableMap除了继承SortedMap的特性外,它的提供的功能可以分为4类:


第1类,提供操作键-值对的方法。


  •  lowerEntry、floorEntry、ceilingEntry 和 higherEntry 方法,它们分别返回与小于、小于等于、大于等于、大于给定键的键关联的 Map.Entry 对象。
  • firstEntry、pollFirstEntry、lastEntry 和 pollLastEntry 方法,它们返回和/或移除最小和最大的映射关系(如果存在),否则返回 null。

第2类,提供操作键的方法。这个和第1类比较类似


  •  lowerKey、floorKey、ceilingKey 和 higherKey 方法,它们分别返回与小于、小于等于、大于等于、大于给定键的键。

第3类,获取键集。


  • navigableKeySet、descendingKeySet分别获取正序/反序的键集。

第4类,获取键-值对的子集。


 


5、具体操练实例:


/**
* @author Jason
* @create 2020-07-12 16:24
*/
public class MapTest {
  public static void main(String[] args) {
    //创建map集合
    HashMap<Integer, String> map = new HashMap<>();
    //向map集合中添加元素
    map.put(1, "jason");
    map.put(2, "bill");
    map.put(3, "jack");
    map.put(4, "lili");
    map.put(5, "tom");

    //通过key获取value
    String value = map.get(2);
    System.out.println(value);

    //通过key获取键值对的数量
    System.out.println("键值对的数量:"+map.size());

    //通过key删除key-value
    map.remove(2);
    System.out.println("删除后键值对的数量:"+map.size());

    //判断是否包含key,contains底层调用了equals进行对比
    System.out.println(map.containsKey(new Integer(4)));

    //判断是否包含value
    System.out.println(map.containsValue(new String("jason")));

    //获取所有value
    Collection<String> values = map.values();
    for (String s : values) {
      System.out.println(s);
    }

    //清空集合
    map.clear();
    System.out.println("清空后集合元素数量:"+map.size());

    //判断该集合是否为空
    System.out.println(map.isEmpty());
  }
}

6、不同遍历方式:


/**
* @author Jason
* @create 2020-07-13 10:00
* 遍历方式
*/
public class MapTest {
  public static void main(String[] args) {
    //第一种遍历方式:通过获取key来遍历value
    //创建map集合
    HashMap<Integer, String> map = new HashMap<>();
    //向map集合中添加元素
    map.put(1, "jason");
    map.put(2, "bill");
    map.put(3, "jack");
    map.put(4, "lili");
    map.put(5, "tom");

    //获取所有的key,所有key是一个set集合
    Set<Integer> keys = map.keySet();
    //迭代器遍历
    Iterator<Integer> it = keys.iterator();
    while (it.hasNext()) {
      Integer key = it.next();
      //通过key获取value
      String value = map.get(key);
      System.out.println(key+"="+value);
    }

    System.out.println("============");
    //foreach遍历
    for (Integer key : keys) {
      System.out.println(key+"="+ map.get(key));
    }

    System.out.println("==============");
    //第二种遍历方式:通过entrySet
    //这个方法是把map集合直接全部转换成set集合,元素类型:Map.Entry
    Set<Map.Entry<Integer, String>> set = map.entrySet();
    //比那里set集合,每次取出一个node
    Iterator<Map.Entry<Integer, String>> it2 = set.iterator();
    while (it2.hasNext()) {
      Map.Entry<Integer, String> node = it2.next();
      Integer key = node.getKey();
      String value = node.getValue();
      System.out.println("entrySet方式迭代器遍历:"+key+"="+value);
    }

    System.out.println("==============");
    //foreach遍历
    for (Map.Entry<Integer, String> node : set) {
      System.out.println(node.getKey()+"--->"+node.getValue());
    }
  }
}