Java 集合体系之 Map 源码分析

前言

大家好,在之前的文章中,我们分析了 List 接口下的实现类 ArrayList 和 LinkedList 的源码。但是其中还有一个实现类 Vector 并没有说到,该类的实现与 ArrayList 基本相同,都是采用数组实现,区别就是其中的大量方法,如 add()、get() 等都是采用 synchronized 进行修饰,保证了线程安全。而 ArrayList 我们之前就分析过了,所以这里就不对 Vector 进行分析了,大家有兴趣可以自己看看源码。

下面我们该进入今天的主题了:来分析 Map 接口的源码。
“诶!你这跳转也太大了吧!Set 接口怎么还没分析就跳到 Map 了!”
别心急嘛,谁叫 HashSet 和 TreeSet 的内部实现都分别是由 HashMap 和 TreeMap 实现的呢?不先分析 Map 没办法呐… 好了,快进入正题吧。

概述

Java 中的 Map 接口和 Collection 接口一样,都是同一等级的根接口。Map 表示的是一个键(key)值(Value)对的映射,其中键是唯一的,每个键可以映射最多一个值。该接口取代了 Dictionary 抽象类。

需要注意的是,如果我们把可变对象作为 Map 的键,则必须要非常小心了,因为有可能会导致对象更改后查找不到对应的 value 了。这是因为,像 Map 的实现类,如 HashMap,是以 key 的哈希值来存储和查找键值对的(在后面的文章中会进行深深入分析),而一个可变对象在创建后其哈希值是可能被改变的。为了解决这种问题,我们最好是用 String、Integer 等不可变对象来作为 key,或者我们重写类的 hasCode() 、equals() 方法,用类的成员变量来进行计算。例如:

public class Person {
    private String id;
    public Person(String id) {
        this.id = id;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        if (id != null ? !id.equals(person.id) : person.id != null) return false;
        return true;
    }
    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;
    }
}

另外,我们虽然可以将 Map 作为 Map 的值,但是应避免将 Map 作为 Map 的键,因为 Map 比较复杂,比较难定义 hashCode() 与 equals() 方法。

Map 所有通用的实现类应该提供两个“标准”的构造函数:

  • 一个无参数无返回值的空构造函数。
  • 一个具有 Map 类型单个参数的构造函数,它创建一个具有参数相同键值的新 Map。

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

结构

这里写图片描述
上图是 Map 集合的整体 UML 图。在以后的文章,我们再来一个个分析 Map 接口下面的实现类、接口。

再来看看 Map 接口中都定义了一些什么方法:
这里写图片描述
其中,从 getOrDefault() 方法开始的方法,都是从 Java8 开始引入的新特性:默认方法。下面我们来开始从头开始一个一个分析。

源码分析

查询操作

/**
 * 返回此 map 中键值映射的数量。如果 map 包含多于 Integer.MAX_VALUE 的元素,返回 Integer.MAX_VALUE。
 */
int size();
/**
 * 如果此 map 中不包含键值映射,返回 true,否则返回 false。
 */
boolean isEmpty();
/**
 * 如果此 map 的映射中包含指定的映射键,则返回 true。
 */
boolean containsKey(Object key);
/**
 * 如果此 map 中将一个或多个键映射到指定的 value,则返回 true。
 */
boolean containsValue(Object value);
/**
 * 返回指定键映射的值,如果此映射中不包含该键的映射,则返回 null。
 */
V get(Object key);

修改操作

/**
 * 将指定的值于此 map 中指定的键相关联。
 * 如果 map 中已经存在该键,那么将会替换值。
 */
V put(K key, V value);
/**
 * 如果此 map 中存在指定 key 的映射,那么删除这个映射,并返回对于的值。
 * 如果不存在指定 key 的映射,那么返回 null。(如果此 map 允许 null 值
 * ,那么返回 null 并不一定表示该 map 不存在指定 key 的映射)
 */
V remove(Object key);

批量操作

/**
 * 将指定 map 中的所有映射复制到此 map。
 * 如果在操作过程中修改了指定的 map,则此操作的行为是未定义的。
 */
void putAll(Map<? extends K, ? extends V> m);
/**
 * 从此 map 中删除所有的映射。
 */
