0%

java | Map

java 原生 map 解析。

  • HashMap
  • TreeMap
  • LinkedHashMap
  • Hashtable

环境说明


  • java 14

通用用法


方法名称 说明
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) 返回与指定键关联的值
getOrDefault(Object key,String defaultValue) 返回指定键,如果没有返回预定值
containsKey(Object key) 如果 Map 包含指定键的映射,则返回 true
containsValue(Object value) 如果此 Map 将一个或多个键映射到指定值,则返回 true
isEmpty() 如果 Map 不包含键-值映射,则返回 true
size() 返回 Map 中的键-值映射的数目

各类型区别


  • HashMap
    • 最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为Null(多条会覆盖);允许多条记录的值为 Null。非同步的。
  • TreeMap
    • 能够把它保存的记录根据键(key)排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。TreeMap不允许key的值为null。非同步的。
  • Hashtable
    • HashMap类似,不同的是:keyvalue的值均不允许为null;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢。
  • LinkedHashMap
    • 保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.在遍历的时候会比HashMap慢。keyvalue均允许为空,非同步的。

四种常用Map插入与读取性能比较

网上数据

插入10次平均(ms) 插入10次平均(ms) 插入10次平均(ms) 读取10次平均(ms) 读取10次平均(ms) 读取10次平均(ms)
1W 10W 100W 1W 10W 100W
HashMap 56 261 3030 2 21 220
LinkedHashMap 25 229 3069 2 20 216
TreeMap 29 295 4117 5 103 1446
Hashtable 24 234 3275 2 22 259

初始化


  • 常态方法
  • 匿名内部类方法
  • collections 实现以及其他
  • 使用guava

常态方法

1
2
3
Map<String, String> m = new LinkedHashMap<>();
m.put("1", "test1");
m.put("2", "test2");

匿名内部类

1
2
3
4
Map<String, String> m = new LinkedHashMap<>(){{
put("1","test1");
put("2","test2");
}};

我们必须避免使用这种初始化技术,因为它在每次使用时都会创建一个匿名的额外类,并且还包含对封闭对象的隐藏引用,这可能会导致内存泄漏问题。

collections 实现

单例 Map

1
Map<String,String> m = Collections.singletonMap("1","test1");

这里的 map 是不可变的,如果我们尝试添加更多条目,它将抛出

java.lang.UnsupportedOperationException

Collections.emptyMap()创建一个不变的空地图 :

1
Map<String, String> emptyMap = Collections.emptyMap();

java 8

java 8 的实现方式会造成巨大开销,如使用 Streams 进行此类初始化。

感兴趣的可以看下面的文章

java 9

Map.of()

Java 9Map 界面中附带了各种工厂方法 ,从而简化了不可变 map 的创建和初始化。

1
2
3
Map<String, String> emptyMap = Map.of();
Map<String, String> singletonMap = Map.of("key1", "value");
Map<String, String> map = Map.of("key1","value1", "key2", "value2");

ps:

  • 此方法最多仅支持10个键值对
  • map 都是不可变的

Map.ofEntries()

它与 Map.of()类似, 但对键值对的数量没有限制:

1
2
3
4
5
6
 Map<String, String> map = Map.ofEntries(
new AbstractMap.SimpleEntry<String, String>("name", "John"),
new AbstractMap.SimpleEntry<String, String>("city", "budapest"),
new AbstractMap.SimpleEntry<String, String>("zip", "000000"),
new AbstractMap.SimpleEntry<String, String>("home", "1231231231")
);

请注意,工厂方法会生成不可变的映射,因此任何突变都将导致 UnsupportedOperationException

而且,它们不允许空键和重复键。

现在,如果在初始化后需要可变的或正在增长的地图,则可以创建Map接口的任何实现, 并将这些不可变的地图传递给构造函数:

