映射(Map)相关
- Map
- Map常用接口
- HashMap
- LinkedHashMap
- LinkedHashMap的具体实现(待补充)
Map
映射(Map)是一种十分常用和基础的数据结构,用来存放键/值对。如果提供了键,就能查找到值。
1.HashMap:它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度。
HashMap最多只允许一条记录的键为null,允许多条记录的值为null。非线程安全。
如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap
2.Hashtable:Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类。线程安全。并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。
3.LinkedHashMap:LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。
4.TreeMap:TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。
在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。
Map常用接口
java.util.Map<K,V> 1.2
• V get(Object key)
获取与键对应的值;返回与键对应的对象, 如果在映射中没有这个对象则返回 null。键可以为 null。
• default V getOrDefault(Object key, V defaultValue)
获得与键关联的值;返回与键关联的对象, 或者如果未在映射中找到这个键, 则返回defaultValue。
• V put(K key, V value)
将键与对应的值关系插入到映射中。如果这个键已经存在, 新的对象将取代与这个键对应的旧对象。这个方法将返回键对应的旧值。如果这个键以前没有出现过则返回null。键可以为 null, 但值不能为 null。
• void putAll(Map<? extends K , ? extends V> entries)
将给定映射中的所有条目添加到这个映射中。
• boolean containsKey(Object key)
如果在映射中已经有这个键, 返回 true。
• boolean containsValue(Object value)
如果映射中已经有这个值, 返回 true。
•default void forEach(BiConsumer<? super K ,? super V> action)8
对这个映射中的所有键 / 值对应用这个动作。
•public V remove(Object key):把指定的键所对应的键值对元素在Map集合中删除返回被删除元素的值.
public static void mapDemo() {
//创建Map对象
Map<String, String> map = new HashMap<String, String>();
//给map添加元素
map.put("星期一", "Monday");
map.put("星期二", "Tuesday");
map.put("星期日", "Sunday");
System.out.println(map);
System.out.println(map.get("星期一"));
System.out.println(map.get("Monday"));
System.out.println(map.containsValue("Monday"));
System.out.println(map.remove("星期一"));
System.out.println(map);
}
注意:Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。所以在获取map里的所有键或者所有值的时候,都要通过方法先转换成Set,再遍历,比如
map.keySet();
map.entrySet();
HashMap
HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的。
这里需要讲明白两个问题:数据底层具体存储的是什么?这样的存储方式有什么优点呢?
1.HashMap类中有一个非常重要的字段,就是 Node[] table,即哈希桶数组
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //用来定位数组索引位置
final K key;
V value;
Node<K,V> next; //链表的下一个node
Node(int hash, K key, V value, Node<K,V> next) { ... }
public final K getKey(){ ... }
public final V getValue() { ... }
public final String toString() { ... }
public final int hashCode() { ... }
public final V setValue(V newValue) { ... }
public final boolean equals(Object o) { ... }
}
Node是HashMap的一个内部类,实现了Map.Entry接口,本质是就是一个映射(键值对)。上图中的每个黑色圆点就是一个Node对象。
2.HashMap就是使用哈希表来存储的。Java中HashMap采用了拉链法解决冲突。
例如程序执行下面代码:
map.put("美团","小美");
系统将调用"美团"这个key的hashCode()方法得到其hashCode 值(该方法适用于每个Java对象),然后再通过Hash算法的后两步运算(高位运算和取模运算,下文有介绍)来定位该键值对的存储位置。
哈希桶数组需要在空间成本和时间成本之间权衡。那么通过什么方式来控制map使得Hash碰撞的概率又小,哈希桶数组(Node[] table)占用空间又少呢?答案就是好的Hash算法和扩容机制。(待补充)
LinkedHashMap
HashMap有一个顺序的问题,就是在对HashMap进行迭代访问时,添加的顺序和访问的顺序可能就不一样的,这个时候我们可以选择LinkedHashMap,LinkedHashMap继承了HashMap,所以拥有和HashMap一样的功能;而且在此基础上有增加了一个双向链表来实现元素迭代的顺序,但是肯定会增加时间和空间的消耗,LinkedHashMap和HashMap一样,也是非线程安全的
public static void mapDemo() {
HashMap<String, Object> hashMap = new HashMap<String, Object>();
hashMap.put("name","chenweijie");
hashMap.put("age", 22);
hashMap.put("addr", "北京");
hashMap.put(null, null);
hashMap.put("abc", null);
hashMap.put(null, "bcd");
LinkedHashMap<String, Object> hashMap1 = new LinkedHashMap<String, Object>();
hashMap1.put("name","chenweijie");
hashMap1.put("age", 22);
hashMap1.put("addr", "北京");
hashMap1.put(null, null);
hashMap1.put("abc", null);
hashMap1.put(null, "bcd");
for(Map.Entry<String, Object> entry : hashMap.entrySet()) {
System.out.println(entry.getKey() + "=" +entry.getValue());
}
System.out.println("--------------------------------------------");
for(Map.Entry<String, Object> entry : hashMap1.entrySet()) {
System.out.println(entry.getKey() + "=" +entry.getValue());
}
}
结果:
可以看到一个无序输出,一个有序输出。
根据输出我们可以得出以下几个结论:
LinkedHashMap的输入顺序和输出顺序是一致的。
LinkedHashMap允许Key和Value都可以null(可是hashmap好像也可以啊?)
LinkedHashMap中添加元素时,如果Key重复,则后添加的会覆盖前面已经存在的值
LinkedHashMap的具体实现(待补充)
/**
* HashMap.Node subclass for normal LinkedHashMap entries.
*/
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
Entry就是LinkedHashMap基本数据结构,Entry是LinkedHashMap定义的一个内部类,继承了HaspMap.Entry,在此基础上添加了新添加了两个属性。
before、after是用于维护链表中Entry的前一个元素和后一个元素。
在HashMap的构造函数中最后有一个init的方法,但是此方法在HashMap中没有实现,LinkedHashMap中重写了该方法,用来初始化化链表。
总结:从以上可以判断LinkedHashMap的实现就是 HashMap+LinkedList 的实现方式,用HashMap维护数据结构,用LinkList的方式维护数据插入顺序。