1、Java 集合框架之 Map 实例解析1、Map 概述1.1 什么是 MapMap 是将键映射到值( key-value )的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。Map 接口提供三种 collection 视图,允许以键集(keySet()、值集(values()或键-值映射关系集 (entrySet()的形式查看某个映射的内容 ( 即获取键值对的内容 )。映射顺序定义为迭代器在映射的 collection 视图上返回其元素的顺序,即可以映射得到键、值和键-值的 Set 集合,元素的顺序是由得到的 Set 集合所决定的。某些映射实现可明确保证其顺序,如 TreeMap
2、类;另一些映射实现则不保证顺序,如 HashMap 类 。1.2 Map 与 Collection 的区别 1.Map 存储的是键值对形式的元素,键唯一,值可以重复。 2.Collection 存储的是单列元素,子接口 Set 元素唯一,子接口 List 元素可重复。 3.Map 集合的数据结构值针对键有效,跟值无关,Collection 集合的数据结构是针对元素有效关于 Collection 可以戳这里 java 集合框架之 Collection 实例解析2、Map 继承体系下面列出了常见 Map 集合的继承体系与他们的特点-Map 键唯一|-HashMap基于哈希表的 Map 接口的实现。
3、此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 此实现不是同步的。|-LinkedHashMapMap 接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。此实现不是同步的|-WeakHashMap以弱键 实现的基于哈希表的 Map。在 WeakHashMap 中
4、,当某个键不再正常使用时,将自动移除其条目。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。丢弃某个键时,其条目从映射中有效地移除,null 值和 null 键都被支持。|-Hashtable此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值。 Hashtable 是同步的 |-TreeMap基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。 此
5、实现不是同步的3、Map 泛型接口Map 特点 : 由 key-value 键值对组成,键不可重复,值可重复大致包含如下功能: 插入(put、putAll())、删除(remove()) 获取(entrySet()、get()、keySet() 、size()、values()) 判断(containsKey()、containsValue()、equals()、isEmpty() )、清除(clear()) 替换(replace(),replace(K key, V oldValue, V newValue) jdk1.8 之后,后面示例会讲到它们)方法摘要void clear() 从此映射
6、中移除所有映射关系(可选操作)。 boolean containsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true。 boolean containsValue(Object value) 如果此映射将一个或多个键映射到指定值,则返回 true。 Set entrySet() 返回此映射中包含的映射关系的 Set 视图。 boolean equals(Object o) 比较指定的对象与此映射是否相等。 V get(Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。 int hashCode() 返回此映射的哈希码值
7、。 boolean isEmpty() 如果此映射未包含键 -值映射关系,则返回 true。 Set keySet() 返回此映射中包含的键的 Set 视图。 V put(K key, V value) 将指定的值与此映射中的指定键关联(可选操作)。 void putAll(Map m) 从指定映射中将所有映射关系复制到此映射中(可选操作)。 V remove(Object key) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。 int size() 返回此映射中的键 -值映射关系数。 Collection values() 返回此映射中包含的值的 Collection 视图。
8、3.1、 Map 集合遍历的常见方式方式 1、根据键获取值(key - value)1.获取所有键的集合2.遍历键的集合,获取到每一个键3.根据键找值方式 2、根据键值对对象获取键和值( entrySet - key,value)1.获取所有键值对对象的集合2.遍历键值对对象的集合,获取到每一个键值对对象3.根据键值对对象找键和值3.11 Map 使用示例public class MapReview public static void main(String args) Map map=new HashMap();map.put(“000“, “qqq“);map.put(“003“, “r
9、rr“);map.put(“001“, “www“);map.put(“002“, “eee“);map.put(“004“, “sss“);/ System.out.println(map); / 直接打印输出键值对/ 遍历 1 : 通过键值对对象 entrySet 获取键与值Set entrySet = map.entrySet();for (Entry entry : entrySet) String key = entry.getKey();String value = entry.getValue();System.out.println(“key=“+key+“ value=“+v
10、alue);System.out.println(“-“);/ 遍历 2 : 通过键 keySet 获取值Set keySet = map.keySet(); / 得到键的集合for (String key : keySet) String value = map.get(key);System.out.println(“key=“+key+“ value=“+value);System.out.println(“-“);/ 获取 Map 值的集合Collection values = map.values();System.out.println(values);/ 判断是否存在键和值Sys
11、tem.out.println(“containsKey=“+map.containsKey(“001“);System.out.println(“containsKey=“+map.containsValue(“eee“);/ 向 Map 集合添加元素时,若键存在,则返回之前与键对应的值String put = map.put(“000“, “aaa“);System.out.println(“put=“+put); / output: qqq/ default V replace(K key, V value)/ 替换功能,将旧值替换成新值,并返回旧值(若有的话)String replac
12、e = map.replace(“003“, “666“);System.out.println(“replace=“+replace);System.out.println(map);/ default boolean replace(K key, V oldValue, V newValue/ 只有当键 key 存在,并且 oldValue 与 newValue 相等时,旧的值才会被替换成新的值,并且返回 trueboolean success = map.replace(“004“, “sss“, “lll“); System.out.println(“replace2=“+succes
13、s); / output : true3.2、 HashMap3.21.HashMap 的特点键是哈希表结构,可以保证键的唯一性,当向已存在 key 的 Map 中添加元素时,会覆盖掉旧值,并将旧值返回。它允许使用 null 值和 null 键,但不保证映射的顺序,特别是它不保证该顺序恒久不变(即不会保证存储的顺序与输出的顺序恒久不变) 。 此实现不是同步的。注意:对于自定义对象,需重写其 equals 和 hashCode 方法才能保证其 key 的唯一性3.22.HashMap 与 Hashtable 的异同除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大
14、致相同3.23.HashMap 的使用示例public class HashMapReview public static void main(String args) test1();test2();/* 自定义类型做 key,* 需重写其 equals 和 hashCode 方法才能保证其 key 的唯一性*/private static void test2() HashMap map=new HashMap();map.put(new Info(0, “aaa“),“0000“);map.put(new Info(1, “bbb“),“1111“);map.put(new Info(3
15、, “ddd“),“3333“);map.put(new Info(0, “aaa“),“4444“);map.put(new Info(2, “ccc“),“2222“);printMap(map);/ output:/ key=Info id=3, adress=ddd value=3333/ key=Info id=2, adress=ccc value=2222/ key=Info id=0, adress=aaa value=0000/ key=Info id=1, adress=bbb value=1111/ key=Info id=0, adress=aaa value=4444
16、 / 当 Info 没有重写 equals 和 hashCode 方法时,key 出现重复元素/* String 或基本数据类型的包装类做 key,他们已重写了 hashCode 与equals 方法* 键唯一,重复添加会替换旧值成新值*/private static void test1() HashMap map=new HashMap();map.put(“aaa“, “123“);map.put(“bbb“, “789“);map.put(“aaa“, “456“);map.put(“ccc“, “321“);System.out.println(map);/ output:/ aaa
17、=456, ccc=321, bbb=789/ 重复的键不会重新插入,只会替换旧值成新值private static void printMap(Map map) Set entrySet = map.entrySet();for (Entry entry : entrySet) System.out.println(“key=“+entry.getKey()+“ value=“+entry.getValue();3.24 一个 HashMap 面试题需求如下:已知一个 HashMap 集合, Person 有 name(String)和 age(int)属性。请写一个方法实现对 HashMa
18、p 的排序功能。该方法接收 HashMap为形参,返回类型为 HashMap ,要求对 HashMap中的 Person 的 age 升序进行排序。排序时 key=value 键值对不得拆散。分析:HashMap 本身是不保证元素的顺序不变的,要对其排序可使用 LinkedHashMap,它是有序的并且还是 HashMap 的子类,我们可以使用它来完成排序的目的。最后返回它的实例即可满足要求 并且还符合多态的编程思想 示例代码public class SortedHashMapDemo public static void main(String args) HashMap map = new
19、 LinkedHashMap();map.put(0, new Person(“小明“, 20);map.put(1, new Person(“小二“, 26);map.put(2, new Person(“小四“, 19);map.put(3, new Person(“阿七“, 33);map.put(4, new Person(“十四“, 25);map.put(4, new Person(“小花“, 19);System.out.println(map);HashMap sortedHashMap = SortedHashMap(map);System.out.println(sorte
20、dHashMap);public static HashMap SortedHashMap(HashMap map) / 获得键值对 Set 集合Set entrySet = map.entrySet();/ 将键值对 Set 集合转化为 List 以用 Collections 来排序List list = new ArrayList(entrySet);/ 通过 Collections 来排序,添加比较器,比较年龄Collections.sort(list, new Comparator() Overridepublic int compare(Entry o1, Entry o2) int
21、 result = o2.getValue().age - o1.getValue().age;result = result = 0 ? o2.hashCode() - o1.hashCode() : result;return result;);/ 创建 LinkedHashMap 来存储排好序的 List 元素LinkedHashMap linkedHashMap = new LinkedHashMap();/ 遍历 List,将元素添加到 linkedHashMap 中for (Entry entry : list) linkedHashMap.put(entry.getKey(),
22、entry.getValue();return linkedHashMap;class Person String name;int age;public Person(String name, int age) super();this.name = name;this.age = age;Overridepublic String toString() return “Person name=“ + name + “, age=“ + age + “;3.3、 LinkedHashMap3.31 LinkedHashMap 特点:Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。此
23、链接列表定义了迭代顺序(即存储的顺序与输出的顺序相同),该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。此实现不是同步的注意:当 key 为 String 或基本数据类型包装类,键相同自动替换旧值为新值 (因为他们已重写了 hashCode 与 equals 方法) 当 key 为自定义对象,需让其重写 hashCode 与 equals 方法才能保证 key 的唯一性3.32 LinkedHashMap 使用示例public class LinkedHashMapReview public static void main(String args) test1();test2();te
24、st3();/* 自定义对象为 key* 需让其重写 hashCode 与 equals 方法才能保证 key 的唯一性*/private static void test3() LinkedHashMap map=new LinkedHashMap();map.put(new Info(0, “vvv“), 000);map.put(new Info(0, “vvv“), 333);map.put(new Info(1, “ccc“), 111);System.out.println(map);/ output : 当 Info 没有重写 hashCode 与 equals 方法,/ Inf
25、o id=0, adress=vvv=0, Info id=0, adress=vvv=333, Info id=1, adress=ccc=111/ output : 当 Info 重写 hashCode 与 equals 方法/ Info id=0, adress=vvv=333, Info id=1, adress=ccc=111/* 基本数据类型包装类重写了 hashCode 与 equals 方法,键相同自动替换旧值为新值*/private static void test2() LinkedHashMap map=new LinkedHashMap();map.put(1, 66)
26、;map.put(2, 67);map.put(3, 68);map.put(1, 69); / 自动装箱 Integer.valueOf(69)System.out.println(map);/ output/ 1=69, 2=67, 3=68/ 输出顺序与存储相同,重复添加已有的键会替换掉旧值/* String 做 key* String 类实现了 hashCode 与 equals 方法,键相同自动替换旧值为新值*/private static void test1() LinkedHashMap map=new LinkedHashMap();map.put(“aa“, “121“);
27、map.put(“bb“, “122“);map.put(“cc“, “123“);map.put(“aa“, “122“);map.put(“dd“, “122“);map.put(“ee“, “122“);System.out.println(map);/ output / aa=122, bb=122, cc=123, dd=122, ee=122/ 输出顺序与存储相同,重复添加已有的键会替换掉旧值3.4、 HashtableHashtable 是同步的,它不允许使用 null 值和 null 键。除此之外与 HashMap 大致相同,示例略 3.41 使用 PropertiesProp
28、erties 类继承自 Hashtable,表示了一个持久的属性集,由键值对(key-value)组成。与 Hashtable 不同的是,Properties 属性列表中每个键及其对应值都是一个字符串,因此不推荐使用 Hashtable 的 put 方法为 Properties 添加属性。Properties 可通过 store 方法保存在流中或通过 load 方法从流中加载。如下三种方式:1.通过字节输入输出流void load(InputStream inStream) 从输入流中读取属性列表(键和元素对)。 void store(OutputStream out, String comm
29、ents) 以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。 2.通过字符输入输出流void load(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。 void store(Writer writer, String comments) 以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符。 3.通过 xmlvoid loadFromXML(InputStream in)
30、 将指定输入流中由 XML 文档所表示的所有属性加载到此属性表中。 void storeToXML(OutputStream os, String comment) 发出一个表示此表中包含的所有属性的 XML 文档。 void storeToXML(OutputStream os, String comment, String encoding) 使用指定的编码发出一个表示此表中包含的所有属性的 XML 文档。 通过如下方法存取键值String getProperty(String key) 用指定的键在此属性列表中搜索属性。 String getProperty(String key, St
31、ring defaultValue) 用指定的键在属性列表中搜索属性。 Object setProperty(String key, String value) 调用 Hashtable 的方法 put。 void list(PrintStream out) 将属性列表输出到指定的输出流。 void list(PrintWriter out) 将属性列表输出到指定的输出流。 Set stringPropertyNames() 返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。 下面通过示例使用 Properties 1.创建一
32、个属性文件/* 创建一个属性文件* throws IOException*/private static void create() throws IOException / 创建一个空的属性文件Properties prop = new Properties();/ 创建一个文件输出流,用于写出属性文件到本地FileWriter writer = new FileWriter(“config.properties“);/亦可以使用 FileOutputStream/ 设置属性键与值prop.setProperty(“name“, “pecuyu“);prop.setProperty(“cha
33、racter“, “kind“);/ 通过字符输出流将键值对写入属性文件prop.store(writer, “this is new“);writer.close();2.读取属性文件内容/* 读取一个属性文件*/private static void load() throws FileNotFoundException, IOException / 创建属性文件Properties prop=new Properties();/ 从流中读取属性列表到属性文件prop.load(new FileInputStream(“config.properties“);/ 亦可使用 FileRead
34、er/ 通过键取值String name = prop.getProperty(“name“);String character = prop.getProperty(“character“);/System.out.println(“name=“+name+“ character=“+character);prop.list(System.out); / 将属性键值对列出并打印到控制台Set stringPropertyNames = prop.stringPropertyNames();/ 键的 set 集合for (String key : stringPropertyNames) St
35、ring value = prop.getProperty(key); / 通过键获取值System.out.println(“key=“+key+“ value=“+value);3.5、 WeakHashMap3.51 特点以弱键 实现的基于哈希表的 Map。在 WeakHashMap 中,当某个键不再正常使用时,将自动移除其条目。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。丢弃某个键时,其条目从映射中有效地移除,因此,该类的行为与其他的 Map 实现有所不同。 null 值和 null 键都被支持3.52 实现注意
36、事项WeakHashMap 中的值对象由普通的强引用保持。因此应该小心谨慎,确保值对象不会直接或间接地强引用其自身的键,因为这会阻止键的丢弃。注意,值对象可以通过 WeakHashMap 本身间接引用其对应的键;这就是说,某个值对象可能强引用某个其他的键对象,而与该键对象相关联的值对象转而强引用第一个值对象的键。处理此问题的一种方法是,在插入前将值自身包装在 WeakReferences 中,如:m.put(key, new WeakReference(value),然后,分别用 get 进行解包。 3.6、 TreeMap3.61 TreeMap 特点- 基于红黑树(Red-Black tr
37、ee)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。- 键值对是红黑树结构,可以保证键的排序和唯一性- 此实现不是同步的须为 TreeMap 提供排序方案根据其键的自然顺序进行排序(自定义对象须实现 Comparable 接口并重写 compare方法) 根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。3.62 分析 TreeMap 的 put 方法源码1、判断 Entry 是否有元素,没有则 new 一个,并添加新元素 2、通过自然排序与比较器排序来为
38、TreeMap 排序,优先使用 Comparator 来排序通过 Entry 对象来来确保插入元素的唯一性,它建立在 compare 方法的基础上,此方法返回 0 时,表示插入的键存在,直接替换旧值并返回。因此在使用 TreeMap 时,自定义对象须实现 Comparable 接口并重写 compare 方法或在创建TreeMap 时提供 Comparatorcmp = pare(key, t.key);if (cmp 0)t = t.right;elsereturn t.setValue(value);3、在插入元素后重新调整红黑树(即 Entry 树)源码如下public V put(K
39、key, V value) Entry t = root;/ 判断 Entry 是否有元素,没有则 new 一个if (t = null) compare(key, key); / type (and possibly null) checkroot = new Entry(key, value, parent);if (cmp map=new TreeMap(new Comparator() Overridepublic int compare(Info o1, Info o2) int num = o2.getId() - o1.getId();num = num = 0 ? o2.get
40、Adress().hashCode() - o1.getAdress().hashCode() : num;return num;);map.put(new Info(000, “hhh“), “qqq“);map.put(new Info(001, “hhh“), “aaa“);map.put(new Info(002, “hhh“), “zzz“);map.put(new Info(000, “hhh“), “qqq“);System.out.println(map);/* String 类型或基本类型包装类做 key* String 类实现了 Comparable 接口,可以进行自然排序
41、*/private static void test1() TreeMap map = new TreeMap();map.put(“a“, “111“);map.put(“b“, “123“);map.put(“c“, “121“);map.put(“c“, “121“);Set entrySet = map.entrySet();for (Entry entry : entrySet) System.out.println(“key=“ + entry.getKey() + “ value=“+ entry.getValue();/ output:/ key=a value=111/ ke
42、y=b value=123/ key=c value=1214、Collections 集合工具类与 Arrays 的使用4.1 Arrays 将数组转化为 ListArrays 的 asList 方法将数组转化为 List。 注意:获得的 List 是不可修改的String array=new String“new“,“horld“,“new“,“dream“;/ 将数组转化为 List,注意,获得的 List 是不可修改的List asList = Arrays.asList(array);/asList.add(“do it!“); / 错误,java.lang.UnsupportedO
43、perationExceptionSystem.out.println(asList);4.2Collections 大致功能: 增删改查(addAll、replaceAll、fill、binarySearch、indexOfSubList、disjoint 、max 、min) 位置变换(反转 reverse、排序 sort、随机置换 shuffle、元素交换 swap) 同步 (synchronizedMap、synchronizedList 、synchronizedCollection、synchronizedSet、synchronizedSortedMap 、synchronize
44、dSortedSet) 转化(复制 cpoy、返回视图 asLifoQueue、list、enumeration ) 等常用方法摘要static boolean addAll(Collection c, T. elements) 将所有指定元素添加到指定 collection 中。 static Queue asLifoQueue(Deque deque) 以后进先出 (Lifo) Queue 的形式返回某个 Deque 的视图。 static int binarySearch(List list, T key) 使用二分搜索法搜索指定列表,以获得指定对象。 static int binary
45、Search(List list, T key, Comparator c) 使用二分搜索法搜索指定列表,以获得指定对象。 static void copy(List dest, List src) 将所有元素从一个列表复制到另一个列表。 static boolean disjoint(Collection c1, Collection c2) 如果两个指定 collection 中没有相同的元素,则返回 true。 static void fill(List list, T obj) 使用指定元素替换指定列表中的所有元素。 static int frequency(Collection c,
46、 Object o) 返回指定 collection 中等于指定对象的元素数。 static int indexOfSubList(List source, List target) 返回指定源列表中第一次出现指定目标列表的起始位置;如果没有出现这样的列表,则返回 -1。 static int lastIndexOfSubList(List source, List target) 返回指定源列表中最后一次出现指定目标列表的起始位置;如果没有出现这样的列表,则返回 -1。 static T max(Collection coll) 根据元素的自然顺序,返回给定 collection 的最大元素
47、。 static T max(Collection coll, Comparator comp) 根据指定比较器产生的顺序,返回给定 collection 的最大元素。 static T min(Collection coll) 根据元素的自然顺序 返回给定 collection 的最小元素。 static T min(Collection coll, Comparator comp) 根据指定比较器产生的顺序,返回给定 collection 的最小元素。 static boolean replaceAll(List list, T oldVal, T newVal) 使用另一个值替换列表中出
48、现的所有某一指定值。 static void reverse(List list) 反转指定列表中元素的顺序。 static Comparator reverseOrder() 返回一个比较器,它强行逆转实现了 Comparable 接口的对象 collection 的自然顺序。 static Comparator reverseOrder(Comparator cmp) 返回一个比较器,它强行逆转指定比较器的顺序。 static void shuffle(List list) static void sort(List list) 根据元素的自然顺序 对指定列表按升序进行排序。 static void sort(List list, Comparator c) 根据指定比较器产生的顺序对指定列表进行排序。 static void swap(List list, int i, int j) 在指定列表的指定位置处交换元素。 static Collection synchronizedCollection(Collection c) static Map synchronizedMap(Map m) 返回由指定映射支持的同步(线程安全的)映射。 4.3 Collections 使用示例public class Collec