Map大家族的那点事儿 :Map

Posted ssming

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Map大家族的那点事儿 :Map相关的知识,希望对你有一定的参考价值。

Map是一种用于快速查找的数据结构,它以键值对的形式存储数据,每一个键都是唯一的,且对应着一个值,如果想要查找Map中的数据,只需要传入一个键,Map会对键进行匹配并返回键所对应的值,可以说Map其实就是一个存放键值对的集合。Map被各种编程语言广泛使用,只不过在名称上可能会有些混淆,像Python中叫做字典(Dictionary),也有些语言称其为关联数组(Associative Array),但其实它们都是一样的,都是一个存放键值对的集合。至于Java中经常用到的HashMap也是Map的一种,它被称为散列表,关于散列表的细节我会在本文中解释HashMap的源码时提及。

Java还提供了一种与Map密切相关的数据结构:Set,它是数学意义上的集合,特性如下:

  • 无序性:一个集合中,每个元素的地位都是相同的,元素之间也都是无序的。不过Java中也提供了有序的Set,这点倒是没有完全遵循。
  • 互异性:一个集合中,任何两个元素都是不相同的。
  • 确定性:给定一个集合以及其任一元素,该元素属于或者不属于该集合是必须可以确定的。

很明显,Map中的key就很符合这些特性,Set的实现其实就是在内部使用Map。例如,HashSet就定义了一个类型为HashMap的成员变量,向HashSet添加元素a,等同于向它内部的HashMap添加了一个key为a,value为一个Object对象的键值对,这个Object对象是HashSet的一个常量,它是一个虚拟值,没有什么实际含义,源码如下:

1
2
3
4
5
6
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

小插曲过后,让我们接着说Map,它是JDK的一个顶级接口,提供了三种集合视图(Collection Views):包含所有key的集合、包含所有value的集合以及包含所有键值对的集合,Map中的元素顺序与它所返回的集合视图中的元素的迭代顺序相关,也就是说,Map本身是不保证有序性的,当然也有例外,比如TreeMap就对有序性做出了保证,这主要因为它是基于红黑树实现的。

所谓的集合视图就是由集合本身提供的一种访问数据的方式,同时对视图的任何修改也会影响到集合。好比Map.keySet()返回了它包含的key的集合,如果你调用了Map.remove(key)那么keySet.contains(key)也将返回false,再比如说Arrays.asList(T)可以把一个数组封装成一个List,这样你就可以通过List的API来访问和操作这些数据,如下列示例代码:

1
2
3
4
5
6
7
String[] strings = {"a", "b", "c"};
List<String> list = Arrays.asList(strings);
System.out.println(list.get(0)); // "a"
strings[0] = "d";
System.out.println(list.get(0)); // "d"
list.set(0, "e");
System.out.println(strings[0]); // "e"

是不是感觉很神奇,其实Arrays.asList()只是将传入的数组与Arrays中的一个内部类ArrayList(注意,它与java.util包下的ArrayList不是同一个)做了一个”绑定“,在调用get()时会直接根据下标返回数组中的元素,而调用set()时也会直接修改数组中对应下标的元素。相对于直接复制来说,集合视图的优点是内存利用率更高,假设你有一个数组,又很想使用List的API来操作它,那么你不用new一个ArrayList以拷贝数组中的元素,只需要一点额外的内存(通过Arrays.ArrayList对数组进行封装),原始数据依然是在数组中的,并不会复制成多份。

Map接口规范了Map数据结构的通用API(也含有几个用于简化操作的default方法,default是JDK8的新特性,它是接口中声明的方法的默认实现,即非抽象方法)并且还在内部定义了Entry接口(键值对的实体类),在JDK中提供的所有Map数据结构都实现了Map接口,下面为Map接口的源码(代码中的注释太长了,基本都是些实现的规范,为了篇幅我就尽量省略了)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
package java.util;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.io.Serializable;
public interface Map<K,V> {
 