void clear();

查看

/**
 * 返回此 map 中包含所有键的 Set 集合。
 * 该集合由 map 支持,因此对 map 的更改将反映在集合中,反之亦然。
 * 如果在集合的迭代过程中修改了 map(除了用迭代器自己的删除操作之外),迭代的结果是未定义的。
 * 该集合支持删除元素,可以通过 Iteration.remove,Set.remove,removeAll,retainAll
 * 和 clear 操作从 map 中删除相应的映射。它不支持 add 或 addAll 操作。
 */
Set<K> keySet();
/**
 * 返回此 map 中包含所有值的 Collection 集合。
 * 该集合由 map 支持,因此对 map 的更改将反映在集合中,反之亦然。
 * 如果在集合的迭代过程中修改了 map(除了用迭代器自己的删除操作之外),迭代的结果是未定义的。
 * 该集合支持删除元素,可以通过 Iteration.remove,Collection.remove,removeAll,retainAll
 * 和 clear 操作从 map 中删除相应的映射。它不支持 add 或 addAll 操作。
 */
Collection<V> values();
/**
 * 返回此 map 中包含所有映射(Map.Entry)的 Set 集合。
 * 该集合由 map 支持,因此对 map 的更改将反映在集合中,反之亦然。
 * 如果在集合的迭代过程中修改了 map(除了用迭代器自己的删除操作,或
 * 通过迭代器返回的映射中的 setValue 操作之外),迭代的结果是未定义的。

 * 该集合支持删除元素,可以通过 Iteration.remove,Set.remove,removeAll,retainAll
 * 和 clear 操作从 map 中删除相应的映射。它不支持 add 或 addAll 操作。
 */
Set<Map.Entry<K, V>> entrySet();

Entry 是 Map 接口当中的一个内部接口,表示的是一个键值对的映射。其源码如下:

/**
 * map 条目(键值对)。通过 Map.entrySet() 方法可以获取在 Set 集合中其所有的映射条目。
 * 因为是 Set 集合,所以该映射条目也是唯一的。要想获取映射条目的引用唯一的方法就是通过集合的迭代器。
 * 这些 Map.Entry 对象仅在迭代期间有效。如果在迭代器返回条目之后修改了底层映射,
 * 除了通过映射条目上的 setValue 操作,则映射条目的行为是不确定的。
 */
interface Entry<K,V> {
    /**
     * 返回与此条目想对应的键。
     */
    K getKey();

    /**
     * 返回与此条目相对应的值。
     * 如果这个映射已经从底层映射中删除(通过迭代器的 remove 操作),则此调用结果未定义。
     */
    V getValue();

    /**
     * 用指定的值替换此条目相对应的值。(写入 map)
     * 如果这个映射已经从底层映射中删除(通过迭代器的 remove 操作),则此调用结果未定义。
     */
    V setValue(V value);

    /**
     * 将指定的对象与此条目进行比较以获得相等性。
     * 如果给定的对象也是映射条目,并且两个条目表示相同的映射,则返回 true。
     */
    boolean equals(Object o);

    /**
     * 返回此映射条目的哈希码。
     */
    int hashCode();
    // 下面的这几个都是从 Java8 开始提供的静态方法。关于什么是静态方法,可以看看下面提到的文章。

    /**
     * 返回一个以 key 的自然顺序进行比较的比较器。
     * 返回的比较器是可序列化的,并在与为 null 的 key 比较时返回 NullPointerExcetion。
     * 该方法是从 Java8 开始提供的。
     */
    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());
    }

    /**
     * 返回一个以 value 的自然顺序进行比较的比较器。
     * 返回的比较器是可序列化的,并在与为 null 的 value 比较时返回 NullPointerExcetion。
     * 该方法是从 Java8 开始提供的。
     */
    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());
    }

    /**
     * 返回一个使用指定的比较器比较 Map.Entry key 的比较器。
     * 如果指定的比较器是可序列化的,那么返回的比较器也可序列化。
     * 该方法是从 Java8 开始提供的。
     */
    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());
    }

    /**
     * 返回一个使用指定的比较器比较 Map.Entry value 的比较器。
     * 如果指定的比较器是可序列化的,那么返回的比较器也可序列化。
     * 该方法是从 Java8 开始提供的。
     */
    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());
    }
}

