日期:2017/11/11
下面详细介绍了 HashMap 、HashTable 、TreeMap 的实现方法,从数据结构、实现原理、源码分析三个方面进行阐述,从而就 Map 做一个简单的总结。
四、Map
java.util
接口 Map<K,V>
-
类型参数:
-
K
- 此映射所维护的键的类型 -
V
- 映射值的类型
-
所有已知子接口:
- Bindings, ConcurrentMap<K,V>, ConcurrentNavigableMap<K,V>, LogicalMessageContext, MessageContext, NavigableMap<K,V>, SOAPMessageContext, SortedMap<K,V>
-
所有已知实现类:
- AbstractMap, Attributes, AuthProvider, ConcurrentHashMap, ConcurrentSkipListMap, EnumMap, HashMap, Hashtable, IdentityHashMap, LinkedHashMap, PrinterStateReasons, Properties, Provider, RenderingHints, SimpleBindings, TabularDataSupport, TreeMap, UIDefaults, WeakHashMap
public interface Map<K,V>
将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。
此接口取代 Dictionary 类,后者完全是一个抽象类,而不是一个接口。
Map 接口提供三种collection 视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序定义为迭代器在映射的 collection 视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。
注:将可变对象用作映射键时必须格外小心。当对象是映射中某个键时,如果以影响 equals 比较的方式更改了对象的值,则映射的行为将是不确定的。此项禁止的一种特殊情况是不允许某个映射将自身作为一个键包含。虽然允许某个映射将自身作为值包含,但请格外小心:在这样的映射上 equals 和 hashCode 方法的定义将不再是明确的。
所有通用的映射实现类应该提供两个“标准的”构造方法:一个 void(无参数)构造方法,用于创建空映射;一个是带有单个 Map 类型参数的构造方法,用于创建一个与其参数具有相同键-值映射关系的新映射。实际上,后一个构造方法允许用户复制任意映射,生成所需类的一个等价映射。尽管无法强制执行此建议(因为接口不能包含构造方法),但是 JDK 中所有通用的映射实现都遵从它。
此接口中包含的“破坏”方法可修改其操作的映射,如果此映射不支持该操作,这些方法将抛出 UnsupportedOperationException。如果是这样,那么在调用对映射无效时,这些方法可以(但不要求)抛出 UnsupportedOperationException。例如,如果某个不可修改的映射(其映射关系是“重叠”的)为空,则对该映射调用 putAll(Map)
方法时,可以(但不要求)抛出异常。
某些映射实现对可能包含的键和值有所限制。例如,某些实现禁止 null 键和值,另一些则对其键的类型有限制。尝试插入不合格的键或值将抛出一个未经检查的异常,通常是 NullPointerException 或 ClassCastException。试图查询是否存在不合格的键或值可能抛出异常,或者返回 false;某些实现将表现出前一种行为,而另一些则表现后一种。一般来说,试图对不合格的键或值执行操作且该操作的完成不会导致不合格的元素被插入映射中时,将可能抛出一个异常,也可能操作成功,这取决于实现本身。这样的异常在此接口的规范中标记为“可选”。
此接口是 Java Collections Framework 的成员。
(1)Map的特点
是由键和值组成的。每一个元素是一个键值对,由两部分组成。
键唯一,值可以重复。
它的实现类的数据结构只针对键有效,跟值无关。
(2)Map和Collection的区别?
A:Map双列集合,由键值对组成;键唯一,值可以重复;数据结构针对键有效
B:Collection单列集合,有单个元素组成;Set唯一,List可重复;数据结构针对元素有效
(3)Map的功能概述(自己补齐方法和中文意思)
A:添加功能
put
B:移除功能
remove
clear
C:判断功能
containsKey,containsValue
isEmpty
D:获取功能
size
get
keySet
values
entrySet
(4)Map的遍历
A:根据键找值
获取所有键的集合
遍历键的集合,得到每一个键
根据键找值
B:根据键值对对象找键和值
获取所有的键值对对象的集合
遍历键值对对象的集合,得到每一个键值对对象
根据键值对对象获取键和值
Map是不能直接被实例化的。作为接口,它只能先被子类实现,然后通过子类被实例化而出现在大众面前.
3.1 HashMap
java.util
类 HashMap<K,V>
java.lang.Objectjava.util.AbstractMap<K,V>
java.util.HashMap<K,V>
-
类型参数:
-
K
- 此映射所维护的键的类型 -
V
- 所映射值的类型
-
所有已实现的接口:
- Serializable, Cloneable, Map<K,V>
-
直接已知子类:
- LinkedHashMap, PrinterStateReasons
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
例子1 ---- 基本操作
package com.code.Map.HashMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.TreeSet;
/*
* A:创建一个HashMap集合
* B:创建一个ArrayList集合
* C:装牌
* D:洗牌
* E:发牌
* F:看牌
*/
public class PokerDemo {
public static void main(String[] args) {
HashMap<Integer,String> hm = new HashMap<Integer,String>();
ArrayList<Integer> array = new ArrayList<Integer>();
String[] colors = { "♠", "♥", "♣", "♦" };
String[] numbers = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q",
"K", "A", "2" };
int key = 0;
for(String number : numbers){
for(String color : colors){
//String.concat: 将指定字符串连接到此字符串的结尾。
String value = color.concat(number);
//hashMap: 在此映射中 关联 指定值与指定键。如果 key 没有任何映射关系,则返回值:null,
hm.put(key, value);
// System.out.println("------");
// System.out.println(hm.put(key, value));
array.add(key);
key++;
}
}
hm.put(key, "little king");
array.add(key);
key++;
hm.put(key, "big king");
array.add(key);
//Collections 此类完全由在 collection 上进行操作或返回 collection 的静态方法组成。它包含在 collection 上操作的多态算法,
//即“包装器”,包装器返回由指定 collection 支持的新 collection,以及少数其他内容。
//shuffle : 使用默认随机源对指定列表进行置换。
System.out.println("*****"+array.toString());
Collections.shuffle(array);
System.out.println("*****"+array.toString());
TreeSet<Integer> caoCao = new TreeSet<Integer>();
TreeSet<Integer> liuBei = new TreeSet<Integer>();
TreeSet<Integer> sunQuan = new TreeSet<Integer>();
TreeSet<Integer> diPai = new TreeSet<Integer>();
for (int x = 0; x < array.size(); x++) {
if (x >= array.size() - 3) {
diPai.add(array.get(x));
} else if (x % 3 == 0) {
caoCao.add(array.get(x));
} else if (x % 3 == 1) {
liuBei.add(array.get(x));
} else if (x % 3 == 2) {
sunQuan.add(array.get(x));
}
}
// 看牌
lookPoker("曹操", caoCao, hm);
lookPoker("刘备", liuBei, hm);
lookPoker("孙权", sunQuan, hm);
lookPoker("底牌", diPai, hm);
}
public static void lookPoker(String name, TreeSet<Integer> array,HashMap<Integer,String> hm){
System.out.println(name + " \' card is ");
for(Integer s : array){
System.out.println(s + " ");
}
System.out.println();
}
}
*****[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53]
*****[50, 5, 32, 51, 35, 13, 36, 38, 53, 7, 24, 8, 0, 1, 15, 41, 9, 12, 47, 4, 33, 40, 29, 18, 22, 30, 49, 10, 21, 42, 26, 37, 23, 17, 2, 48, 34, 52, 46, 20, 27, 19, 6, 14, 11, 45, 16, 44, 39, 28, 25, 3, 31, 43]
曹操 ' card is
0
6
7
10
17
20
22
26
34
36
39
40
41
45
47
50
51
刘备 ' card is
1
2
4
5
9
14
16
21
24
27
28
29
30
35
37
38
52
孙权 ' card is
8
11
12
13
15
18
19
23
25
32
33
42
44
46
48
49
53
底牌 ' card is
3
31
43
HashMap 与 HashTable 区别:
* A:HashMap是线程不安全的,效率高。允许使用 null 值和 null 键。
* B:Hashtable是线程安全的,效率低。不允许使用 null 值和 null 键。
3.2 HashTable
java.util
类 Hashtable<K,V>
java.lang.Objectjava.util.Dictionary<K,V>
java.util.Hashtable<K,V>
-
所有已实现的接口:
- Serializable, Cloneable, Map<K,V>
-
直接已知子类:
- Properties, UIDefaults
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Serializable
此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null
对象都可以用作键或值。
为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode
方法和 equals
方法。
Hashtable
的实例有两个参数影响其性能:初始容量 和加载因子。容量 是哈希表中桶 的数量,初始容量 就是哈希表创建时的容量。注意,哈希表的状态为 open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。加载因子 是对哈希表在其容量自动增加之前可以达到多满的一个尺度。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用 rehash 方法的具体细节则依赖于该实现。
通常,默认加载因子(.75)在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 get 和 put 操作,都反映了这一点)。
初始容量主要控制空间消耗与执行 rehash
操作所需要的时间损耗之间的平衡。如果初始容量大于 Hashtable 所包含的最大条目数除以加载因子,则永远 不会发生 rehash
操作。但是,将初始容量设置太高可能会浪费空间。
如果很多条目要存储在一个 Hashtable
中,那么与根据需要执行自动 rehashing 操作来增大表的容量的做法相比,使用足够大的初始容量创建哈希表或许可以更有效地插入条目。
下面这个示例创建了一个数字的哈希表。它将数字的名称用作键:
Hashtable<String, Integer> numbers
= new Hashtable<String, Integer>();
numbers.put("one", 1);
numbers.put("two", 2);
numbers.put("three", 3);
要获取一个数字,可以使用以下代码:
Integer n = numbers.get("two");
if (n != null) {
System.out.println("two = " + n);
}
}
由所有类的“collection 视图方法”返回的 collection 的 iterator 方法返回的迭代器都是快速失败 的:在创建 Iterator 之后,如果从结构上对 Hashtable 进行修改,除非通过 Iterator 自身的 remove 方法,否则在任何时间以任何方式对其进行修改,Iterator 都将抛出ConcurrentModificationException
。因此,面对并发的修改,Iterator 很快就会完全失败,而不冒在将来某个不确定的时间发生任意不确定行为的风险。由 Hashtable 的键和元素方法返回的 Enumeration 不 是快速失败的。
注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误做法:迭代器的快速失败行为应该仅用于检测程序错误。
从Java 2 平台 v1.2起,此类就被改进以实现 Map
接口,使它成为 Java Collections Framework 中的一个成员。不像新的 collection 实现,Hashtable
是同步的
-
从以下版本开始:
package com.code.Map.HashTable;
/*
* 面试题:
* 1:HashMap和Hashtable的区别?
* A:HashMap是线程不安全的,效率高。允许使用 null 值和 null 键。
* B:Hashtable是线程安全的,效率低。不允许使用 null 值和 null 键。
*
* 2:List,Set,Map等接口是否都继承自Map接口
* List,Set都继承自Collection接口。
* Map本身就是顶层接口
*
* 3:你常见的集合类有哪些,都有什么方法?
* Collection
* |--List
* |--ArrayList
* |--Vector
* |--LinkedList
* |--Set
* |--HashSet
* |--LinkedHashSet
* |--TreeSet
* Map
* |--HashMap
* |--LinkedHashMap
* |--Hashtable
* |--TreeMap
*
* ArrayList
* 添加功能,移除功能,判断功能,获取,长度
* HashSet
* 添加功能,移除功能,判断功能,获取,长度
* HashMap
* 添加功能,移除功能,判断功能,获取,长度
*/
import java.util.Hashtable;
public class HashTableDemo{
public static void main(String[] args){
// HashMap<String, String> hm = new HashMap<String, String>();
Hashtable<Integer,String> ht = new Hashtable<Integer,String>();
ht.put(1,"周小琴");
System.out.println("1"+ht);
ht.put(2,"特兰普");
System.out.println("2"+ht);
System.out.println(ht.put(2, "特里"));
System.out.println("3"+ht);
System.out.println("----------");
for(int i = 0; i < 10; i++){
System.out.println(ht.get(i));
}
}
}
1{1=周小琴}
2{2=特兰普, 1=周小琴}
特兰普
3{2=特里, 1=周小琴}
----------
null
周小琴
特里
null
null
null
null
null
null
null
特点:1. 当有键值(KEY)相同并put 进去了,那么会覆盖原有键值对!!
2. Hashtable 的变量名直接可以访问整个map;或者提供KEY 和 get() 方法获取映射的值,而 get() 方法会返回旧的被覆盖的值!!
3.3 TreeMap
java.util
类 TreeMap<K,V>
java.lang.Objectjava.util.AbstractMap<K,V>
java.util.TreeMap<K,V>
-
类型参数:
-
K
- 此映射维护的键的类型 -
V
- 映射值的类型
-
所有已实现的接口:
- Serializable, Cloneable, Map<K,V>, NavigableMap<K,V>, SortedMap<K,V>
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, Serializable
package com.code.Map.TreeMap;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
public class TreeMapTest {
public static void main(String [] args){
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String s = sc.nextLine();
TreeMap<Character,Integer> tm = new TreeMap<Character,Integer>();
char[] c = s.toCharArray();
// 遍历字符串,得到每一个字符
for (char ch : c) {
// 拿这个字符作为键到map里面找值
Integer i = tm.get(ch);
// 返回null
if (i == null) {
// 存储
tm.put(ch, 1);
} else {
// 把值++,重新存储
i++;
tm.put(ch, i);
}
}
StringBuilder sb = new StringBuilder();
Set<Character> set = tm.keySet();
for(Character key : set){
Integer value = tm.get(key);
sb.append(key).append("(").append(value).append(")");
}
// 把StringBuilder转换为字符串
String result = sb.toString();
System.out.println(result);
}
}
输出结果:
请输入一个字符串:
aaabbbccccdd
a(3)b(3)c(4)d(2)
解释:
(1) 上面的例子4实现的功能是:对输入的字符串进行字符个数的统计,里面用到的数据结构有:TreeMap + Set + String[] +StringBuilder + char[] 。