1
2
3
4
5
6
Map<String, String> map = new HashMap<String, String> (
Map.of("key1","value1", "key2", "value2"));
Map<String, String> map2 = new HashMap<String, String> (
Map.ofEntries(
new AbstractMap.SimpleEntry<String, String>("name", "John"),
new AbstractMap.SimpleEntry<String, String>("city", "budapest")));

使用guava

现在,当我们研究了使用核心Java的方式时,让我们继续使用Guava库初始化地图:

1
2
Map<String, String> articles 
= ImmutableMap.of("Title", "My New Article", "Title2", "Second Article");

这将创建一个不变的 map

创建一个可变的 map

1
2
Map<String, String> articles 
= Maps.newHashMap(ImmutableMap.of("Title", "My New Article", "Title2", "Second Article"));

[ImmutableMap.of()] 方法 还具有重载版本,最多可以包含5对键值参数。这是带有两个参数对的示例:

ImmutableMap.of("key1", "value1", "key2", "value2");

超过5个 key value

1
2
3
4
5
Map<String, String> test = ImmutableMap.<String, String>builder()
.put("k1", "v1")
.put("k2", "v2")
...
.build();

遍历


增强for循环遍历

使用keySet()遍历

1
2
3
for (String key : map.keySet()) {
System.out.println(key + " :" + map.get(key));
}

使用entrySet()遍历

1
2
3
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " :" + entry.getValue());
}

迭代器遍历

使用keySet()遍历

1
2
3
4
5
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
System.out.println(key + " :" + map.get(key));
}

使用entrySet()遍历

1
2
3
4
5
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
System.out.println(entry.getKey() + " :" + entry.getValue());
}

有人专门比较过他们的性能

  • 增强for循环使用方便,但性能较差,不适合处理超大量级的数据
  • 迭代器的遍历速度要比增强for循环快很多,是增强for循环的2倍左右
  • 使用entrySet遍历的速度要比keySet快很多,是keySet1.5倍左右

Map 排序


HashMapHashtableLinkedHashMap排序

ps:TreeMap也可以使用此方法进行排序,但是更推荐下面的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Map<String, String> map = new HashMap<String, String>();
map.put("a", "c");
map.put("b", "b");
map.put("c", "a");

// 通过ArrayList构造函数把map.entrySet()转换成list
List<Map.Entry<String, String>> list = new ArrayList<Map.Entry<String, String>>(map.entrySet());
// 通过比较器实现比较排序
Collections.sort(list, new Comparator<Map.Entry<String, String>>() {
public int compare(Map.Entry<String, String> mapping1, Map.Entry<String, String> mapping2) {
return mapping1.getKey().compareTo(mapping2.getKey());
}
});

for (Map.Entry<String, String> mapping : list) {
System.out.println(mapping.getKey() + " :" + mapping.getValue());
}

TreeMap排序

TreeMap默认按key进行升序排序,如果想改变默认的顺序,可以使用比较器:

1
2
3
4
5
6
7
8
9
10
11
12
Map<String, String> map = new TreeMap<String, String>(new Comparator<String>() {
public int compare(String obj1, String obj2) {
return obj2.compareTo(obj1);// 降序排序
}
});
map.put("a", "c");
map.put("b", "b");
map.put("c", "a");

for (String key : map.keySet()) {
System.out.println(key + " :" + map.get(key));
}

按value排序(通用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Map<String, String> map = new TreeMap<String, String>();
map.put("a", "c");
map.put("b", "b");
map.put("c", "a");

// 通过ArrayList构造函数把map.entrySet()转换成list
List<Map.Entry<String, String>> list = new ArrayList<Map.Entry<String, String>>(map.entrySet());
// 通过比较器实现比较排序
Collections.sort(list, new Comparator<Map.Entry<String, String>>() {
public int compare(Map.Entry<String, String> mapping1, Map.Entry<String, String> mapping2) {
return mapping1.getValue().compareTo(mapping2.getValue());
}
});

for (String key : map.keySet()) {
System.out.println(key + " :" + map.get(key));
}
请我喝杯咖啡吧~