Java.util.Map
Map接口详解
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;某些实现将表现出前一种行为,而另一些则表现后一种。一般来说,试图对不合格的键或值执行操作且该操作的完成不会导致不合格的元素被插入映射中时,将可能抛出一个异常,也可能操作成功,这取决于实现本身。这样的异常在此接口的规范中标记为“可选”。
Map接口源代码
package java.util;
public interfaceMap<K, V> {
// 返回此映射中的键-值映射关系数。
int size();
// 如果此映射未包含键-值映射关系,则返回 true。
boolean isEmpty();
// 如果此映射包含指定键的映射关系,则返回 true。
boolean containsKey(Object key);
// 如果此映射将一个或多个键映射到指定值,则返回 true。
boolean containsValue(Object value);
// 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
Vget(Object key);
// 将指定的值与此映射中的指定键关联(可选操作)。
Vput(K key,V value);
// 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
Vremove(Object key);
// 从指定映射中将所有映射关系复制到此映射中(可选操作)。
void putAll(Map<? extends K, ? extends V> m);
// 从此映射中移除所有映射关系(可选操作)。
void clear();
// 返回此映射中包含的键的 Set 视图。
Set<K>keySet();
// 返回此映射中包含的值的 Collection 视图。
Collection<V>values();
// 返回此映射中包含的映射关系的 Set 视图。
Set<Map.Entry<K,V>> entrySet();
// Map.Entry是Map的一个内部接口。 Entry也是键值对,只不过Entry只是Map中的一个键值对
interface Entry<K, V>{
KgetKey();
VgetValue();
VsetValue(V value);
boolean equals(Object o);
int hashCode();
}
// 比较指定的对象与此映射是否相等。
boolean equals(Object o);
// 返回此映射的哈希码值。
int hashCode();
}
Map的遍历方法
package com.zxt.base;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public classMapIteration {
@SuppressWarnings({"unused","rawtypes"})
public static void main(String args[]) {
Map<String,Integer> testMap= newConcurrentHashMap<String, Integer>();
testMap.put("Tom", 12);
testMap.put("Kat", 16);
testMap.put("Jey", 19);
// 循环Map的几种方法
// 方法一:在for-each循环中使用entries来遍历
// 这是最常见的并且在大多数情况下也是最可取的遍历方法
// 如果遍历的是一个空map对象啊,for-each循环将抛出NullPointerException
for (Map.Entry<String,Integer> entry: testMap.entrySet()){
System.out.println("key = "+ entry.getKey()+ ", value = " + entry.getValue());
}
// 方法二:在for-each循环中遍历keys或values。
// 该方法比entrySet遍历在性能上稍好(快了10%),而且代码更加干净。
// 如果只需要map中的键或者值,可以通过keySet或values来实现遍历,而不是用entrySet
// 遍历map中的键
for (String key : testMap.keySet()) {
System.out.println("key = "+ key);
}
// 遍历map中的值
for (Integer value : testMap.values()) {
System.out.println("value = "+ value);
}
// 方法三:使用Iterator遍历(使用泛型)
Iterator<Map.Entry<String,Integer>> entries= testMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<String,Integer> entry= entries.next();
System.out.println("key = "+ entry.getKey()+ ", value = " + entry.getValue());
}
// 不使用泛型
Iteratorentriess = testMap.entrySet().iterator();
while (entriess.hasNext()) {
Map.Entryentry = (Map.Entry) entriess.next();
Stringkey = (String) entry.getKey();
Integervalue = (Integer) entry.getValue();
System.out.println("key = "+ entry.getKey()+ ", value = " + entry.getValue());
}
// 方法四:通过键找值遍历(效率低)
for (String key : testMap.keySet()) {
Integervalue = testMap.get(key);
System.out.println("key = "+ key+ ", value = " + value);
}
}
}
常用的实现Map接口的类
图1:一些实现Map接口的经典类
图2:实现Map接口类的比较
哈希映射技术
几乎所有通用Map都使用哈希映射技术。哈希映射技术是一种就元素映射到数组的非常简单的技术。由于哈希映射采用的是数组结果,那么必然存在一中用于确定任意键访问数组的索引机制,该机制能够提供一个小于数组大小的整数,我们将该机制称之为哈希函数。在Java中我们不必为寻找这样的整数而大伤脑筋,因为每个对象都必定存在一个返回整数值的hashCode方法,而我们需要做的就是将其转换为整数,然后再将该值除以数组大小取余即可。如下:
int hashValue =Maths.abs(obj.hashCode()) % size;
每个Java对象都有hashCode()方法,都可通过该方法获得它的hashCode值。得到这个对象的hashCode值之后,系统会根据该hashCode值来决定该元素的存储位置。
集合和引用
虽然集合号称存储的是Java对象,但实际上并不会真正将Java对象放入Set集合中,只是在Set集合中保留这些对象的引用而言。也就是说:Java集合实际上是多个引用变量所组成的集合,这些引用变量指向实际的Java对象。
就像引用类型的数组一样,当我们把Java对象放入数组之时,并不是真正的把Java对象放入数组中,只是把对象的引用放入数组中,每个数组元素都是一个引用变量。