根据jdk1.8的源码,系统出了一个类图,这里考虑几个Map实现的类 HashTable、HashMap、TreeMap、ConcurrentHashMap、LinkedHashMap
有图有真相,我们看图可以看出各个类实现的接口有
Serializable | Cloneable | AbctractMap | |
HashTable | √ | √ | |
HashMap | √ | √ | √ |
TreeMap | √ | √ | √ |
ConcurrentHashMap | √ | √ | |
LinkedHashMap | √ | √ | √ |
HashMap: 继承了AbstractMap
public class HashMap
extends AbstractMap
implements Map, Cloneable, Serializable {
}
hashtable,继承了 Dictionary
public class Hashtable
extends Dictionary
implements Map, Cloneable, java.io.Serializable {
}
LinkedHashMap
public class LinkedHashMap
extends HashMap
implements Map
{}
一个个解释它们都是用来干嘛的
map接口,集合类的接口,声明了map的常用方法。所有的map都继承自改接口
接口里没有声明任何方法,该接口用来实现对象的序列化反序列化功能
package java.io;
public interface Serializable {
}
接口里没有声明任何方法,该接口实现对象复制功能
package java.lang;
public interface Cloneable {
}
重要的类:AbstractMap
public abstract class AbstractMap implements Map {
transient Set keySet;
transient Collection values;
}
AbstractMap抽象类实现了一些简单且通用的方法,
在这个抽象类中有两个方法非常值得关注,keySet和values方法源码的实现可以说是教科书式的典范。
抽象类通常作为一种骨架实现,为各自子类实现公共的方法。
Java中Map类型的数据结构有相当多,AbstractMap作为它们的骨架实现实现了Map接口部分方法,也就是说为它的子类各种Map提供了公共的方法,没有实现的方法各种Map可能有所不同。
抽象类不能通过new关键字直接创建抽象类的实例,但它可以有构造方法。AbstractMap提供了一个protected修饰的无参构造方法,意味着只有它的子类才能访问(当然它本身就是一个抽象类,其他类也不能直接对其实例化),也就是说只有它的子类才能调用这个无参的构造方法。
在Map接口中其内部定义了一个Entry接口,这个接口是Map映射的内部实现用于维护一个key-value键值对,key-value存储在这个Map.Entry中。AbstractMap对这个内部接口进行了实现,一共有两个:一个是可变的SimpleEntry和一个是不可变的SimpleImmutableEntry。
实现了Map.Entry
public static class SimpleEntry
implements Entry, java.io.Serializable
{
private static final long serialVersionUID = -8499721149061103585L;
private final K key;
private V value;
}
ll另外其setValue方法稍微特殊,存入value值返回的并不是存入的值,而是返回的以前的旧值。
源码:
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
需要重点学习的是它重写的equals和hashCode方法。
public boolean equals(Object o) {
//判断参数是否是Map.Entry类型,要equals相等首先得是同一个类型
if (!(o instanceof Map.Entry))
return false;
/*将Object类型强转为Map.Entry类型,这里参数使用“?”而不是“K, V”
是因为泛型在运行时类型会被擦除,编译器不知道具体的K,V是什么类型*/
Map.Entry,?> e = (Map.Entry,?>)o;
//key和value分别调用eq方法进行判断,都返回ture时equals才相等。
return eq(key, e.getKey()) && eq(value, e.getValue());
}
eq方法
private static boolean eq(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);//三目运算符
}
要想正确重写equals方法并能正确使用,通常还需要重写hashCode方法。因为集合中的元素,判断是否一样时,先hash再equals,这也是个知识点,详细的,学习一下原理。
hashcode源码:可以看成求hashcode值时,即返回的int数据,是key.hashCode() ^ value.hashCode(),即key、value的hashcode值异或
public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
源码:
public static class SimpleImmutableEntry
implements Entry, java.io.Serializable
{
private static final long serialVersionUID = 7138329143949025153L;
private final K key;
private final V value;
}
它相比于SimpleEntry其key和value成员变量都被定义为了final类型。即定义为不可变的Entry,不提供setValue方法,不能通过setValue方法进行修改。
调用setValue方法将会抛出UnsupportedOperationException异常。即定义为不可变的Entry,不提供setValue方法,不能通过setValue方法进行修改。
public V setValue(V value) {
throw new UnsupportedOperationException();
}
它的equals和hashCode方法和SimpleEntry一致。
接下来查看AbstractMap抽象类实现了哪些Map接口中的方法。
Map中定义了一个entrySet方法,返回的是Map.Entry的Set集合,直接调用Set集合的size方法即是Map的大小。
public int size() {
return entrySet().size();
}
public abstract Set> entrySet();
调用size方法,等于0即为空。
public boolean isEmpty() {
return size() == 0;
}
public int size() {
return entrySet().size();
}
这个方法的实现较为简单,通过调用entrySet方法获取Set集合的迭代器遍历Map.Entry,获取对应的value与参数value比较。Map可以存储为null的value值,由于value=null在Map中存储比较特殊(不能计算hashCode值),所以在这里也做了判断参数value是否为空。
public boolean containsValue(Object value) {
Iterator> i = entrySet().iterator();
if (value==null) {
while (i.hasNext()) {
Entry e = i.next();
if (e.getValue()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry e = i.next();
if (value.equals(e.getValue()))
return true;
}
}
return false;
}
public boolean containsKey(Object key)
这个方法实现和containsValue一致。
这个方法实现和containsValue类似,不同的是上面相等返回boolean,这个方法返回value值。
public V get(Object key) {
Iterator> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry e = i.next();
if (e.getKey()==null)
return e.getValue();
}
} else {
while (i.hasNext()) {
Entry e = i.next();
if (key.equals(e.getKey()))
return e.getValue();
}
}
return null;
}
向Map中存入key-value键值对的方法并没有具体实现,会直接抛出一个UnsupportedOperationException异常。
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
通过参数key删除Map中指定的key-value键值对。这个方法也很简单,也是通过迭代器遍历Map.Entry的Set集合,找到对应key值,通过调用 Iterator.remove() 方法删除Map.Entry。
public V remove(Object key) {
Iterator> i = entrySet().iterator();
Entry correctEntry = null;
if (key==null) {
while (correctEntry==null && i.hasNext()) {
Entry e = i.next();
if (e.getKey()==null)
correctEntry = e;
}
} else {
while (correctEntry==null && i.hasNext()) {
Entry e = i.next();
if (key.equals(e.getKey()))
correctEntry = e;
}
}
V oldValue = null;
if (correctEntry !=null) {
oldValue = correctEntry.getValue();
i.remove();
}
return oldValue;
}
这个方法也很简单遍历传入的Map,调用put方法存入就可以了。
public void putAll(Map extends K, ? extends V> m) {
for (Map.Entry extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
public void clear()
调用entrySet方法获取Set集合再调用Set#clear()方法清空。
public void clear() {
entrySet().clear();
}
keyset
返回Map key值的Set集合。AbstractMap中定义了一个成员变量“transient Set
public abstract class AbstractMap implements Map {
transient Set keySet;
transient Collection values;
}
首先思考该方法是返回key值的Set集合,很自然的能想到一个简单的实现方式,遍历Entry数组取出key值放到Set集合中,类似下面代码:
public Set keySet() {
Set ks = null;
for (Map.Entry entry : entrySet()) {
ks.add(entry.getKey());
}
return ks;
}
这就意味着每次调用keySet方法都会遍历Entry数组,数据量大时效率会大大降低。不得不说JDK源码是写得非常好,它并没有采取遍历的方式。如果不遍历Entry,那又如何知道此时Map新增了一个key-value键值对呢?
答案就是在keySet方法内部重新实现了一个新的自定义Set集合,在这个自定义Set集合中又重写了iterator方法,这里是关键,iterator方法返回Iterator接口,而在这里又重新实现了Iterator迭代器,通过调用entrySet方法再调用它的iterator方法。下面结合代码来分析:
public Set keySet() {
Set ks = keySet; //定义的transient Set keySet
if (ks == null) { //第一次调用肯定为null,则通过下面代码创建一个Set示例
ks = new AbstractSet() { //创建一个自定义Set
public Iterator iterator() { //重写Set集合的iterator方法
return new Iterator() { //重新实现Iterator接口
private Iterator> i = entrySet().iterator(); //引用Entry的Set集合Iterator迭代器
public boolean hasNext() {
return i.hasNext(); //对key值的判断,就是对entry的判断
}
public K next() {
return i.next().getKey(); //取下一个key值,就是取entry#getKey
}
public void remove() {
i.remove(); //删除key值,就是删除entry
}
};
}
public int size() { //重写的Set#size方法
return AbstractMap.this.size(); //key值有多少就是整个Map有多大,所以调用本类的size方法即可。这个是内部类,直接使用this关键字代表这个类,应该指明是调用AbstractMap中的size方法,没有this则表示是static静态方法
}
public boolean isEmpty() { //重写的Set#isEmpty方法
return AbstractMap.this.isEmpty(); //对是否有key值,就是判断Map是否为空,,所以调用本类的isEmpty方法即可
}
public void clear() { //重写的Set#clear方法
AbstractMap.this.clear(); //清空key值,就是清空Map,,所以调用本类的clear方法即可
}
public boolean contains(Object k) { //重写Set#contains方法
return AbstractMap.this.containsKey(k); //判断Set是否包含数据k,就是判断Map中是否包含key值,所以调用本类的containsKey方法即可
}
};
keySet = ks; //将这个自定义Set集合赋值给变量keySet,在以后再次调用keySet方法时,因为keySet不为null,只需直接返回。
}
return ks;
Dictionary 类是一个抽象类,用来存储键/值对,作用和Map类相似。
给出键和值,你就可以将值存储在Dictionary对象中。一旦该值被存储,就可以通过它的键来获取它。所以和Map一样, Dictionary 也可以作为一个键/值对列表。Dictionary类已经过时了。在实际开发中,你可以实现Map接口来获取键/值的存储功能。