Java笔记3:Java集合之Map
本文根据 李刚《疯狂Java讲义》第8章源码整理而来。
通用Map,用于在应用程序中管理映射,通常在 java.util 程序包中实现。
Map提供了一种映射关系,其元素是以键值对(key-value)的形式存储的,能够根据key查找value,key和value可以是任意类型的对象。key和value属于Entry类的对象实例。key值不能重复。
Map体系:主要有HashTable(线程安全的)、HashMap(线程不安全的)、SortedMap(TreeMap)、LinkedHashMap(基于链表,比TreeMap效率高)、WeakHashMap(弱引用)、IdentityHashMap、EnumMap等。
Map要点
键值对的集合 成对放入多个key-value对
Map map = new HashMap();
map.put("疯狂Java讲义" , 109);
map.put("疯狂iOS讲义" , 10);
map.put("疯狂Ajax讲义" , 79);
// 多次放入的key-value对中value可以重复
map.put("轻量级Java EE企业应用实战" , 99);
// 放入重复的key时,新的value会覆盖原有的value
map.put("疯狂iOS讲义" , 99);//覆盖了原有的value,返回被覆盖的value 返回10
map.containsKey("疯狂iOS讲义"); // true
map.containsValue(99); // true
// 获取Map集合的所有key组成的集合,通过遍历key来实现遍历所有key-value对
for (Object key : map.keySet() )
{
// map.get(key)方法获取指定key对应的value
System.out.println(key + "-->" + map.get(key));
}
map.remove("疯狂Ajax讲义"); // 根据key来删除key-value对。
map.replace("疯狂XML讲义" , 66); // 尝试替换key为"疯狂XML讲义"的value
// 使用原value与参数计算出来的结果覆盖原有的value
map.merge("疯狂iOS讲义" , 10 ,(oldVal , param) -> (Integer)oldVal + (Integer)param);
// 当key为"Java"对应的value为null(或不存在时),使用计算的结果作为新value
map.computeIfAbsent("Java" , (key)->((String)key).length());
System.out.println(map); // map中添加了 Java=4 这组key-value对
// 当key为"Java"对应的value存在时,使用计算的结果作为新value
map.computeIfPresent("Java",(key , value) -> (Integer)value * (Integer)value);
System.out.println(map); // map中 Java=4 变成 Java=16
1. HashTable
是线程安全的,HashTable不允许使用null作为key和value。
支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此在写入时会比较慢。
Properties是HashTable的子类。Properties在HashTable基础上扩展了一些功能。
Properties props = new Properties();
// 向Properties中增加属性
props.setProperty("username" , "yeeku");
props.setProperty("password" , "123456");
props.store(new FileOutputStream("a.ini"), "comment line"); //保存到a.ini文件中
// 新建一个Properties对象
Properties props2 = new Properties();
props2.setProperty("gender" , "male");// 向Properties中增加属性
props2.load(new FileInputStream("a.ini") ); //将a.ini文件中的key-value对追加到props2中
2. HashMap
是线程不安全的,但效率比HashTable高。
根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。
key值和value值可以为null,但是只能有一个key为null,因为key不可重复。
HashMap中的Entry对象是无序排列的。
3.SortedMap接口和TreeMap实现类
能够把它保存的记录根据键(key)排序,默认是按升序排序,也可以指定排序的比较器。
不允许key的值为null。
public class TreeMapTest
{
public static void main(String[] args)
{
TreeMap tm = new TreeMap();
tm.put(new R(3) , "轻量级Java EE企业应用实战");
tm.put(new R(-5) , "疯狂Java讲义");
tm.put(new R(9) , "疯狂Android讲义");
System.out.println(tm);
tm.firstEntry() // 返回该TreeMap的第一个Entry对象
tm.lastKey() // 返回该TreeMap的最后一个key值
tm.higherKey(new R(2)) // 返回该TreeMap的比new R(2)大的最小key值。
tm.lowerEntry(new R(2))// 返回该TreeMap的比new R(2)小的最大的key-value对。
tm.subMap(new R(-1) , new R(4)) // 返回该TreeMap的子TreeMap
}
}
4.LinkedHashMap
LinkedHashMap用链表记录元素插入的顺序,可以避免对key-value排序,又可避免使用TreeMap所增加的成本,效率比TreeMap高。
在用Iterator遍历LinkedHashMap时,按插入的顺序得到元素,会比HashMap慢。
LinkedHashMap scores = new LinkedHashMap();
scores.put("语文" , 80);
scores.put("英文" , 82);
scores.put("数学" , 76);
// 调用forEach方法遍历scores里的所有key-value对
scores.forEach((key, value) -> System.out.println(key + "-->" + value));
5.WeakHashMap
WeakHashMap 继承于AbstractMap,实现了Map接口。和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。
“弱键”通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。
WeakHashMap whm = new WeakHashMap();
//key只保留对实际对象new String("英文")的弱引用,对象new String("英文") 没有被其他强引用变量引用,可以被系统进行垃圾回收
whm.put(new String("英文") , new String("中等"));// 该key是一个系统缓存的字符串对象。
whm.put("java" , new String("中等")); // "java"是字符串直接量,垃圾回收时不会回收它
System.gc(); // 通知系统立即进行垃圾回收
System.runFinalization();
6. IdentityHashMap
key不能重复,key重复的判断条件与HashMap不同。key相等的条件是严格相等key1==key2,而不是equals。
IdentityHashMap ihm = new IdentityHashMap();
// 下面两行代码将会向IdentityHashMap对象中添加两个key-value对
ihm.put(new String("语文") , 89);
ihm.put(new String("语文") , 78);
// 下面两行代码只会向IdentityHashMap对象中添加一个key-value对
ihm.put("java" , 93);
ihm.put("java" , 98);
7. EnumMap
// 所有key都是枚举类的枚举值
EnumMap enumMap = new EnumMap(Season.class);
enumMap.put(Season.SUMMER , "夏日炎炎");
enumMap.put(Season.SPRING , "春暖花开");
8.ConcurrentHashMap
多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为所有访问HashTable的线程都必须竞争同一把锁。
ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁。
应用场景:当有一个大数组需要在多个线程共享时就可以考虑是否把它给分成多个节点了,避免大锁。
9.Map的API
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) |
返回与指定键关联的值 |
containsKey(Object key) |
如果 Map 包含指定键的映射,则返回 true |
containsValue(Object value) |
如果此 Map 将一个或多个键映射到指定值,则返回 true |
isEmpty() |
如果 Map 不包含键-值映射,则返回 true |
size() |
返回 Map 中的键-值映射的数目 |
本文参考和使用了以下文章的部分内容:
https://www.cnblogs.com/nayitian/p/3266090.html#LinkedHashSet
https://www.cnblogs.com/shan1393/p/9020564.html
https://www.cnblogs.com/liyiran/p/4607817.html