比较和散列

/**
 * 将指定的对象与此 map 进行比较以获得相等性。
 * 如果给定的对象也是一个 map,并且两个 map 代表相同的映射,则返回 true。
 */
boolean equals(Object o);
/**
 * 返回此 map 的哈希码。map 的哈希码定义是 map 的 entrySet() 集合中每个条目的哈希码的总和。
 */
int hashCode();

Java 8 新增的默认方法

关于什么是默认方法,大家可以看这篇文章:Java 8 之接口中的默认方法与静态方法

由于 Map 是接口,不可实例化,于是我们用 HashMap 为实例,演示下以下默认方法的使用。
首先,存储几个数据,后面的例子以这些数据为例来演示。

HashMap<Integer, String> map = new HashMap<>();
map.put(1, "a");
map.put(2, "b");
map.put(3, "c");

getOrDefault

/**
 * 如果指定的 key 存在,则返回指定 key 对应的 value。
 * 如果不存在则返回指定的默认 value 值。
 */
default V getOrDefault(Object key, V defaultValue) {
    V v;
    return (((v = get(key)) != null) || containsKey(key))
        ? v
        : defaultValue;
}

例:

System.out.println(map.getOrDefault(1, "d"));// a
System.out.println(map.getOrDefault(4, "d"));// d

forEach

/**
 * 遍历 Map 中所有的 Entry,对 key、value 进行处理。
 *
 * 这个方法的默认实现相当于:
 * for (Map.Entry<K, V> entry : map.entrySet())
 *     action.accept(entry.getKey(), entry.getValue());
 * }
 *
 * 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
 */
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.forEach((key, value) -> System.out.print(key + value));// 1a2b3c

replaceAll

/**
 * 将 map 中每个 Entry 的 value 替换为给定的 value。
 *
 * 这个方法的默认实现相当于:
 * for (Map.Entry<K, V> entry : map.entrySet())
 *     entry.setValue(function.apply(entry.getKey(), entry.getValue()));
 * }
 * 
 * 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
 */
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
    Objects.requireNonNull(function);
    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);
        }

        // ise thrown from function is not a cme.
        v = function.apply(k, v);

        try {
            entry.setValue(v);
        } catch(IllegalStateException ise) {
            // this usually means the entry is no longer in the map.
            throw new ConcurrentModificationException(ise);
        }
    }
}

例:

map.replaceAll((key, value) -> "d" );
map.forEach((key, value) -> System.out.println(key + value));// 1d 2d 3d

putIfAbsent

/**
 * 如果指定的 key 尚未与指定的 value 相关联(或映射到 null),
 * 则将其指定的 value 相关联并返回 null,否则返回当前 value。
 *
 * 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
 */
default V putIfAbsent(K key, V value) {
    V v = get(key);
    if (v == null) {
        v = put(key, value);
    }
    return v;
}

例:

System.out.println(map.putIfAbsent(4, "d"));// null
map.forEach((key, value) -> System.out.println(key + value));// 1a 2b 3c 4d

remove

/**
 * 当指定的 key 映射到指定的 value 时,删除该 Entry。
 *
 * 该方法的默认实现相当于:
 * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
 *     map.remove(key);
 *     return true;
 * } else
 *     return false;
 * }
 *
 * 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
 */
default boolean remove(Object key, Object value) {
    Object curValue = get(key);// 获取指定 key 的映射 value
    if (!Objects.equals(curValue, value) ||// 比对指定的 value 与映射 value 是否是同一个,不是同一个直接返回 false
        (curValue == null && !containsKey(key))) {// 当是同一个时(可能都为 null),再判断映射值是否为 null 并且 key 不存在,返回 false
        return false;
    }
    remove(key);// 删除指定 Entry (抽象方法,由具体实现类实现删除逻辑)
    return true;
}

例:

System.out.println(map.remove(1, "a"));// true
System.out.println(map.remove(4, null));// false
map.forEach((key, value) -> System.out.println(key + value));// 2b 3c

replace

