文章目录
- Map接口概述
- Map接口和Collection接口的不同
- Map接口的方法列表
- 部分方法测试
- Map接口的实现类
- HashMap
- LinkedHashMap
- TreeMap
- Hashtable与ConcurrentHashMap
- Map集合关系粗略图
Map接口概述
- 将键映射到值的对象
- 不能包含重复的键
- 每个键最多只能映射到一个值
Map接口和Collection接口的不同
- Map是双列的,Collection是单列的
- Map的键唯一,Collection的子体系Set是唯一的(实际上Set就是Map的键)
- Map集合的数据结构值针对键有效,跟值无关,Collection集合的数据结构是针对元素有效
Map接口的方法列表
注:部分方法的作用解释可能存在偏差
方法 | 作用 |
int size(); | 返回map中 键值对 的个数 |
boolean isEmpty(); | 判断map是否为空(长度为0) |
boolean containsKey(Object key); | 判断map中是否包含给定键 |
boolean containsValue(Object value); | 判断map中是否包含给定值 |
V get(Object key); | 根据给定键,获取对应值 |
V put(K key, V value); | ,若key不存在,则添加一对键值对,返回null;若key已存在则替换其value,返回旧的value |
V remove(Object key); | 根据键移除对应键值对 |
void putAll(Map<? extends K, ? extends V> m); | 添加或设置给定Map所包含的所有键值对 |
void clear(); | 移除所有键值对 |
Set keySet(); | 获取以所有键为元素的Set集合 |
Collection values() | 获取以所有值为元素的Collection集合 |
Set<Map.Entry<K, V>> entrySet(); | 获取map中所有键值对对象的set集合 |
default V getOrDefault(Object key, V defaultValue) | 返回给定key对应的值,若给定key不存在或值为null则返回给定值defaultValue(1.8新增) |
default void forEach(BiConsumer<? super K, ? super V> action) | 遍历map,将每个键值对按照给定消费型函数规则改变(1.8新增) |
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) | 遍历map中每个键值对对象,根据给定函数型函数规则替换键值对对象的 值 (1.8新增) |
default V putIfAbsent(K key, V value) | 如果map中不存在给定的key,或给定的key对应的值为null,则添加,否则返回原本的value(1.8新增) |
default boolean remove(Object key, Object value) | 若map中已存在给定的key-value键值对则移除,返回true;否则返回false(1.8新增) |
default boolean replace(K key, V oldValue, V newValue) | 若map中已存在给定的key-oldValue键值对,则替换为给定的key-newValue。返回true;否则返回false(1.8新增) |
default V replace(K key, V value) | 若map中已存在给定key且其映射的值不为null,则将其值替换为给定value,返回原本值,否则返回null |
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) | 若给定key不存在或其所对应的值为null,则按照给定函数式函数计算得出新value作为该key的对应值,返回该新value;若给定key对应的值不为null或计算得出的新value为null,则返回原本的value(1.8新增) |
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) | 若给定key存在且其对应值不为null,则按照给定函数式函数计算得出newValue,若newValue不为空,则将给定key的旧值替换为newValue,返回newValue,若newValue为空则移除给定key,返回null;否则返回null。(1.8新增) |
default V compute(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction) | 根据给定函数式函数得出newValue。若newValue不为null,则添加或替换key-newValue键值对,返回newValue;若newValue为空,则移除key,返回null |
default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) | 如果Map中key对应的映射不存在或者为null,则将value关联到key上;否则执行remappingFunction,如果执行结果为null则删除key的映射,否则用该结果跟key关联。 |
部分方法测试
注:打印结果以注释方式呈现,部分例子可不能不是很恰当,见笑
@Test
public void test1() {
Map<Integer, String> map = new HashMap<>();
//put方法
map.put(1, "1");
map.put(2, "2");
String put = map.put(3, "3");
String put1 = map.put(3, "33");
System.out.println(put); // null
System.out.println(put1); // 3
System.out.println(map); // {1=1, 2=2, 3=33}
Map<Integer, String> map2 = new HashMap<>();
map2.put(1, "11");
map2.put(2, "22");
map2.put(4, "44");
//putAll方法
map.putAll(map2);
System.out.println(map); // {1=11, 2=22, 3=33, 4=44}
//keySet()
Set<Integer> keySet = map.keySet();
System.out.println(keySet);// [1, 2, 3, 4]
//values()
Collection<String> values = map.values();
System.out.println(values); //[11, 22, 33, 44]
//entrySet()
Set<Map.Entry<Integer, String>> entries = map.entrySet();
//Map遍历的一种方式(注意此处调用的是Iterable的forEach方法)
entries.forEach(e -> {
Integer key = e.getKey();
String value = e.getValue();
System.out.println(key + "--" + value);
});
/* 结果:
1--11
2--22
3--33
4--44
*/
//getOrDefault方法
String aDefault = map.getOrDefault(5, "不存在该key");
System.out.println(aDefault);// 不存在该key
//forEach方法
map.forEach((key, value) -> {
if ((key & 1) == 1) {
value += "(key是奇数)";
} else {
value += "(key是偶数)";
}
map.put(key, value);
});
System.out.println(map); //{1=11(key是奇数), 2=22(key是偶数), 3=33(key是奇数), 4=44(key是偶数)}
//replaceAll方法
map.replaceAll((key, val) -> {
if (val.endsWith(")")) {
return val.substring(0, 2);
}
return key.toString() + key + toString();
});
System.out.println(map); //{1=11, 2=22, 3=33, 4=44}
//putIfAbsent方法
String putIfAbsent1 = map.putIfAbsent(4, "王富贵");// 已存在该key -> 4==44
System.out.println(putIfAbsent1); // 44
System.out.println(map); // {1=11, 2=22, 3=33, 4=44}
String putIfAbsent2 = map.putIfAbsent(5, "55");// 不存在该key
System.out.println(putIfAbsent2); // null
System.out.println(map); //{1=11, 2=22, 3=33, 4=44, 5=55}
//remove(key,val)方法
System.out.println("移除键值对 5==55 :" + map.remove(5, "55")); //移除键值对 5==55 :true
System.out.println(map); //{1=11, 2=22, 3=33, 4=44}
System.out.println("移除键值对 4==520 : " + map.remove(4, "520")); //移除键值对 4==520 : false
System.out.println(map); //{1=11, 2=22, 3=33, 4=44}
//replace(key,oldVal,newOld)方法
boolean replace = map.replace(4, "44", "newOld 44");
System.out.println(replace); //true
System.out.println(map); // {1=11, 2=22, 3=33, 4=newOld 44}
//replace(key,val)方法
String oldVal = map.replace(4, "44");
System.out.println(oldVal); //newOld 44
System.out.println(map); //{1=11, 2=22, 3=33, 4=44}
//computeIfAbsent方法
String s = map.computeIfAbsent(4, key -> key.toString() + key.toString());//给定key存在且对应值不为空
System.out.println(s); //44
System.out.println(map); //{1=11, 2=22, 3=33, 4=44}
String s2 = map.computeIfAbsent(5, key -> key.toString() + key.toString());//给定key不存在
System.out.println(s2); //55
System.out.println(map); //{1=11, 2=22, 3=33, 4=44, 5=55}
//computeIfPresent方法
String present1 = map.computeIfPresent(5, (key, oldValue) -> {
String val = key.toString() + key.toString(); // 55
if (oldValue.equals(val)) {
return val + ":符合条件";
}
return null;
});
System.out.println(present1); //55:符合条件
System.out.println(map); // {1=11, 2=22, 3=33, 4=44, 5=55:符合条件}
//再次执行 5=55:符合条件
String present2 = map.computeIfPresent(5, (key, oldValue) -> {
String val = key.toString() + key.toString(); // 55
if (oldValue.equals(val)) {
return val + ":符合条件";
}
return null; //newValue为null时将删除该键值对
});
System.out.println(present2); //null
System.out.println(map); //{1=11, 2=22, 3=33, 4=44}
//compute方法
String compute = map.compute(5, (k, v) -> k.toString() + k.toString());
System.out.println(compute); //55
System.out.println(map); // {1=11, 2=22, 3=33, 4=44, 5=55}
//merge方法
String merge = map.merge(6, "新val", (oldValue, newValue) -> oldValue + newValue);//给定key不存在
System.out.println(merge); //新val
System.out.println(map); //{1=11, 2=22, 3=33, 4=44, 5=55, 6=新val}
String merge2 = map.merge(6, "66", (oldValue, newValue) -> newValue);//给定key已存在
System.out.println(merge2); //66
System.out.println(map); // {1=11, 2=22, 3=33, 4=44, 5=55, 6=66}
}
Map接口的实现类
HashMap
键是哈希表结构,可以保证键的唯一性。线程不安全,HashMap最多只允许一条记录的键为Null,允许多条记录的值为 Null;。
jdk1.8前后HashMap底层结构的变化(原文:浅谈HashMap的底层原理(JDK1.8之前与JDK1.8之后)):
一、在JDK1.8之前,HashMap的底层是采用数组+链表的方法,即用链表处理哈希冲突。插入元素是先通过HashMap中Key的hashcode()方法计算出插入到数组的位置,如果数组当前位置没有元素,直接插入;如果当前位置已有元素,通过equals方法比较key,如果key也相同,直接覆盖,如果key不相同,插入链表中。(即拉链法:数组每一格都代表可以“下挂”一个链表,当遇到哈希冲突时,插入到链表中即可。)
二、Jdk1.8之后,HashMap的底层结构在处理哈希冲突时有了较大改变,即采用数组+链表+红黑树的结构,当链表长度大于8时,链表自动转化为二叉树。这样做的好处是,提高当链表长度过长时,搜索速度过慢的问题。
有关HashMap的更多理解可参考这篇文章:HashMap和currentHashMap的知识总结
LinkedHashMap
继承自HashMap。使用Map接口的哈希表和链表实现,具有可预知的迭代顺序。
此实现与HashMap的不同之处在于:LinkedHashMap维护着一个双向循环链表。此链表定义了迭代顺序,该迭代顺序通常就是存放元素的顺序。遍历速度比HashMap慢。
小测试
@Test
public void test2() {
HashMap<String,String> hashMap = new HashMap<>();
hashMap.put("hello","11");
hashMap.put("hash","22");
hashMap.put("map","33");
hashMap.put("Good","44");
hashMap.put("Luck","55");
hashMap.put(null,null);//允许一个键为null
Set<Map.Entry<String, String>> entries = hashMap.entrySet();
entries.forEach(System.out::println);
/* 结果(遍历顺序与添加顺序不同):
null=null
Luck=33
hello=11
Good=33
map=33
hash=22
*/
System.out.println("-----------------------------");
LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("hello","11");
linkedHashMap.put("hash","22");
linkedHashMap.put("map","33");
linkedHashMap.put("Good","44");
linkedHashMap.put("Luck","55");
linkedHashMap.put(null,null);//允许一个键为null
Set<Map.Entry<String, String>> entrySet = linkedHashMap.entrySet();
entrySet.forEach(System.out::println);
/* 结果(遍历顺序与添加顺序相同) :
hello=11
hash=22
map=33
Good=44
Luck=55
null=null
*/
}
TreeMap
键是红黑树结构,可以保证键的排序(自然排序或自定义排序)和唯一性。线程不安全。不允许有null键,但允许有null值。
小测试
@Test
public void test3() {
//自然排序
TreeMap<Character,String> treeMap = new TreeMap<>();
treeMap.put('D',"DI");
treeMap.put('B',"Battle");
treeMap.put('A',"Abstract");
treeMap.put('C',"Character");
Set<Map.Entry<Character, String>> entries = treeMap.entrySet();
entries.forEach(System.out::println);
/* 结果:
A=Abstract
B=Battle
C=Character
D=DI
*/
//自定义排序
TreeMap<String,Integer> map = new TreeMap<>(Comparator.comparingInt(String::length)); //按照主键字符串的长度
map.put("333",333);
map.put("22",22);
map.put("1",1);
map.put("55555",333);
map.put("4444",333);
map.entrySet().forEach(System.out::println);
/* 结果
1=1
22=22
333=333
4444=333
55555=333
*/
}
Hashtable与ConcurrentHashMap
- 1.Hashtable: 线程安全,效率低。使用一把锁处理并发问题,当有多个线程访问时,需要多个线程竞争一把锁,容易导致阻塞。
- 2.ConcurrentHashMap:线程安全,效率高。ConcurrentHashMap则从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树的底层实现使之在多线程下效率高。
Map集合关系粗略图