    // 查询操作
    /**
     * 返回这个Map中所包含的键值对的数量,如果大于Integer.MAX_VALUE,
     * 则应该返回Integer.MAX_VALUE。
     */
    int size();
    /**
     * Map是否为空。
     */
    boolean isEmpty();
    /**
     * Map中是否包含key,如果是返回true,否则false。
     */
    boolean containsKey(Object key);
    /**
     * Map中是否包含value,如果是返回true,否则false。
     */
    boolean containsValue(Object value);
    /**
     * 根据key查找value,如果Map不包含该key,则返回null。
     */
    V get(Object key);
    // 修改操作
    /**
     * 添加一对键值对,如果Map中已含有这个key,那么新value将覆盖掉旧value,
     * 并返回旧value,如果Map中之前没有这个key,那么返回null。
     */
    V put(K key, V value);
    /**
     * 删除指定key并返回之前的value,如果Map中没有该key,则返回null。
     */
    V remove(Object key);
    // 批量操作
    /**
     * 将指定Map中的所有键值对批量添加到当前Map。
     */
    void putAll(Map<? extends K, ? extends V> m);
    /**
     * 删除Map中所有的键值对。
     */
    void clear();
    // 集合视图
    /**
     * 返回包含Map中所有key的Set,对该视图的所有修改操作会对Map产生同样的影响,反之亦然。
     */
    Set<K> keySet();
    /**
     * 返回包含Map中所有value的集合,对该视图的所有修改操作会对Map产生同样的影响,反之亦然。
     */
    Collection<V> values();
    /**
     * 返回包含Map中所有键值对的Set,对该视图的所有修改操作会对Map产生同样的影响,反之亦然。
     */
    Set<Map.Entry<K, V>> entrySet();
    /**
     * Entry代表一对键值对,规范了一些基本函数以及几个已实现的类函数(各种比较器)。
     */
    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());
        }
    }
    // 比较和hashing
    /**
     * 将指定的对象与此Map进行比较是否相等。
     */
    boolean equals(Object o);
    /**
     * 返回此Map的hash code。
     */
    int hashCode();
    // 默认方法(非抽象方法)
    /**
     * 根据key查找value,如果该key不存在或等于null则返回defaultValue。
     */
    default V getOrDefault(Object key, V defaultValue) {
        V v;
        return (((v = get(key)) != null) || containsKey(key)) ? v : defaultValue;
    }
    /**
     * 遍历Map并对每个键值对执行指定的操作(action)。
     * BiConsumer是一个函数接口(具有一个抽象方法的接口,用于支持Lambda),
     * 它代表了一个接受两个输入参数的操作,且不返回任何结果。
     * 至于它奇怪的名字,根据Java中的其他函数接口的命名规范,Bi应该是Binary的缩写,意思是二元的。
     */
    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,然后调用传入的函数function生成新value对旧value进行替换。
     * BiFunction同样是一个函数接口,它接受两个输入参数并且返回一个结果。
     */
    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);
            }
        }
    }
    /**
     * 如果指定的key不存在或者关联的value为null,则添加键值对。
     */
    default V putIfAbsent(K key, V value) {
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }
        return v;
    }
    /**
     * 当指定key关联的value与传入的参数value相等时删除该key。
     */
    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;
    }
    /**
     * 当指定key关联的value与oldValue相等时,使用newValue进行替换。
     */
    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;
    }
    /**
     * 当指定key关联到某个value时进行替换。
     */
    default V replace(K key, V value) {
        V curValue;
        if (((curValue = get(key)) != null) || containsKey(key)) {
            curValue = put(key, value);
        }
        return curValue;
    }
    /**
     * 当指定key没有关联到一个value或者value为null时,调用mappingFunction生成值并添加键值对到Map。
     * Function是一个函数接口,它接受一个输入参数并返回一个结果,如果mappingFunction返回的结果
     * 也为null,那么将不会调用put。
     */
    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;
    }
    /**
     * 当指定key关联到一个value并且不为null时,调用remappingFunction生成newValue,
     * 如果newValue不为null,那么进行替换,否则删除该key。
     */
    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;
        }
    }
    /**
     * remappingFunction根据key与其相关联的value生成newValue,
     * 当newValue等于null时删除该key,否则添加或者替换旧的映射。
     */
    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;
        }
    }
    /**
     * 当指定key没有关联到一个value或者value为null,将它与传入的参数value
     * 进行关联。否则,调用remappingFunction生成newValue并进行替换。
     * 如果,newValue等于null,那么删除该key。
     */
    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;
    }
}

需要注意一点,这些default方法都是非线程安全的,任何保证线程安全的扩展类都必须重写这些方法,例如ConcurrentHashMap。

以上是关于Map大家族的那点事儿 :Map的主要内容,如果未能解决你的问题,请参考以下文章

ES6 Set和Map的那点事

IMU的那点事儿

关于代码质量的那点事儿

关于SSL/TLS的那点事儿

关于SSL/TLS的那点事儿

mysql的那点事儿,你知道吗