hashMap 和hashTable实现的类和接口

根据jdk1.8的源码,系统出了一个类图,这里考虑几个Map实现的类 HashTable、HashMap、TreeMap、ConcurrentHashMap、LinkedHashMap

hashMap 和hashTable实现的类和接口_第1张图片

有图有真相,我们看图可以看出各个类实现的接口有

  Serializable Cloneable AbctractMap
HashTable  
HashMap
TreeMap
ConcurrentHashMap  
LinkedHashMap

HashMap: 继承了AbstractMap类,实现了Map、Cloneable、Serializable接口

public class HashMap 
    extends AbstractMap
    implements Map, Cloneable, Serializable {
}

hashtable,继承了 Dictionary类,实现了接口Map, Cloneable, java.io.Serializable  LinkedHashMap

public class Hashtable
    extends Dictionary
    implements Map, Cloneable, java.io.Serializable {
}
LinkedHashMap
public class LinkedHashMap
    extends HashMap
    implements Map
{}

一个个解释它们都是用来干嘛的

一、Map

map接口,集合类的接口,声明了map的常用方法。所有的map都继承自改接口

二、java.io.Serializable接口,

接口里没有声明任何方法,该接口用来实现对象的序列化反序列化功能

package java.io;
public interface Serializable {
}

三、java.lang.Cloneable

接口里没有声明任何方法,该接口实现对象复制功能

package java.lang;
public interface Cloneable {
}

 

四. AbstractMap

重要的类: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。

1. SimpleEntry

hashMap 和hashTable实现的类和接口_第2张图片

1.1 定义

实现了Map.Entry接口,并且实现了Serializable(可被序列化)。它的方法比较简单都是取值存值的操作,对于key值的定义是一个final修饰意味着是一个不可变的引用。

  public static class SimpleEntry
        implements Entry, java.io.Serializable
    {
        private static final long serialVersionUID = -8499721149061103585L;
        private final K key;
        private V value;
}

1.2 setValue

ll另外其setValue方法稍微特殊,存入value值返回的并不是存入的值,而是返回的以前的旧值。

源码:

  public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

1.3 equals和hashCode

需要重点学习的是它重写的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());
        }

2.   SimpleImmutableEntry

hashMap 和hashTable实现的类和接口_第3张图片

2.1 定义

源码:

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方法进行修改。

2.2  setValue

调用setValue方法将会抛出UnsupportedOperationException异常。即定义为不可变的Entry,不提供setValue方法,不能通过setValue方法进行修改。

  public V setValue(V value) {
            throw new UnsupportedOperationException();
        }

   它的equals和hashCode方法和SimpleEntry一致。

3. 实现的Map接口方法

hashMap 和hashTable实现的类和接口_第4张图片

 接下来查看AbstractMap抽象类实现了哪些Map接口中的方法。

3.1 public int size()

Map中定义了一个entrySet方法,返回的是Map.Entry的Set集合,直接调用Set集合的size方法即是Map的大小。

 public int size() {
        return entrySet().size();
    }
  
 public abstract Set> entrySet();

3.2 public boolean isEmpty()

调用size方法,等于0即为空。

public boolean isEmpty() {
        return size() == 0;
    }

 public int size() {
        return entrySet().size();
    }

3.3  public boolean containsValue(Object value)

  这个方法的实现较为简单,通过调用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一致。

3.4 public V get(Object key)

  这个方法实现和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;
    }

3.5 public V put(K key, V value)

  向Map中存入key-value键值对的方法并没有具体实现,会直接抛出一个UnsupportedOperationException异常。

 public V put(K key, V value) {
        throw new UnsupportedOperationException();
    }

public V remove(Object key)

  通过参数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;
    }

7. public void putAll(Map m)

  这个方法也很简单遍历传入的Map,调用put方法存入就可以了。

 public void putAll(Map m) {
        for (Map.Entry 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 keySet”,在JDK7中keySet变量是由volatile修饰的,但在JDK8中并没有使用volatile修饰。在对keySet变量的注释中解释道,访问这些字段的方法本身就没有同步,加上volatile也不能保证线程安全。关于keySet方法的实现就有点意思了。

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

Dictionary 类是一个抽象类,用来存储键/值对,作用和Map类相似。

给出键和值,你就可以将值存储在Dictionary对象中。一旦该值被存储,就可以通过它的键来获取它。所以和Map一样, Dictionary 也可以作为一个键/值对列表。Dictionary类已经过时了。在实际开发中,你可以实现Map接口来获取键/值的存储功能。

 

你可能感兴趣的:(Java)