Map

约定

  • Map: 表示 Interface Map
  • map: 表示 Interface Map 的具体实现的一个实例
  • 每个方法后面的 O(f(n)) 表示大部分 map 的实现对应方法的实现的复杂度,但不绝对

引入

Map 是 Interface,定义了用来存储键值对 (key-value pair) 的抽象数据类型 (ADT) 的契约。一般来说,Map 中:

  • 不存在重复的键
  • 每个键最多只能与一个值对应
  • 键的顺序一般不能保证,但部分实现如 TreeMap 可以保证键的顺序

一些值得注意的地方:

  • 可变 (mutable) 对象被用作键时需要额外注意,specification 并没有明确规定对象变化时 map 的行为
  • 所有的 Map 实现一般都有两个构造器
    • 空构造器 (void constructor):构造空的 map
    • 转化构造器 (conversion constructor):按照给定的 map 构造一个新的 map,新旧 map 的实现可以不同。该构造器常常用于不同类型的 map 之间互相转化

声明

public interface Map<K, V> {}

Map 是 generic type,可以接受类别作为其参数,在声明一个新的 Map 类型时,我们可以同时指定其键、值的类别,如:

Map<String, Integer> map = new HashMap<>();

Instance Methods

Query Operations

size - O(1)
int size();

返回 map 中键值对的数量,当 map 含有超过 Integer.MAX_VALUE 数量的键值对时,返回后者

isEmpty - O(1)
boolean isEmpty();

检查 map 是否为空

containsKey - O(1)
boolean containsKey(Object key);

检查 map 中是否包含键为 key 的键值对

containsValue - O(n)
boolean containsValue(Object value);

检查 map 中是否包含值为 value 的键值对

get - O(1)
V get(Object key);

返回 key 对应的值,如果 key 不存在于 map 中则返回 null。对于这个方法,有两个值得注意的点:

为什么不是下面这种形式?
V get(K key);

请参考 What are the reasons why Map.get(Object key) is not (fully) generic,本问题也适用于 containsKey、containsValue、remove 等

如果返回值是 null,怎么判断返回的是值还是告诉我们键值对不存在?

用 containsKey

Modifications

put - O(1)
V put(K key, V value);

如果 map 中不存在键为 key 的键值对,则插入新的键值对 (key, value),否则覆盖原 key 对应的 value。

remove - O(1)
V remove(Object key);

如果 map 中不存在键为 key 的键值对,则返回 null;如果存在则删除该键值对,同时返回被删除键值对的 value。同样,因为可能存在 value 为 null 的情况,因此返回 null 并不代表 map 中一定不存在键为 key 的键值对。

Bulk Operations

putAll - O(k)
void putAll(Map<? extends K, ? extends V> m);

本方法相当于对 m 中的每个键值对调用一次 put,如果 putAll 的过程中 m 发生改变,则行为是不可预测的。注意这里 m 的类型声明。

clear() - O(1) 或 O(n)
void clear();

清空 map 的键值对。注意这里不同类型的 map 相应方法的复杂度不同,请参考 Java: Why does clear() on a HashMap take O(n) time while clear() on a TreeMap takes only O(1) time?

Views

keySet - O(n)
Set<K> keySet();

返回 map 中所有键的集合 (Set) 的视图 (view),这个视图是以 map 为基础构建的,因此当 map 发生变化时 keySet 也将发生变化。但如果在 keySet 的使用过程中,map 发生变化,keySet 的行为不确定。keySet 支持删除元素,对应的键值对也会被删除;keySet 不支持新增元素。

values - O(n)
Collection<V> values();

返回 map 中所有值的集合 (Collection) 的视图,剩下的说明与 keySet 相同。

entrySet - O(n)
Set<Map.Entry<K, V>> entrySet();

返回 map 中所有键值对的集合 (Set) 的视图,剩下的说明与 keySet 相同。

Interface Entry:

Interface entry 是 entrySet 的元素所需实现的 Interface,因此也放在 Views 中讨论:

Entry: getKey - O(1)
K getKey();

返回 entry 的键,如果 map 中对应的键值对被删除,则行为不确定。

Entry: getValue - O(1)
V getValue();

返回 entry 的值,如果 map 中对应的键值对被删除,则行为不确定。

Entry: setValue - O(1)
V setValue(V value);

将 entry 中 key 对应的值修改成 value,因为 map 是 entrySet 的基础,map 中的键值对也要相应地发生改变。如果 map 中对应的键值对被删除,则本操作行为不确定。

Entry: equals - O(1)
boolean equals(Object o);

判断两个 entry 中的 key 和 value 是否两两相等

Entry: hashCode - O(1)
int hashCode();

计算 entry 的 hashCode。specification 中指定了 hashCode 的计算方式,该方式同时保证了如果 entry1.equals(entry2) == true,那么 entry1.hashCode() == entry2.hashCode()。

Entry: static comparingByKey - O(1)
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> 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());
}
  • <K extends Comparable<? super K>, V> 用来指定 Comparator 参数的类别,K 需要实现 Comparable Interface
  • (<Comparator<Map.Entry<K, V>> & Serializable) 指定返回的参数不仅实现 Comparator Interface,而且实现 Serializable Interface

