1、什么是Map集合?
- Map提供了一个通用的元素存储方法,用于存储元素对(也叫键值对),其中每个键映射到一个值。
- Map是key-value的映射接口,不能包含重复的键,每个键最多只能映射到一个值。
- Map提供三张Collection视图:键集、值集、键-值映射关系
2、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());
}
}
}