/**
 * 当指定的 key 和 value 是映射关系时,用 newValue 替换指定 value。
 *
 * 该方法的默认实现相当于:
 * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
 *     map.put(key, newValue);
 *     return true;
 * } else
 *     return false;
 * }
 *
 * 如果 oldValue 为 null,则默认实现不会为不支持 null 值的映射抛出 NullPointerException,除非 newValue 也为 null。
 *
 * 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
 */
default boolean replace(K key, V oldValue, V newValue) {
    Object curValue = get(key);
    if (!Objects.equals(curValue, oldValue) ||
        (curValue == null && !containsKey(key))) {
        return false;
    }
    put(key, newValue);
    return true;
}

例:

System.out.println(map.replace(1, "a", "d"));// true
System.out.println(map.replace(2, "c", "e"));// false
map.forEach((key, value) -> System.out.println(key + value));// 1d 2b 3c
/**
 * 当指定 key 的映射 value 不为 null 或 key 存在时,给 key 替换指定的 value,并返回被替换的 value。否则返回 null。 
 * 
 * 该默认实现相当于:
 * if (map.containsKey(key)) {
 *     return map.put(key, value);
 * } else
 *     return null;
 * }
 *
 * 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
 */
default V replace(K key, V value) {
    V curValue;
    if (((curValue = get(key)) != null) || containsKey(key)) {
        curValue = put(key, value);
    }
    return curValue;
}

例:

System.out.println(map.replace(1, "d"));// a
System.out.println(map.replace(4, "d"));// null
map.forEach((key, value) -> System.out.println(key + value));// 1d 2b 3c

coomputeIfAbsent

/**
 * 如果指定的 key 尚未与 value 相关联(或映射到 null),
 * 则尝试使用给定的映射函数计算其 value 并返回,当返回不是 null,则将其输入到此映射。
 *
 * 默认实现相当于:
 * if (map.get(key) == null) {
 *     V newValue = mappingFunction.apply(key);
 *     if (newValue != null)
 *         map.put(key, newValue);
 * }
 *
 * 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
 * 特别的,只有当该值不存在时,子接口 ConcurrentMap 的所有实现类必须记录该函数是否在原子上应用。
 */
default V computeIfAbsent(K key,
        Function<? super K, ? extends V> mappingFunction) {
    Objects.requireNonNull(mappingFunction);
    V v;
    if ((v = get(key)) == null) {
        V newValue;
        if ((newValue = mappingFunction.apply(key)) != null) {
            put(key, newValue);
            return newValue;
        }
    }

    return v;
}

例:

System.out.println(map.computeIfAbsent(1, (key) -> "d"));// a
System.out.println(map.computeIfAbsent(4, (key) -> "d"));// d
map.forEach((key, value) -> System.out.println(key + value));// 1a 2b 3c 4d

computeIfPresent

/**
 * 如果指定的 key 存在并且不为 null(不存在则返回 null),则根据旧的 key 和 value 计算新的 value,如果新 value 不为 null,
 * 则设置 key 的新 value,并返回新 value。否则,删除指定 key 的 Entry,并返回 null。
 * 
 * 默认实现相当于如下操作:
 * if (map.get(key) != null) {
 *     V oldValue = map.get(key);
 *     V newValue = remappingFunction.apply(key, oldValue);
 *     if (newValue != null)
 *         map.put(key, newValue);
 *     else
 *         map.remove(key);
 * }
 *
 * 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
 * 特别的,只有当该值不存在时,子接口 ConcurrentMap 的所有实现类必须记录该函数是否在原子上应用。
 */
default V computeIfPresent(K key,
        BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    Objects.requireNonNull(remappingFunction);
    V oldValue;
    if ((oldValue = get(key)) != null) {
        V newValue = remappingFunction.apply(key, oldValue);
        if (newValue != null) {
            put(key, newValue);
            return newValue;
        } else {
            remove(key);
            return null;
        }
    } else {
        return null;
    }
}

例:

System.out.println(map.computeIfPresent(4, (key, value) -> key + value));// null (不存在指定的 key,直接返回 nullSystem.out.println(map.computeIfPresent(1, (key, value) -> key + value));// 1a (存在指定的 key,将该 key 的值替换为 key + value:1aSystem.out.println(map.computeIfPresent(2, (key, value) -> null));// nullkey 存在,但是 valuenull,于是该 Entry 被删除并返回 nullmap.forEach((key, value) -> System.out.println(key + value));// 11a 3c

