一、Map的用法:
1. 类型介绍:Java自带了各种 Map 类,这些 Map 类可以归为三种类型:
(1) 通用 Map:用于在应用程序中管理映射,通常在 java.util 程序包中实现;分别是:HashMap、HashTable、Properties、LinkedHashMap、IdentityHashMap、TreeMap、WeakHashMap、ConcurrentHashMap;
(2) 专用 Map:通常使用者不必亲自创建此类 Map,而是通过某些其他类对其进行访问:
java.util.jar.Attributes、javax.print.attribute.standard.PrinterStateReasons、java.security.Provider、java.awt.RenderingHints、javax.swing.UIDefaults;
(3) 自行实现 Map:一个用于帮助我们实现自己的 Map 类的抽象类 AbstractMap;
二、部分常用类型的区别:
(1) HashMap:HashMap是最常用的 Map,它根据键的 HashCode 值存储数据,根据键可以直接获取其对应的值;具有很快的访问速度。HashMap 最多只允许一条记录的键为 null(多条将会依次覆盖);允许多条记录的值为 null。非同步的。
(2) TreeMap:能够把它保存的记录根据键 (key) 排序,默认是按升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排序过的。TreeMap 不允许 key 的值为 null。非同步的。
(3) HashTable:与 HashMap 类似,不同的是,key 和 value 的值均不允许为 null;它支持线程的同步,即,任一时刻只有一个线程能写 HashTable,因此也导致了 HashTable 在写入时会比较慢。
(4) LinkedHashMap:保存了记录的插入顺序,在用 Iterator 遍历 LinkedHashMap 时,先得到的记录肯定是先插入的。在遍历的时候会比 HashMap 慢。key 和 value 均允许为 null。非同步的 。
三、Map 初始化及增、删、获取、清空元素:
// 初始化 Map
Map<String, String> map = new HashMap<String, String>();
// 插入元素
map.put("key1", "value1");
map.put("key2", "value2");
// 获取元素
map.get("key1");
// 删除元素
map.remove("key2");
// 清空map
map.clear();
四、四种常用 Map 插入与获取性能比较:
测试比较代码:
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
import java.util.UUID;
public class MainTestClass {
static int hashMap_W = 0;
static int hashMap_R = 0;
static int linkedHashMap_W = 0;
static int linkedHashMap_R = 0;
static int treeMap_W = 0;
static int treeMap_R = 0;
static int hashTable_W = 0;
static int hashTable_R = 0;
public static void main(String[] args) {
for(int i=0; i<10; i++){
MainTestClass mtc = new MainTestClass();
mtc.testMethod(100 * 10000);
System.out.println();
}
System.out.println("hashMap_W = " + hashMap_W / 10);
System.out.println("hashMap_R = " + hashMap_R / 10);
System.out.println("linkedHashMap_W = " + linkedHashMap_W / 10);
System.out.println("linkedHashMap_R = " + linkedHashMap_R / 10);
System.out.println("treeMap_W = " + treeMap_W / 10);
System.out.println("treeMap_R = " + treeMap_R / 10);
System.out.println("hashTable_W = " + hashTable_W / 10);
System.out.println("hashTable_R = " + hashTable_R / 10);
}
public void testMethod(int size){
int index;
Random rand = new Random();
String[] key = new String[size];
// HashMap 插入数据
Map<String, String> map = new HashMap<String, String>();
long start = System.currentTimeMillis();
for(int i=0; i<size; i++){
key[i] = UUID.randomUUID().toString();
map.put(key[i], UUID.randomUUID().toString());
}
long end = System.currentTimeMillis();
hashMap_W += (end - start);
System.out.println("HashMap插入数据耗时 = " + (end - start) + "ms");
// HashMap 获取数据
start = System.currentTimeMillis();
for(int i=0; i<size; i++){
index = rand.nextInt(size);
map.get(key[index]);
}
end = System.currentTimeMillis();
hashMap_R += (end - start);
System.out.println("HashMap获取数据耗时 = " + (end - start) + "ms");
// LinkedHashMap 插入数据
map = new LinkedHashMap<String, String>();
start = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
key[i] = UUID.randomUUID().toString();
map.put(key[i], UUID.randomUUID().toString());
}
end = System.currentTimeMillis();
linkedHashMap_W += (end - start);
System.out.println("LinkedHashMap插入数据耗时 = " + (end - start) + "ms");
// LinkedHashMap 获取数据
start = System.currentTimeMillis();
for(int i=0; i<size; i++){
index = rand.nextInt(size);
map.get(key[index]);
}
end = System.currentTimeMillis();
linkedHashMap_R += (end - start);
System.out.println("LinkedHashMap获取数据耗时 = " + (end - start) + "ms");
// TreeMap 插入数据
key = new String[size];
map = new TreeMap<String, String>();
start = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
key[i] = UUID.randomUUID().toString();
map.put(key[i], UUID.randomUUID().toString());
}
end = System.currentTimeMillis();
treeMap_W += (end - start);
System.out.println("TreeMap插入数据耗时 = " + (end - start) + "ms");
// TreeMap 获取数据
start = System.currentTimeMillis();
for(int i=0; i<size; i++){
index = rand.nextInt(size);
map.get(key[index]);
}
end = System.currentTimeMillis();
treeMap_R += (end - start);
System.out.println("TreeMap获取数据耗时 = " + (end - start) + "ms");
// HashTable 插入数据
key = new String[size];
map = new Hashtable<String, String>();
start = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
key[i] = UUID.randomUUID().toString();
map.put(key[i], UUID.randomUUID().toString());
}
end = System.currentTimeMillis();
hashTable_W += (end - start);
System.out.println("Hashtable插入数据耗时 = " + (end - start) + "ms");
// HashTable 获取数据
start = System.currentTimeMillis();
for(int i=0; i<size; i++){
index = rand.nextInt(size);
map.get(key[index]);
}
end = System.currentTimeMillis();
hashTable_R += (end - start);
System.out.println("Hashtable获取数据耗时 = " + (end - start) + "ms");
}
}
测试:分别向四种Map集合中插入/取出1W条、10W条、100W条数据,每一数据量循环10次,分别取十次的平均时间;分别测试两轮,测试结果如下表所示(单位为:ms):
插入10次平均时间(ms):
1W | 10W | 100W | ||||
第一轮 | 第二轮 | 第一轮 | 第二轮 | 第一轮 | 第二轮 | |
HashMap | 69 | 57 | 252 | 226 | 2249 | 2272 |
LinkedHashMap | 22 | 18 | 190 | 193 | 2220 | 2233 |
TreeMap | 25 | 21 | 267 | 248 | 2805 | 2859 |
Hashtable | 21 | 18 | 185 | 171 | 2134 | 2068 |
获取10次平均时间(ms) :
1W | 10W | 100W | ||||
第一轮 | 第二轮 | 第一轮 | 第二轮 | 第一轮 | 第二轮 | |
HashMap | 1 | 1 | 16 | 15 | 198 | 202 |
LinkedHashMap | 1 | 1 | 15 | 15 | 191 | 192 |
TreeMap | 5 | 4 | 92 | 96 | 1078 | 1069 |
Hashtable | 1 | 1 | 16 | 17 | 206 | 225 |
五、Map 遍历方法:
初始化数据:
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
map.put("key4", "value4");
(1) 增强 for 循环遍历
① 使用 keySet() 遍历:
for(String key : map.keySet()){
System.out.println(key + " : " + map.get(key));
}
② 使用 entrySet() 遍历:
for(Map.Entry<String, String> entry : map.entrySet()){
System.out.println(entry.getKey() + " : " + entry.getValue());
}
(2) 迭代器遍历
① 使用 keySet() 遍历:
Iterator<String> iterator = map.keySet().iterator();
while(iterator.hasNext()){
String key = iterator.next();
System.out.println(key + " : " + map.get(key));
}
② 使用 entrySet() 遍历:
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());
}
六、HashMap 四种遍历方式性能比较
比较方式:分别对四种遍历方式进行10W次迭代,比较用时。
测试比较代码:
package com.sztxtech.testmap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
/**
* 测试Map遍历速度
*/
public class TestMapSpeed {
public static void main(String[] args) {
// 初始化Map,并进行10W次赋值
Map<Integer, String> map = new HashMap<Integer, String>();
for (int i = 0; i < 100000; i++) {
map.put(i, UUID.randomUUID().toString());
}
long start = System.currentTimeMillis();
/* 增强for循环,keySet迭代 */
for(Integer key : map.keySet()){
map.get(key);
}
long end = System.currentTimeMillis();
System.out.println("增强 for 循环,keySet 迭代 --> "+(end-start)+"ms");
/* 增强for循环,entrySet迭代 */
start = System.currentTimeMillis();
for(Entry<Integer, String> entry : map.entrySet()){
entry.getKey();
entry.getValue();
}
end = System.currentTimeMillis();
System.out.println("增强 for 循环,entrySet 迭代 --> "+(end-start)+"ms");
/* 迭代器,keySet迭代 */
start = System.currentTimeMillis();
Iterator<Integer> iterator = map.keySet().iterator();
Integer key;
while(iterator.hasNext()){
key = iterator.next();
map.get(key);
}
end = System.currentTimeMillis();
System.out.println("迭代器,keySet迭代 --> "+(end-start)+"ms");
/* 迭代器,entrySet迭代 */
start = System.currentTimeMillis();
Iterator<Map.Entry<Integer, String>> iterators = map.entrySet().iterator();
Map.Entry<Integer, String> entry;
while(iterators.hasNext()){
entry = iterators.next();
entry.getKey();
entry.getValue();
}
end = System.currentTimeMillis();
System.out.println("迭代器,entrySet迭代 --> "+(end-start)+"ms");
}
}
以上测试代码分别运行三次的测试结果如下:
遍历方式 | 第一次 | 第二次 | 第三次 |
增强for循环,keySet迭代 | 13ms | 10ms | 10ms |
增强for循环,entrySet迭代 | 7ms | 5ms | 4ms |
迭代器,keySet迭代 | 3ms | 6ms | 7ms |
迭代器,entrySet迭代 | 3ms | 2ms | 4ms |
根据以上测试结果得出结论:
① 增强 for 循环使用方便,但性能较差,不适合处理超大量级的数据;
② 迭代器的遍历速度要比增强 for 循环快很多,是增强 for 循环的 2 倍左右;
③ 使用 entrySet 遍历的速度要比 keySet 快很多,是 keySet 的 1.5 倍左右;
七、Map 排序
(1) HashMap、Hashtable、LinkedHashMap 排序;
注:TreeMap 也可以使用此方法进行排序,但是更推荐下面的方法:
Map<String, String> map = new HashMap<String, String>();
map.put("cccc", "aaaa");
map.put("bbbb", "bbbb");
map.put("aaaa", "cccc");
map.put("eeee", "dddd");
map.put("dddd", "ffff");
// 通过 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>>(){
@Override
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());
}
(2) TreeMap 排序;
TreeMap 默认按 key 进行升序排序,如果想改变默认的顺序,可以使用比较器:
Map<String, String> map = new TreeMap<String, String>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);// 降序排序
// return o1.compareTo(o2);// 升序排序
}
});
map.put("cccc", "aaaa");
map.put("bbbb", "bbbb");
map.put("aaaa", "cccc");
map.put("eeee", "dddd");
map.put("dddd", "ffff");
for(String key : map.keySet()) {
System.out.println(key + " : " + map.get(key));
}
(2) 按 value 排序,通用方法;
Map<String, String> map = new TreeMap<String, String>();
map.put("张三", "dddd");
map.put("李四", "cccc");
map.put("王五", "ffff");
map.put("马六", "bbbb");
// 通过 ArrayList 构造函数将 map.entrySet() 转换成 list
List<Entry<String, String>> list = new ArrayList<Entry<String, String>>(map.entrySet());
// 通过比较器实现比较排序
Collections.sort(list, new Comparator<Map.Entry<String, String>>() {
@Override
public int compare(Entry<String, String> o1, Entry<String, String> o2) {
// TODO Auto-generated method stub
return o1.getValue().compareTo(o2.getValue());
}
});
for(Entry<String, String> entry : list) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
八、常用 API
方法 | 描述 |
clear() | 从 Map 中删除/清空所有映射 |
remove(Object key) | 从 Map 中删除键所对应的值 |
put(Object key, Object value) | 将指定的值与键相关联并存入 Map |
putAll(Map map) | 将指定的 Map 中的所有映射复制到新的 Map 中 |
entrySet() | 返回 Map 中所包含映射的 Set 视图。Set 中的每个元素都是一个 Map.Entry 对象,可以使用 getKey() 和 getValue()方法(还有一个 setValue()方法)访问后者的键元素和值元素 |
keySet() | 返回 Map 中所包含键的 Set 视图。删除 Set 中的元素也将删除 Map 中相应的映射(键值对) |
values() | 返回 Map 中所包含值的 Collection 视图。删除 Collection 中的元素也将删除 Map 中相应的映射(键值对) |
get(Object key) | 返回与指定键所关联的映射 |
containsKey(Object key) | 如果 Map 包含指定键的映射,则返回 true |
containsValue(Object value) | 如果此 Map 将一个或多个键映射到指定的值,则返回 true |
isEmpty() | 如果 Map 没有任何键-值映射,则返回 true |
size() | 返回 Map 中的键-值映射的数目 |