关于Java的Map
总结
Java的Collections Framework提供了许多方便的Map实现。
在这里,我将简要介绍它们。
Collections Framework 中的Map 的完整概述
以下是Collections Framework提供的Map接口、抽象类和具体类的完整内容。
以下是接口的五个选项。
在抽象类中有AbstractMap,它扮演着类似于模板方法的角色。
如果自行分类具象类,可能是这样的。
-
- keyにhashCodeを使う系
HashMap
LinkedHashMap
順序がある系
TreeMap
並行処理系
ConcurrentHashMap
ConcurrentSkipListMap
特殊系
EnumMap
WeakHashMap
IdentityHashMap
使用hashCode来作为键的一类
如果将hashCode用作Map的键,则只要适当实现与键对应的类的hashCode,就可以在O(1)的时间内进行数据搜索。
哈希映射
个人认为这是我经常遇到的最佳Map实现。
看起来可以在key和value中都放入null。
jshell> HashMap<String, String> a = new HashMap<String, String>();
a ==> {}
jshell> a.put(null, null);
$2 ==> null
jshell> a.get(null);
$3 ==> null
jshell> a.size();
链式哈希映射
这里的key和value都可以设置为null。
这个Map可以记住被添加的顺序。
但是,不能覆盖已经在Map中注册的元素的顺序。
jshell> LinkedHashMap<String, String> b = new LinkedHashMap<String, String>();
b ==> {}
jshell> b.put("1", "one");
$6 ==> null
jshell> b.put("2", "two");
$7 ==> null
jshell> for (Map.Entry<String, String> entry: b.entrySet()){
...> System.out.println(entry.getKey());
...> System.out.println(entry.getValue());
...> }
1
one
2
two
jshell> b.put("1", "one");
$9 ==> "one"
jshell> for (Map.Entry<String, String> entry: b.entrySet()){
...> System.out.println(entry.getKey());
...> System.out.println(entry.getValue());
...> }
1
one
2
two
有一個有次序的系統。
树图
这个Map的containsKey、get、put和remove操作的时间复杂度为O(log(n))。
在指定的键值以下,能够轻松地返回最接近该键的条目。这对竞技编程很有帮助。
jshell> c.put(1, "one");
$19 ==> null
jshell> c.put(100, "one hundred");
$20 ==> null
jshell> c.put(200, "two hundred");
$21 ==> null
jshell> c.floorEntry(150).getValue();
$22 ==> "one hundred"
TreeMap无法将null作为键。(可以将null放入值中。)
jshell> c.put(null, "one");
| 例外java.lang.NullPointerException
| at Objects.requireNonNull (Objects.java:208)
| at TreeMap.put (TreeMap.java:809)
| at TreeMap.put (TreeMap.java:534)
| at (#23:1)
jshell> c.put(300, null);
$24 ==> null
并发处理系统
在使用computeIfAbsent和computeIfPresent时,可以执行具有原子性的操作。
并发哈希表
在具有多个线程的情况下,在同时使用computeIfPresent更改value状态的操作中,执行具有原子性的处理。
import java.util.concurrent.ConcurrentHashMap;
public class Main {
// public static HashMap<String, Integer> map = new HashMap<>();
public static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
static {
map.put("key", 1);
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new MyTask());
thread1.start();
Thread thread2 = new Thread(new MyTask());
thread2.start();
thread1.join();
thread2.join();
System.out.println(map.get("key")); //ConcurrentHashMapを利用したら常に 201。HashMap だと不定。
}
}
class MyTask implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
Main.map.computeIfPresent("key", (key, value) -> value + 1);
}
}
}
看起来key和value都不接受null。
jshell> d.put("a", null);
| 例外java.lang.NullPointerException
| at ConcurrentHashMap.putVal (ConcurrentHashMap.java:1011)
| at ConcurrentHashMap.put (ConcurrentHashMap.java:1006)
| at (#27:1)
jshell> d.put(null,"a");
| 例外java.lang.NullPointerException
| at ConcurrentHashMap.putVal (ConcurrentHashMap.java:1011)
| at ConcurrentHashMap.put (ConcurrentHashMap.java:1006)
| at (#28:1)
跳表图
好像是用跳跃表算法实现的。
即使有多个线程同时访问Map,也可以安全地进行注册、更新和删除操作。
似乎在key和value中都不接受null值。
特殊的系别
枚举映射
key只能是Enum类型的Map。
无法将key设置为null,但可以将value设置为null。
jshell> public enum DayOfWeek {
...> MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
...> }
| 次を作成しました: 列挙型 DayOfWeek
jshell> EnumMap<DayOfWeek, String> e = new EnumMap<>(DayOfWeek.class);
e ==> {}
jshell> e.put(null, null);
| 例外java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "key" is null
| at EnumMap.typeCheck (EnumMap.java:736)
| at EnumMap.put (EnumMap.java:264)
| at (#33:1)
jshell> e.put(DayOfWeek.MONDAY, null);
$34 ==> null
弱引用哈希表
这是一个具有这样的性质的Map:当与key相关联的对象不再被其他方式引用时,会触发垃圾回收并从Map中删除。我认为这个页面的解释很容易理解。
在哪里使用?当我调查Spring时,发现它在一个名为AbstractClassGenerator的类中被使用。
private static volatile Map<ClassLoader, ClassLoaderData> CACHE = new WeakHashMap<ClassLoader, ClassLoaderData>();
根据变量名推测,这应该是某种缓存。我决定不再深入追究(因为无法做到)。
WeakHashMap可以在key和value中都放入null。
jshell> WeakHashMap<Integer, String> g = new WeakHashMap<>();
g ==> {}
jshell> g.put(null, "a");
$2 ==> null
jshell> g.put(1, null);
$3 ==> null
jshell> g.size();
$4 ==> 2
身份HashMap
判定 key 是否相匹配的方法在 IdentityHashMap 中与其他 Map 不同。在其他 Map 中,判断两个 key 是否相等是通过 k1.equals(k2) 来判断的,而在 IdentityHashMap 中是通过 k1 == k2 来判断的。
jshell> HashMap<Integer, String> hash = new HashMap<>();
hash ==> {}
jshell> IdentityHashMap<Integer, String> identity = new IdentityHashMap<>();
identity ==> {}
jshell> hash.put(1, "one");
$29 ==> null
jshell> hash.get(new Integer(1));
| 警告:
| java.lang.IntegerのInteger(int)は推奨されておらず、削除用にマークされています
| hash.get(new Integer(1));
| ^------------^
$30 ==> "one"
jshell> identity.put(1, "one");
$31 ==> null
jshell> identity.get(new Integer(1));
| 警告:
| java.lang.IntegerのInteger(int)は推奨されておらず、削除用にマークされています
| identity.get(new Integer(1));
| ^------------^
$32 ==> null
IdentityHashMap可以在key和value中都存放null值。
结束时
我大致浏览了Collections Framework中的Map。挖掘每个Map在具体场景中的使用方式可能会很有趣。