compute

/**
 * 根据指定的 key 与映射的 value 计算新的 value,
 * 新 value 不为 null 时,则设置为 key 的新 value,并返回新 value。
 * 否则当旧 value 不为 null 或者 key 存在时,删除 key 对应的 Entry,并返回 null。
 *
 * 默认实现相当于:
 * V oldValue = map.get(key);
 * V newValue = remappingFunction.apply(key, oldValue);
 * if (oldValue != null ) {
 *    if (newValue != null)
 *       map.put(key, newValue);
 *    else
 *       map.remove(key);
 * } else {
 *    if (newValue != null)
 *       map.put(key, newValue);
 *    else
 *       return null;
 * }
 * }
 *
 * 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
 * 特别的,只有当该值不存在时,子接口 ConcurrentMap 的所有实现类必须记录该函数是否在原子上应用。
 */
default V compute(K key,
        BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    Objects.requireNonNull(remappingFunction);
    V oldValue = get(key);
    V newValue = remappingFunction.apply(key, oldValue);
    if (newValue == null) {
        // delete mapping
        if (oldValue != null || containsKey(key)) {
            // something to remove
            remove(key);
            return null;
        } else {
            // nothing to do. Leave things as they were.
            return null;
        }
    } else {
        // add or replace old mapping
        put(key, newValue);
        return newValue;
    }
}

例:

System.out.println(map.compute(4, (key, value) -> key + value));// 4null (指定 key 虽然不存在,但是新 value 不为 null,于是 key 与 新 value 组成一对映射)
System.out.println(map.compute(1, (key, value) -> key + value));// 1a (指定 key 存在,并且新 value 也不为 null,于是设置 keyvalue 为新 valueSystem.out.println(map.compute(2, (key, value) -> null));// null (指定 key 存在,但是新 valuenull,于是删除 key 对应的 Entrymap.forEach((key, value) -> System.out.println(key + value));// 11a 3c 44null

marge

/**
 * 如果指定的 key 尚未与 value 想关联或与 null 相关联,则将其与给定的非空 value 相关联。
 * 否则,将关联的值替换为给定的重映射函数的结果,如果结果为 null,则将其移除。
 *
 * 默认实现相当于:
 * V oldValue = map.get(key);
 * V newValue = (oldValue == null) ? value :
 *              remappingFunction.apply(oldValue, value);
 * if (newValue == null)
 *     map.remove(key);
 * else
 *     map.put(key, newValue);
 * }
 * 
 * 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
 * 特别的,只有当该值不存在时,子接口 ConcurrentMap 的所有实现类必须记录该函数是否在原子上应用。
 */
default V merge(K key, V value,
        BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
    Objects.requireNonNull(remappingFunction);
    Objects.requireNonNull(value);
    V oldValue = get(key);
    V newValue = (oldValue == null) ? value :
               remappingFunction.apply(oldValue, value);
    if(newValue == null) {
        remove(key);
    } else {
        put(key, newValue);
    }
    return newValue;
}

例:

// 指定 key 不存在并且指定 value 不为 null 时,key 与该 value 关联映射,并返回该 value。
System.out.println(map.merge(4, "d", (key, value) -> key + value));// d
// 指定 key 存在但是与指定 value 并不存在映射关系,于是将 key 的 value 设置为新 value,并返回新 value。
System.out.println(map.merge(3, "d", (key, value) -> "new"));// new
// 计算后的新 value 结果为 null,于是移除这个 Entry,并返回 null
System.out.println(map.merge(2, "b", (key, value) -> null));// null
map.forEach((key, value) -> System.out.println    (key + value));// 1a 3new 4d

至此,Map 接口中的所有抽象方法、子接口、静态方法以及默认方法全部都理了一遍。

总结

Map 接口作为映射集合的顶层父接口,里面定义了一些必要的操作方法。其中大部分是抽象的,交由具体实现类根据自身的数据结构作具体的实现。另外还包括了 Java 8 中新引入的默认方法、静态方法。

发布了55 篇原创文章 · 获赞 117 · 访问量 30万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 精致技术 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览