Map接口和AbstractMap抽象类详解
来源:互联网 发布:python arma 编辑:程序博客网 时间:2024/06/03 12:56
为了更好的理解JDK的哈希表实现,首先研究Map接口和AbstractMap抽象类,打好基础很重要~
Map接口
先来研究下Map接口,附上了注释的Map接口定义如下:
/** Map接口是键、值对接口,Map中的键是唯一的 */public interface Map<K,V> { /** Map的键值对数量,如果键值对数量大于Integer.MAX_VALUE,该方法将会返回Integer.Max_VALUE */ int size(); /** 该Map是否存在键值对 */ boolean isEmpty(); /** 该Map是否包含键key,当该Map存在键k,该方法返回值为: key == null ? k == null : key.equals(k); */ boolean containsKey(Object key); /** 该Map是否存在键值对的值为value,当该Map存在值为v,该方法返回值为: value == null ? v == null : value.equals(v); */ boolean containsValue(Object value); /** 返回该Map中键为key的值,若不存在,返回null */ V get(Object key); /** 将键值对k、value加入到Map中,若已经存在键key,则将新的value值关联到键key */ V put(K key, V value); /** 删除键key对应的键值对,返回删除之前key关联的value,若不存在key,返回null */ V remove(Object key); /** 将m的键值对加入到本Map中 */ void putAll(Map<? extends K, ? extends V> m); /** 删除本Map中所有键值对 */ void clear(); /** 返回键的集合 */ Set<K> keySet(); /** 返回值的集合 */ Collection<V> values(); /** 键值对的集合 */ Set<Map.Entry<K, V>> entrySet(); /** 键值对 */ interface Entry<K,V> { K getKey(); V getValue(); V setValue(V value); boolean equals(Object o); int hashCode(); } boolean equals(Object o); /** 该Map的哈希码 */ int hashCode();}
AbstractMap抽象类
AbstractMap抽象类实现了Map接口,它提供了一个实现Map接口的框架,简化了子类实现Map接口所需要做的很多通用的功能,子类可以继承该抽象类来决定具体怎么实现键值对的映射,该抽象类定义如下(只列出方法的声明,具体实现下面还要详细介绍):
public abstract class AbstractMap<K,V> implements Map<K,V> { //键的集合 transient volatile Set<K> keySet = null; //值的集合 transient volatile Collection<V> values = null; protected AbstractMap() { } public int size() { } public boolean isEmpty() { } public boolean containsValue(Object value) { } public boolean containsKey(Object key) { } public V get(Object key) { } public V put(K key, V value) { } public V remove(Object key) { } public void putAll(Map<? extends K, ? extends V> m) { } public void clear() { } public Set<K> keySet() { } public Collection<V> values() { } public abstract Set<Entry<K,V>> entrySet(); public boolean equals(Object o) { } public int hashCode() { } public String toString() { } protected Object clone() throws CloneNotSupportedException { } private static boolean eq(Object o1, Object o2) { } public static class SimpleEntry<K,V> implements Entry<K,V>, java.io.Serializable { } public static class SimpleImmutableEntry<K,V> implements Entry<K,V>, java.io.Serializable { }}
观察AbstractMap的结构,除了entrySet()这个方法定义为抽象的,Map接口中的其它方法都已经给出实现。
另外,该抽象类包含了两个静态内部类:SimpleEntry和SimpleImmutableEntry。
该抽象类有两个字段:keySet和values,分别存储键集合和值集合。这两个字段为何是transient修饰的?这是因为子类会决定如何去序列化,例如HashMap就实现了writeObject方法和readObject方法。
下面我们对每一个方法进行解释:
size方法
size方法的源码实现如下:
public int size() { return entrySet().size();}
该方法调用了entrySet方法,entrySet方法返回的是键值对的集合,该方法也是AbstractMap唯一没有实现的方法(定义为抽象的)。
isEmpty方法
该方法的实现非常简单,通过调用size方法,确定键值对的数量是否为0即可,源码实现如下:
public boolean isEmpty() { return size() == 0;}
containsValue方法
该方法的源码实现如下:
public boolean containsValue(Object value) { Iterator<Entry<K,V>> i = entrySet().iterator(); if (value==null) { while (i.hasNext()) { Entry<K,V> e = i.next(); if (e.getValue()==null) return true; } } else { while (i.hasNext()) { Entry<K,V> e = i.next(); if (value.equals(e.getValue())) return true; } } return false;}
该方法实现的基本思想是首先取键值对集合的迭代器,然后根据value值是否为null分别处理。
- 如果value为空,迭代器每次迭代的时候只要判断当前键值对的值是否为null即可,如果为null表示该Map包含值为null的键值对,返回true,否则继续遍历剩下的键值对。
- 如果value非空,迭代器每次迭代的时候通过调用value的equals方法判断是否和当前键值对的值相等,如果相等表示该Map包含值为value的键值对,返回true,否则继续遍历剩下的键值对。
containsKey方法
该方法的源码实现如下:
public boolean containsKey(Object key) { Iterator<Map.Entry<K,V>> i = entrySet().iterator(); if (key==null) { while (i.hasNext()) { Entry<K,V> e = i.next(); if (e.getKey()==null) return true; } } else { while (i.hasNext()) { Entry<K,V> e = i.next(); if (key.equals(e.getKey())) return true; } } return false;}
该方法的实现与containsValue的实现基本相同,不再说明。
由containsValue和containsKey两个方法可知,AbstractMap的实现是支持键和值为null的场景,这点需要注意。
get方法
该方法的源码实现如下:
public V get(Object key) { Iterator<Entry<K,V>> i = entrySet().iterator(); if (key==null) { while (i.hasNext()) { Entry<K,V> e = i.next(); if (e.getKey()==null) return e.getValue(); } } else { while (i.hasNext()) { Entry<K,V> e = i.next(); if (key.equals(e.getKey())) return e.getValue(); } } return null;}
该方法的实现和containsKey和containsValue方法的实现基本相同,不再说明。
put方法
AbstractMap对该方法的实现仅是抛出异常,子类需要去实现。因为具体的实现方法是由子类来决定的,因此这里抛出异常是合理的。源码如下:
public V put(K key, V value) { throw new UnsupportedOperationException();}
remove方法
remove方法根据键key查找键值对,然后调用迭代器的remove方法删除该项。
public V remove(Object key) { //键值对的迭代器 Iterator<Entry<K,V>> i = entrySet().iterator(); //correctEntry代表最终找到的那个键值对,若没有找到则为null Entry<K,V> correctEntry = null; if (key==null) { //key为null的情况 while (correctEntry==null && i.hasNext()) { Entry<K,V> e = i.next(); if (e.getKey()==null) //找到了键为key的键值对,while循环将结束 correctEntry = e; } } else { //key不为null的情况 while (correctEntry==null && i.hasNext()) { Entry<K,V> e = i.next(); if (key.equals(e.getKey())) correctEntry = e; } } //oldValue表示的是该方法的返回值,即将要删除的键值对的值 V oldValue = null; if (correctEntry !=null) { oldValue = correctEntry.getValue(); //调用迭代器的remove方法删除 i.remove(); } return oldValue;}
putAll方法
putAll方法通过调用put方法将m中的所有键值对加入到本map
public void putAll(Map<? extends K, ? extends V> m) { for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) put(e.getKey(), e.getValue());}
clear方法
clear方法首先通过entrySet方法得到键值对的set集合,然后通过set集合的clear方法清空本map
public void clear() { //其实是通过调用键值对set的clear方法清空的 entrySet().clear();}
keySet方法
该方法获得键的set,源码如下:
public Set<K> keySet() { if (keySet == null) { //若成员变量为空将会创建一个 keySet = new AbstractSet<K>() { //实现AbstractSet的迭代器方法 public Iterator<K> iterator() { //创建一个迭代器 return new Iterator<K>() { //通过外部类的entrySet方法获得键值对的迭代器 private Iterator<Entry<K,V>> i = entrySet().iterator(); public boolean hasNext() { return i.hasNext(); } public K next() { return i.next().getKey(); } public void remove() { i.remove(); } }; } //实现AbstractSet的size方法 public int size() { //其实是调用外部类的size方法 return AbstractMap.this.size(); } //实现AbstractSet的isEmpty方法 public boolean isEmpty() { //调用外部类的isEmpty方法 return AbstractMap.this.isEmpty(); } //实现AbstractSet的clear方法 public void clear() { //调用外部类的clear方法 AbstractMap.this.clear(); } //实现AbstractSet的conains方法 public boolean contains(Object k) { //通过外部类的containsKey实现 return AbstractMap.this.containsKey(k); } }; } return keySet;}
该方法首先判断成员变量keySet是否为空,若不为空,直接返回,否则将初始化一个keySet。
可以看到,最终其实是根据键值对的迭代器实现的。
values方法
该方法返回值的集合,实现方式和keySet类似,只不过这里返回的是Collection,而keySet返回的是Set,源码如下:
public Collection<V> values() { if (values == null) { //若成员变量values为空,则根据AbstractCollection创建一个 values = new AbstractCollection<V>() { //实现AbstractCollection的迭代器方法 public Iterator<V> iterator() { //新建一个迭代器 return new Iterator<V>() { //通过外部类的entrySet方法获得键值对的迭代器 private Iterator<Entry<K,V>> i = entrySet().iterator(); public boolean hasNext() { return i.hasNext(); } public V next() { return i.next().getValue(); } public void remove() { i.remove(); } }; } //实现AbstractCollection的size方法 public int size() { //通过调用外部类的size方法实现 return AbstractMap.this.size(); } //实现AbstractCollection的isEmpty方法 public boolean isEmpty() { //通过调用外部类的isEmpty方法实现 return AbstractMap.this.isEmpty(); } //实现AbstractColleciton的clear方法 public void clear() { //通过调用外部类的clear方法实现 AbstractMap.this.clear(); } //实现AbstractColleciton的contains方法 public boolean contains(Object v) { //通过调用外部类的containsValue方法实现 return AbstractMap.this.containsValue(v); } }; } return values;}
ketSet方法和values方法最终都是通过AbstractMap的entrySet方法实现的,通过entrySet方法获得键值对的迭代器。
equals方法
该方法判断两个AbstractMap是否相同,通过比较每一个键值对来确认两个AbstractMap是否相等,只有所有的键值对的键、值都相等,两个AbstractMap才相等,源码实现如下:
public boolean equals(Object o) { if (o == this) //若o是对象本身,返回true return true; if (!(o instanceof Map)) //如果o不是一种Map,返回false return false; Map<K,V> m = (Map<K,V>) o; if (m.size() != size()) //两个AbstractMap的长度不相等,肯定不相同,返回false return false; try { Iterator<Entry<K,V>> i = entrySet().iterator(); while (i.hasNext()) { //迭代本AbstractMap Entry<K,V> e = i.next(); K key = e.getKey(); V value = e.getValue(); if (value == null) { //当前键值对的值为null //如果m存在键为key且对应的值不为空的键值对,或者m没有键为key的键值对,说明这两个Map不相等 if (!(m.get(key)==null && m.containsKey(key))) return false; } else { //当前键值对的值不为空,通过比较该值和另一个AbstractMap的键为key对应的值是否相等即可 if (!value.equals(m.get(key))) return false; } } } catch (ClassCastException unused) { return false; } catch (NullPointerException unused) { return false; } //所有判断都通过,说明两个AbstractMap相等 return true;}
hashCode方法
该方法计算该AbstractMap对象的哈希码,源码实现如下:
public int hashCode() { int h = 0; Iterator<Entry<K,V>> i = entrySet().iterator(); while (i.hasNext()) //将键值对哈希码加起来 h += i.next().hashCode(); return h;}
其实是将每个键值对的哈希码加起来实现的。
toString方法
该方法实现比较简单,不再说明
clone方法
该方法克隆一个本对象的拷贝,字段keySet和values都设置为空,源码实现如下:
protected Object clone() throws CloneNotSupportedException { //首先调用父类的clone方法 AbstractMap<K,V> result = (AbstractMap<K,V>)super.clone(); //字段都设置为空 result.keySet = null; result.values = null; return result;}
该方法protected修饰,希望子类能够重写该方法。
以上介绍了AbstractMap的主要方法,该抽象类还定义了两个静态内部类: SimpleEntry和SimpleImmutableEntry类,这两个类在EnumMap、IdentityHashMap和WeakHashMap使用的,以后研究这几个类时再来说明这两个静态内部类。
参考
- jdk源码
- Map接口和AbstractMap抽象类详解
- Map和AbstractMap
- Map和AbstractMap
- AbstractMap抽象类源码解析
- 抽象类和接口详解
- 抽象类和接口详解
- 抽象类和接口详解
- 抽象类和接口详解
- 接口和抽象类详解
- 抽象类和接口区别深入详解
- 抽象类和接口的区别详解
- 详解接口和抽象类的区别
- 抽象类和接口的详解
- Java的接口和抽象类详解
- Java的接口和抽象类详解
- Java的接口和抽象类详解
- Java的接口和抽象类详解
- Java的接口和抽象类详解
- Docker中Mysql主从复制实践总结
- 1601: [Usaco2008 Oct]灌水
- springmvc mybatis 分页 控制层及sql语句 oracle mysql easyui
- [2017湖南集训7-9]大佬的问题 (数点问题)
- C++构造拷贝中拷贝的N种调用情况
- Map接口和AbstractMap抽象类详解
- webpack.config.js全部有关配置
- PICT生成两两组合测试用例集
- 网络流最大流EK和Dinic入门算法
- 比赛时出现的一些细节错误
- Android应用签名及团队开发
- Mysql学习历程基本语法(7)--时间日期类型
- A/B-test显著性检验
- Node.js 究竟是什么?