Map基本概念html
数据结构中Map是一种重要的形式。Map接口定义的是查询表,或称查找表,其用于储存所谓的键/值对(key-value pair),其中key是映射表的索引。java
JDK结构中还存在实现Map相似功能的遗留集合:安全
Hashtable(线程安全的散列映射表)数据结构
Properties(属性映射表),经常使用于配置文件(如db.properties)。框架
Map接口源代码ide
package java.util; /* 映射表(查询表)泛型接口 */
public interface Map<K,V> { /* 基本与Collection相同,返回映射表中key-value映射对数量(内部属性size) */ int size(); /* 基本与Collection相同,返回映射表是否包含映射对 */ boolean isEmpty(); /* 返回映射表是否包含指定的键 */ boolean containsKey(Object key); /* 返回映射表是否包含指定的值,或者说指定的值是否有大于等于1个映射的键 */ boolean containsValue(Object value); /* 若是映射表中存在指定key,返回此key的值;不然,返回null */ V get(Object key); /* 将键值对放入映射表中。
* 若是键存在,则用如今的值替换原有值,返回原有值对象;
* 若是键不存在则存入键值对,返回null */
*/ V put(K key, V value); /* 移除指定键对应的值。若是存在键,则移除,并返回移除值对象;反之,则返回null */ V remove(Object key); /* 复制另外一张映射表元素到本映射表中 */ void putAll(Map<? extends K, ? extends V> m); /* 基本同Collection,清空映射表 */ void clear(); /* 得到键的Set集合 */ Set<K> keySet(); /* 得到值的Collection集合 */ Collection<V> values(); /* 得到键值对的Set集合 */ Set<Map.Entry<K, V>> entrySet(); /* 内部接口 Entry<K,V> */ interface Entry<K,V> { /* 获取键值对的键 */ K getKey(); /* 获取键值对的值 */ V getValue(); /* 设置键值对的值 */ V setValue(V value); /* 比较entry(键值对) */ boolean equals(Object o); /* 生成entry对象的hash值 */ int hashCode(); } /* 比较映射表 */ boolean equals(Object o); /* 生成映射表对象的hash码*/ int hashCode(); }
深刻理解码源测试
对象比较好基友:
equals(Object obj)
、hashcode()
this
Map<K, V>
接口及其内部接口Entry<K, V>
都有这俩方法。如此设计,目的就是规范其实现子类,要求子类必须重写Object
类的这俩方法,从而完成映射表这种数据结构的既定思想。spa
boolean equals(Object o); // 对象比较 int hashCode(); // 哈希码
集合框架通用方法:
size()
、isEmpty()
线程集合框架(包括
Collection
接口及其子接口List
、Set
,Map
接口)内部维护一个size
属性,其描述用户提供可操纵元素数量,经过size()
方法向用户提供可见性,经过isEmpty()
方法向用户说明是否集合中仍存在其可操纵的元素。
int size(); // 元素保有量 boolean isEmpty(); // 元素数量是否为0
键、值存在性判断:
containsKey(Object key)
、containsValue(Object value)
boolean containsKey(Object key); // 映射表是否包含指定键的元素 boolean containsValue(Object value); // 映射表是否包含指定值得元素
映射表增删查改:
增、改
V put(K key, V value) V putAll(Map<? extends K, ? value V> m)
删
V remove(Object key) void clear()
查
V get(Object key)
V put(K key, V value) // 放入或替换指定key的键值对 V putAll(Map<? extends K, ? value V> m) // 将另外一映射表全部元素放入本映射表 V remove(Object key) // 移除指定key的键值对 V get(Object key) // 获取指定key的键值对 void clear() // 清除全部映射表元素
package com.forget406.study; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; public class MapStudy { @SuppressWarnings({ "unchecked", "rawtypes" }) public static void main(String[] args) { /* JDK 1.7容许后面的尖括号内不写泛型变量 */ Map<String, Coordinate> rect = new HashMap<>(); rect.put("point A", new Coordinate(0, 0)); rect.put("point B", new Coordinate(1, 0)); rect.put("point C", new Coordinate(1, 1)); rect.put("point D", new Coordinate(0, 1)); Map<String, Coordinate> line = new TreeMap<String, Coordinate>(); line.put("point A", new Coordinate(0, 0)); line.put("point B", new Coordinate(3, 3)); /***** 实验测试部分 *****/ System.out.println(rect); // output rectangle System.out.println(line); // output line System.out.println(rect.put("point D", new Coordinate(2, 2))); // (0, 1) System.out.println(rect.get("point C")); // (1,1) System.out.println(rect.get("point E")); // null rect.putAll(line); System.out.println(rect); System.out.println(line.remove("point C")); // null System.out.println(line.remove("point A")); // (0, 0) } } /** * 坐标类 * * @author forget406 * @since 09/08/2016 * @param <T> 泛型参数 */ class Coordinate<T> { private T x; private T y; public Coordinate(T x, T y) { this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((x == null) ? 0 : x.hashCode()); result = prime * result + ((y == null) ? 0 : y.hashCode()); return result; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Coordinate<T> other = (Coordinate<T>) obj; if (x == null) { if (other.x != null) return false; } else if (!x.equals(other.x)) return false; if (y == null) { if (other.y != null) return false; } else if (!y.equals(other.y)) return false; return true; } @Override public String toString() { return "(" + x + "," + y + ")"; } }
{point C=(1,1), point B=(1,0), point A=(0,0), point D=(0,1)} {point A=(0,0), point B=(3,3)} (0,1) (1,1) null {point C=(1,1), point B=(3,3), point A=(0,0), point D=(2,2)} null (0,0)
映射表元素遍历三种方式(或称 映射表集合视图(*)):
- 按
key
遍历Set<K> keySet() 返回映射表中全部键的视图。能够从这个Set中删除元素,同时也从映射表中删除了它们。
- 按
value
遍历Collection<V> values() 返回映射表中全部值的视图。能够从这个Collection中删除元素,同时也从映射表中删除了它们。
- 按
Entry(key-value对)
遍历Set<Map.Entry<K, V>> entrySet()
返回Map.Entry对象Set集的视图,即映射表中的键/值对。能够从这个集合中删除元素,同时也从映射表中删除了它们。
注意:虽然能够从这三种遍历方式遍历得到的视图集合中删除元素,可是均不可以在其中添加元素。
Set<K> keySet(); // 元素键视图集 Collection<V> values(); // 元素值视图集 Set<Map.Entry<K, V>> entrySet(); // 元素键/值对视图集
Map
接口的内部接口,即Entry<K, V>
接口,起封装键值对的做用。经过键值对遍历Map
时,能够经过Entry
接口提供的方法获取相应的键、值,以及设置键值对的值。
K getKey()
得到键值对的键
V getValue()
得到键值对的值
V setValue(V value)
设置键值对的值,同时映射表的内容也同时更新(*)
但没有提供设置键值对键的方法,why? 键值对的键是键值对的索引,改变键不具备实际意义,并且改变以后会引发一系列诸如hash值改变等问题。K setKey(K key)
/* Entry<K, V>接口,属于内部接口 */
interface Entry<K,V> { K getKey(); // 得到键值对的键 V getValue(); // 得到键值对的值 V setValue(V value); // 设置键值对的值 /* 前面已经论述过 */ boolean equals(Object o); int hashCode(); }
import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class MapStudy { @SuppressWarnings({ "unchecked", "rawtypes" }) public static void main(String[] args) { Map<String, Coordinate> rect = new HashMap<String, Coordinate>(); rect.put("point A", new Coordinate(0, 0)); rect.put("point B", new Coordinate(1, 0)); rect.put("point C", new Coordinate(1, 1)); rect.put("point D", new Coordinate(0, 1)); /***** 实验测试代码 *****/ Set<Entry<String, Coordinate>> pos = rect.entrySet();
/* 最好判断一下是否为null */ if (pos != null) { for(Entry<String, Coordinate> p : pos) { System.out.println(p.getKey()+ "=" +p.getValue()); // 得到键、值 p.setValue(new Coordinate("?", "?")); // 设置对应Entry的值 } System.out.println(rect); // 更新Entry后,原来映射表内容也随之更新 } } } /** * 坐标类 * * @author forget406 * @since 09/08/2016 * @param <T> 泛型参数 */ class Coordinate<T> { private T x; private T y; public Coordinate(T x, T y) { this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((x == null) ? 0 : x.hashCode()); result = prime * result + ((y == null) ? 0 : y.hashCode()); return result; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Coordinate<T> other = (Coordinate<T>) obj; if (x == null) { if (other.x != null) return false; } else if (!x.equals(other.x)) return false; if (y == null) { if (other.y != null) return false; } else if (!y.equals(other.y)) return false; return true; } @Override public String toString() { return "(" + x + "," + y + ")"; } }
point C=(1,1) point B=(1,0) point A=(0,0) point D=(0,1) {point C=(?,?), point B=(?,?), point A=(?,?), point D=(?,?)}
若是读者须要进一步了解Java的内部接口机制,能够阅读另外一篇文章:《Java高级特性(二)内部接口》。
Map遍历方式
Map
遍历做为文章体系中极为重要的一块内容,从上面模块中抽出来单独总结。
Iterator<E>
接口实现
这种实现方式相对来讲比较鸡肋,反正遍历Map
我不多用到的。
import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class MapStudy { @SuppressWarnings({ "unchecked", "rawtypes" }) public static void main(String[] args) { Map<String, Coordinate> rect = new HashMap<String, Coordinate>(); rect.put("point A", new Coordinate(0, 0)); rect.put("point B", new Coordinate(1, 0)); rect.put("point C", new Coordinate(1, 1)); rect.put("point D", new Coordinate(0, 1)); /***** Iterator<E>方式遍历代码 *****/ // 遍历key,获取key Set<String> keys = rect.keySet(); Iterator<String> it1 = keys.iterator(); while(it1.hasNext()) { // 问 String key = it1.next(); // 取 System.out.println(key); // it1.remove(); 删 } // 遍历value,获取value Collection<Coordinate> values = rect.values(); Iterator<Coordinate> it2 = values.iterator(); while(it2.hasNext()) { Coordinate value = it2.next(); System.out.println(value); } // 遍历key-value对,获取key、value,设置value Set<Entry<String, Coordinate>> entries = rect.entrySet(); Iterator<Entry<String, Coordinate>> it3 = entries.iterator(); while(it3.hasNext()) { Entry<String, Coordinate> entry = it3.next(); String key = entry.getKey(); System.out.println(key); Coordinate value = entry.getValue(); System.out.println(value); entry.setValue(new Coordinate("?", "?")); } System.out.println(rect); } } /** * 坐标类 * * @author forget406 * @since 09/08/2016 * @param <T> 泛型参数 */ class Coordinate<T> { private T x; private T y; public Coordinate(T x, T y) { this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((x == null) ? 0 : x.hashCode()); result = prime * result + ((y == null) ? 0 : y.hashCode()); return result; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Coordinate<T> other = (Coordinate<T>) obj; if (x == null) { if (other.x != null) return false; } else if (!x.equals(other.x)) return false; if (y == null) { if (other.y != null) return false; } else if (!y.equals(other.y)) return false; return true; } @Override public String toString() { return "(" + x + "," + y + ")"; } }
point C point B point A point D (1,1) (1,0) (0,0) (0,1) point C (1,1) point B (1,0) point A (0,0) point D (0,1) {point C=(?,?), point B=(?,?), point A=(?,?), point D=(?,?)}
for-each实现
在JDK1.5版本引入for-each(由JVM维护,内部实现也是经过迭代器实现)后,上面那种有点原始社会的感受。因此通常我都是使用for-each遍历Map
。
import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class MapStudy { @SuppressWarnings({ "unchecked", "rawtypes" }) public static void main(String[] args) { Map<String, Coordinate> rect = new HashMap<String, Coordinate>(); rect.put("point A", new Coordinate(0, 0)); rect.put("point B", new Coordinate(1, 0)); rect.put("point C", new Coordinate(1, 1)); rect.put("point D", new Coordinate(0, 1)); /***** for-each方式遍历代码 *****/ // 遍历key,获取key Set<String> keys = rect.keySet(); for(String key : keys) { System.out.println(key); } // 遍历value,获取value Collection<Coordinate> values = rect.values(); for(Coordinate value : values) { System.out.println(value); } // 遍历key-value对,获取key、value,设置value Set<Entry<String, Coordinate>> entries = rect.entrySet(); for(Entry<String, Coordinate> entry : entries) { String key = entry.getKey(); System.out.println(key); Coordinate value = entry.getValue(); System.out.println(value); entry.setValue(new Coordinate("?", "?")); } System.out.println(rect); } } /** * 坐标类 * * @author forget406 * @since 09/08/2016 * @param <T> 泛型参数 */ class Coordinate<T> { private T x; private T y; public Coordinate(T x, T y) { this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((x == null) ? 0 : x.hashCode()); result = prime * result + ((y == null) ? 0 : y.hashCode()); return result; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Coordinate<T> other = (Coordinate<T>) obj; if (x == null) { if (other.x != null) return false; } else if (!x.equals(other.x)) return false; if (y == null) { if (other.y != null) return false; } else if (!y.equals(other.y)) return false; return true; } @Override public String toString() { return "(" + x + "," + y + ")"; } }
Map体系结构
Map接口的实现类大体分为两大类:
通用映射表类
HashMap(散列映射表)、TreeMap(二叉树映射表)
专用映射表类
IdentityHashMap(标识散列映射表)、WeakHashMap(弱散列映射表)、LinkedHashMap(连接散列映射表)、EnumMap(枚举映射表)等。
固然,上面只是比较粗略地分类,后续文章将进一步深刻分析Map相关接口、抽象类、实现类的内部机制,以及其于Set集的对应关系。
Map相关问题思考
思考1:标识符命名单复数
这个问题不光光是Map
有,整个Java语言在设计时都是采用这种思路。这种写法归根结底仍是因为英语国家语言语法习惯,值得咱们在平时命名的时候注意。
boolean containsKey(Object key);
若是换成我习惯的命名法则,让我来设计底层代码,可能就写成
boolean containKey(Object key);
boolean isContainKey(Object key);
真的是一个大写的囧 o(╯□╰)o 和大牛的差距就体现出来了。
对于设计方法标识符时大体应该遵循:
一、若是方法用于判断,即返回值是boolean
boolean isAb(Ab是名词) // 判断对象是Ab吗?
boolean AbsCd(Abs是动词单数,Cd是名词) // 判断对象Abs了Cd?好比上面的ContainsKey
二、通常的方法,规则是动词开头(具体判断是否加单数),后能够加名词或者连词(Of、As等)
思考2:Map视图
视图(views)实际上是集合框架(collection frame)所共有的语言特性。
关于视图的内容,会单独整理总结成文,这里就是做为一块须要重点理解的内容提出来。
Map
涉及到视图的内容:
Set<K> keySet(); // 元素键视图集 Collection<V> values(); // 元素值视图集 Set<Map.Entry<K, V>> entrySet(); // 元素键/值对视图集
/** Entry<K, V>接口,属于内部接口 */
interface Entry<K,V> { K getKey(); // 得到键值对的键 V getValue(); // 得到键值对的值 V setValue(V value); // 设置键值对的值 }
思考3:Map遍历须要用Collection(集合)和Set(集)的缘由分析
回头看前面Map
接口源代码,你会发现它并无写成
public interface Map<K, V> extends Iterable<E>
而是简单地写成
public interface Map<K, V>
也就是说,Map
接口其实是一个顶层接口。
相比较Collection<T>
接口继承更顶层的Iterable<T>
(拥有iterator()
方法,以及for-each使用必须继承的接口),Map
接口显得较为特殊。
为何Map遍历必须借助Collection或Set呢?
假设如今Map接口源代码写成
public interface Map<K, V> extends Iterable<E>
那么问题来了,该怎么遍历Map呢?如今有key、value两个泛型参数,固然咱们能够将这两个泛型参数当作一对Entry
,不过若是我须要单独遍历key或者value呢?没法单独经过Map
实现。这就会显得功能太单调。
与其这样,不如一不作二不休,将Map<K, V>
接口的遍历查询结果作成三种Set
集或Collection
集合视图。
Map<K, V>
的K不可以重复,选择合适的数据结构Set
储存。
Map<K, V>
的V能够重复,选择合适的数据结构Collection
储存。固然我我的认为List
储存也是能够的,只不过设计者选择更加稳妥、保守的方式而已。
思考4:增删查改方法都有泛型返回值V(映射表值)
这一点其实也没琢磨太清楚,多是设计者认为这么定义方法更加人性化。好比:
V remove(Object o) 删除映射表中指定键元素,若是删除则返回删除元素的值对象,反正则返回null。这样咱们还可以看看到底删除值对不对。