用法示例如下:

// 1.
Map<String, Integer> res1 = unsortMap.entrySet().stream()
    .sorted(Map.Entry.comparingByKey())
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
            (oldValue, newValue) -> oldValue, LinkedHashMap::new));
// 2.
Map<String, Integer> res2 = new LinkedHashMap<>();
unsortedMap.entrySet().stream()
    .sorted(Map.Entry.comparingByKey())
    .forEachOrdered(x -> res2.put(x.getKey(), x.getValue()));
Entry: static comparingByValue - O(1)
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>> comparingByValue(Comparator<? super V> cmp) {
    Objects.requireNonNull(cmp);
    return (Comparator<Map.Entry<K, V>> & Serializable)
        (c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
}

与 comparingByKey 类似。

Map.Entry Interface 的讨论到此结束,继续回到 Map Interface

Comparison and hashing

equals - O(1)
boolean equals(Object o);

判断两个 map 是否相等。specification 里面定义,如果 Map m1 与 Map m2 中储存相同的键值对,也即 m1.entrySet().equals(m2.entrySet()) 返回 true,那么两个 map 相等。同时这也表明,两个 map 是否相等并不取决于它们的实现,不同实现的 map 可能也含有相同的键值对。

hashCode - O(1)
int hashCode();

specification 中定义一个 map 的 hashCode 应该等于它的所有 entry 的 hashCode 的和,这也保证两个相等的 map,它们的 hashCode 也相等。

Defaultable methods

default method 是 Java 8 新增的语言特性,允许在 Interface 定义默认方法,从而减小 Interface 实现者所需做的工作。

getOrDefault - O(1)
default V getOrDefault(Object key, V defaultValue) {
    V v;
    return (((v = get(key)) != null) || containsKey(key))
        ? v
        : defaultValue;
}

此方法意义明确,实现非常精致:

  1. get(key) 的同时赋值来避免二次调用 get(key)
  2. (((v = get(key)) != null) || containsKey(key)) 很巧妙地表达了两种 case
forEach - O(n)
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);
    }
}

forEach 依次对 entrySet 中的每一个 entry 执行 action 操作,当 entry.getKey() 或 entry.getValue() 发生错误时,一般是背后 map 的键值被修改或删除。

replaceAll - O(n)
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) {
            throw new ConcurrentModificationException(ise);
        }

        v = function.apply(k, v);

        try {
            entry.setValue(v);
        } catch(IllegalStateException ise) {
            throw new ConcurrentModificationException(ise);
        }
    }
}

replaceAll 依次将 entrySet 的每个 entry 的 value 用 function 转化成另一个与 value 类别兼容的值,两个 try catch 尽最大努力做到及早发现并发修改导致的错误。

putIfAbsent - O(1)
default V putIfAbsent(K key, V value) {
    V v = get(key);
    if (v == null) {
        v = put(key, value);
    }
    return v;
}

顾名思义

  • 当 key 不在 map 中时,将 key, value 插入到 map 中
  • 当 key 在 map 但对应的值为 null 时,用 value 覆盖原来的 null
  • 当 key 在 map 中且对应的值不为 null 时,不变
remove - O(1)
default boolean remove(Object key, Object value) {
    Object curValue = get(key);
    if (!Objects.equals(curValue, value) ||
         (curValue == null && !containsKey(key)) {
        return false;
    }
    remove(key);
    return true;
}

当 map 中存在 key, value 时,则把它们的 mapping 关系从 map 中删除。

replace (K key, V oldValue, V newValue) - O(1)
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;
}

当 map 中存在 key, oldValue 时,则把它们的 mapping 关系改成 key, value。

replace (K key, V value) - O(1)
default V replace(K key, V value) {
    V curValue;
    if (((curValue = get(key)) != null) || containsKey(key)) {
        curValue = put(key, value);
    }
    return curValue;
}

当 map 中存在 key 时,则把 key 对应的值替换成 value

computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) - O(1)
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;
}

当 map 中不存在键 key 或 key 对应的值为 null,且 mappingFunction.apply(key) 的返回值不为 null 时,将 key 与 newValue 的 mapping 关系存入 map 中

computeIfAbsent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) - O(1)
default V computeIfAbsent(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;
    }
}

当 map 中不存在键 key 或 key 对应的值为 null 时,计算 remappingFunction.apply(key, oldValue) 的结果,如果返回 null,则从 map 中删除 key, oldValue 的 mapping 关系;如果返回非 null 值 newValue,则将 key, newValue 的 mapping 关系存入 map 中。

compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) - O(1)
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)) {
            remove(key);
            return null;
        } else {
            return null;
        }
    } else {
        put(key, newValue);
        return newValue;
    }
}

如果 newValue 是 null,则删除 key, oldValue 的 mapping 关系;否则存入 key, newValue 的 mapping 关系。

merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction - O(1)
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;
}

这个操作常常被用来 merge 两个含有共同 key 不同 value 的 map。如果 key 尚不存在,则直接添加 mapping,否则使用 remappingFunction 对同 key 对应的不同 value 进行合理组合。

小结

看来平时忽略了很多 Map Interface 的 default implementations...

参考

results matching ""

    No results matching ""