Map简介

#Map是以Key和Value进行存储的,这点就是与Collection做一个区别。Map的Key不可重复,但是value可以重复。而Collection最直观的感受就是其子接口Set,仅能存单独的元素,且元素不重复,可以类比Map中的key。

  • Map的存储形式是key-value即键值对形式,可以通过key的值快速的找到value的值。
  • Map中的键值对以Entry类型的对象实例化存在,Entry是Map中的一个静态接口,每次我们去取一个键值对的时候,其实就是在访问每一个Entry对象,这个静态接口定义了各种键值对的操作。
  • <K,V>形式中,Key键是不可重复的,Value可重复,一个value值可以和很多个key形成对应关系,一个Key只能对应一个value。
  • 其中K与V的类型是无所谓的。可以是任何类型。
  • Map是一个逻辑结构,如HashMap、TreeMap、Hashtable、LinkedHashMap等。

1. 文档阅读

这个部分想带大家直接的读一下关于这个Map接口的文档,并且加上一些自己的理解。希望能帮助大家手撕源码。这里的源码版本是jdk1.8。因为博主的英文也很一般,仅仅CET-6级水平,加上有道翻译,如有不对还请见谅。

文档中map介绍

  • map是一个键到值的一个对象,这个map是没有重复键的,每一个键最多映射到一个value值。


  • 这个map接口代替了字典类,字典类是一个完全抽象的类,而不是一个接口。


  • 这个接口(即map)提供了三个集合视图,允许一个map的内容被以一组键、一组值或者一个key-value去看待。如下图所示,因为视图有三种,所以遍历的时候也可以有三种方式(后续会细说)。
    map上的order即调用顺序,被定义为一种map集合试图上的迭代器返回其元素的顺序。一些map的实现,比如TreeMap,这些类对顺序做了特点的保证,而HashMap类就没有这样的定义。其实意思就是说对于map这些键的遍历,一般是以迭代器为标准的。但是有的类比如TreeMap,它就有自己的定义,而HashMap就没有。


  • 注意:(这里是关于可不可变的问题)如果把可变对象作为map的键,那就要引起注意了。如果一个对象的值,影响到了比较函数,并且这个对象还是一个键,那么这个映射行为是不被指定的。(意思就说是,如果保存的key是可变对象,比如MutableKey,那么在改变了它的对象值时,它的hashcode就改变了。那后续去查的时候就查不到了。)然后在这个禁止的行为中有一个比较特殊的就是map中的对象包含自己,把map作为一个对象。尽管把自己作为值是没问题的,但是使用注意的是:equals和hashCode方法就不再能够在map中很好的定义了。
    这段的总结就是,不要用可变对象作为key值,多用String,Integer这类不可变对象作为key,还有一点就是少用map作为对象,否则equeal和hashcode方法不好定义。


  • 所有的通用map的实现类都应该提供两个标准的构造方法:一个是无参构造,能创建一个空的map,另一个是map类型的单参数构造器,这个构造法能够创建一个新的map,然后值和原本的那个map一致,(其实就是做一个映射)。实际上,第二个构造器允许用户去复制任何映射,生成所需类的等效映射。上面这个建议没办法强制遵守(因为如果是某接口实现map接口,那没辙,因为接口没有构造方法)但是JDK中的所有通用映射都实遵循了这个建议。


  • 在这个接口中存在“破坏性”方法,即在操作时能够去修改map的方法。如果这个map不支持这个操作,那么“破坏性”方法就需要指定的抛出UnsupportedOperationException。如果出现了没定义的这种情况,但是如果用户发出一个调用对map无影响,然后这方法可能就会抛出UnsupportedOperationException异常,但是这不是必要的。举个例子,在一个不可修改的映射上调用了putAll(map)方法可能会抛出异常,如果map要“叠加”其映射的映射是空的,那就不是必须这样做。


  • 一些map接口的实现类,对它们可能包含的键与值会有限制。举个例子,一些实现类禁止空的键和空的值,而有些实现类对键的类型有限制。企图去插入一个不合法的键值对会抛出未检查的异常,传统上就是空指针异常(NullPointerException)或者ClassCastException异常。试图去查询一个不符合条件的键值对也可能抛出异常,或者返回一个简单的false。一些实现类可能会采取前一种展示的行为,一些可能会是后面一种。更普遍的是,当你试图去操作一个不符合条件的键值对,此键值对的实现不会导致将不符合条件的元素插入到映map中,那这个操作可能会抛出异常或者也可能成功,这根据实现的选项中选择。这种异常在接口的规范中被标记为“可选”。


  • 在集合框架接口中的许多方法用equals的方法定义。比如,在containsKey(Object)方法的规范中提到:如果当且仅当这个map中包含一个key的映射时,返回true,语法就是(keynull ? knull : key.equals(k)),判断key是不是空,是空那么null,不是就是调用equals方法去传入k。该规范不应被解释为暗示调用带有非空参数键的Map.containsKey(),而是将会引起key.equals(k)被调用。因为任何关键k实现都可以自由地实现优化,从而避免equals的调用。举个例子,首先比较两个key的hashcodes值,hashCode()方法的规范确保了这两个对象没有相等的hashcode。更一般的说,各种集合框架接口的实现类是很自由的去利用底层方法指定的行为,只要开发者觉得合适就行。


  • 一些map操作对于map执行递归遍历可能会失败,在map直接或间接包含自身的自引用例子中,会随之而来意外。就比如clone、equals、hashcode和toString方法,实现类可能选择性的处理自引用场景,但是目前大多数实现类都没有这样做。


  • 这个接口是java的集合框架中的成员。


  • 参数是由map维护的键类型,参数是被映射的值。


  • 作者是约书亚·布洛克,这个接口的实现类有HashMap、TreeMap、Hashtable、SorteMap、Collection、Set。然后从1.2版本开始有的这个接口。

接口的方法设计(map信息查询)

1. int size()

    int size();

返回这个集合中的key-value数量,如果键值对超过了Integer类型的最大值,那就返回Integer类型的最大值。
2. boolean isEmpty()

    boolean isEmpty();

判空操作,如果这个map集合中没有key-value映射,那就是true,否则false。
3.boolean containsKey(Object key)

    boolean containsKey(Object key);

用key来做索引判断,如果map中存在这个key那就返回true。但是如果说这个键的类型不适合应该映射那就抛出异常。这就是之前map文档注意的,有的实现类对键的类型有要求。

4.boolean containsValue(Object value)

    boolean containsValue(Object value);

用value来做索引判断,如果map中存在这个value那就返回true。如果这个value值如果不符合返回值类型的设定,那就是抛ClassCastException异常。如果这个value值是空,并且实现类map不允许空的话,那就是抛出NullPointerException异常。
5.V get(Object key)

    V get(Object key);

通过这个方法,可以传入key值,从而获得value值。如果map没有这个key的映射,那就返回空。
文档的其他内容就是关于返回值空不空的问题,有的类会限制是否可以为空值。英文文档在上述,内容比较好理解,这里不赘述。
参数就是key值,返回值就是与key相关联的value或者null。如果类型不符合,那就抛出ClassCastException,如果是空值,并且这个实现类不允许空,那么抛出NullPointerException

接口的方法设计(map中映射修改)

6.V put(K key, V value);

    V put(K key, V value);

插入操作,向map集合中插入键值对。如果说是原本map中不存在那么可以直接添加这个映射,如果是原本存在,那么会替换旧值。
文档中有四种异常的抛出。
UnsupportedOperationException:如果put操作再实现map的类中不被支持,那么旧抛出这个异常。
ClassCastException:如果如果这组key和value的类不被允许插入到map中,则会抛出这个异常。
NullPointerException:如果key或者value的值是空的,并且这个实现类是不允许空值的,那么就会抛出这个异常。
IllegalArgumentException:如果指定的键或者值的某些属性被阻止存储进去,那么就会报非法参数。
注意区分ClassCastException和IllegalArgumentException,一个是因为类不被允许,一个是属性不被允许。
7.V remove(Object key);

V remove(Object key);

如果一个键存在,则从这个映射中移除该键值对,更一般的说判断这个key在不在,如果key存在,那么该映射就会被删除。如果映射不存在,那么就会返回null,但是如果这个实现类是允许空值的,那可能存的就是空值。
可能抛出的三个异常:
UnsupportedOperationException:如果这个remove操作在map中不被支持,那么则会抛出这异常。
ClassCastException:如果key的类型是不合理对于这个map,则会抛出这个异常。
NullPointerException:如果key是空的或者这个map是不允许空值的那么就会报错。
8.void putAll(Map<? extends K, ? extends V> m);

void putAll(Map<? extends K, ? extends V> m);

这个方法可以做一个复制操作,输入的参数是一个map,可以将所有的映射从指定的映射复制到此映射中。这个操作相当于对每个key做了一个put。
输入参数:需要被映射的map,也包含四个异常,如UnsupportedOperationException、ClassCastException、NullPointerException、IllegalArgumentException。四个异常的描述和其他几个方法一致。
9.void clear();

 void clear();

调用这个方法,清除map里的所有映射关系。异常的报错只包含UnsupportedOperationException,即实现类不支持此方法则报错。

接口的方法设计(key-value的查看方式)

10.Set keySet()

  Set<K> keySet();

在前面map文档的批注中,有三种视图,一个从key角度,一个从value角度,另一个从key-value角度。因此这个keySet()方法就是从key的角度进行查看。调用这个方法,就能够返回一个Set集合,其中的元素就是保存的key对象。但是Set其实是由Map支持的,所以其实就是对map的更改会反映在集合中,反之亦然。如果在对集合进行迭代的时修改了映射,那么迭代的这个顺序结果是未定的,即需要实现类自己去规定。这个key值得集合支持删除,不支持添加,并且Set中的元素是不可重复的。
** 11. Collection values(); **

   Collection<V> values();

同理,这个是从值得角度查看的,Collection集合是支持重复值的。集合是映射支持的,所以对映射的更改会反映在集合中,反之亦然。如果在集合中进行迭代,除了迭代器自己的remove操作,其他的结果都是未定义的,就比如遍历的结果顺序是不定的。value值得这个集合也是支持删除,不支持添加。
12.Set<Map.Entry<K, V>> entrySet();

Set<Map.Entry<K, V>> entrySet();

第三个视图是从key-value的角度进行遍历的,把一个key-value的映射关系作为一个对象进行封装。然后把这个对象以Set集合进行返回。同理与SetKey方法,只是集合中的内容不同。

封装映射关系的Entry接口

entry是内部接口,其保存的是映射关系(key与value)的关系。上述的entrySet()方法返回的集合就是这个类。获取一个个map项的唯一方法就是用集合视图迭代器。这些视图在迭代器存在期间是有效的,更正式的说一个映射项entry,在迭代器返回后修改了后台映射,那么映射项的行为是未定义的,除非通过对映射项执行setValue操作。
###entry接口中提供的方法如下
1. K getKey();

 K getKey();

这个接口的entry是包含了key和value的,所以通过getKey()方法即可获得映射关系中的key。
异常:IllegalStateException,即接口可能会抛出这个异常,由于这个entry已经被删除了,但是这个操作不是刚需。
2.V getValue();

V getValue();

相对的,这个方法就是获得映射关系中的value值。异常也是IllegalStateException,即如果这个映射没了。那就得不到,就报错。同理这个也不是必须实现的。

3. V setValue(V value);

V setValue(V value);

这个方法作用是去更改值的,即改变原本映射中的value,写入原本的映射中。但是如果这个映射已经被删除了,那么这个调用的结果就没定义了。(通过迭代器删除的)
参数就是新的value,然后会返回旧的value值。
异常共有5个:
UnsupportedOperationException:后台如果不支持put操作,那就表明不支持这个方法。由此也可以看出替换value值的方法,在实现类中可能是通过put方法执行的。
ClassCastException:如果要存储的这个value类不被支持,那么就报这个错。
IllegalArgumentException:如果被存储的这个value的属性值不被支持,那么就是这个错。
NullPointerException:如果备份映射不允许空值,但指定了空值那就报错。
IllegalStateException:这个错误原因就是如果映射都被删除了,那就存不进去,就报错,但是这个不是刚需。
4.boolean equals(Object o);

boolean equals(Object o);

此方法就是对比两个entry是否是同一个,如果是那就是true。更细致的说就是如果两个key都不是空,那么对比下是不是key相同,然后再对比value,通过getvalue值,确保两个都不是空,并且还相等,那么就是相同的entry。这样就确保了在不同接口中实现map.entry还能让equals方法生效。
5.int hashCode();

int hashCode();

返回的一个对应这个entry的哈希编码。这个哈希编码是由key的哈希和value的哈希功能构成的。这就确保了上一个euqals方法到底比的是什么。实质上比的是哈希值。
6.public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey()

public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {return (Comparator<Map.Entry<K, V>> & Serializable)(c1, c2) -> c1.getKey().compareTo(c2.getKey());}

这个方法就是返回一个比较器,比较器的比较是按key的自然顺序。比较器是可被序列化的,当在比较空的时候会返回空指针异常。
7. public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue()

public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {return (Comparator<Map.Entry<K, V>> & Serializable)(c1, c2) -> c1.getValue().compareTo(c2.getValue());}

有key的比较那么就有value的比较,返回一个value自然顺序的构造器。同样也是可被序列化,如果是null则会报空指针异常。
8.public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp)

public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {Objects.requireNonNull(cmp);return (Comparator<Map.Entry<K, V>> & Serializable)(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());}

这个方法可以与前面比较key的方法做一个区别,上一个key方法是用自然顺序进行返回的,但是这个是需要你传入一个比较器,通过这个比较器进行排序,从而对两个key进行对比。
9.public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp)

public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {Objects.requireNonNull(cmp);return (Comparator<Map.Entry<K, V>> & Serializable)(c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());}

同理这个方法也是传入一个比较器对value值进行对比。

接口的方法设计(比较与hashing)

1.boolean equals(Object o);

boolean equals(Object o);

对两个对象进行比较,更准确的说在map中就是比较key 和value会不会同时都相等,如果会的话那么就返回true,否则false。实质背后调用的是内部接口entry中的equals方法,比较的也是entry中的key与value。这样确保了不同的实现类比较的东西是相同的。

2.int hashCode();

int hashCode();

返回此map的哈希编码。map的哈希值被定义为每一个entry的entrySet()中的值的和。提到Set实际就是每一个key的哈希编码进行加和就是这个map的哈希值。

接口的方法设计(默认方法)

其实在一开始我都以为接口是只能写抽象方法的,不能有方法体,但是在1.8default关键字打破了这个规矩,即接口中能够定义具体的方法了。如果接口不想修改或者重写这个方法,那么实现了这个接口的类可以直接调用。
**1.default V getOrDefault(Object key, V defaultValue) **

default V getOrDefault(Object key, V defaultValue) {V v;return (((v = get(key)) != null) || containsKey(key))? v: defaultValue;}

输入key,然后返回这个key映射的value,但是如果说这个映射不存在value值,那么就会返回一个默认定义的defaultValue。
2.default void forEach(BiConsumer<? super K, ? super V> action)

default void forEach(BiConsumer<? super K, ? super V> action) {Objects.requireNonNull(action);for (Map.Entry<K, V> entry : entrySet()) {K k;V v;try {k = entry.getKey();v = entry.getValue();} catch(IllegalStateException ise) {// this usually means the entry is no longer in the map.throw new ConcurrentModificationException(ise);}action.accept(k, v);}}

//未完待续

手撕Java源码系列之Map接口相关推荐

  1. java 源码系列 - 带你读懂 Reference 和 ReferenceQueue

    java 源码系列 - 带你读懂 Reference 和 ReferenceQueue https://blog.csdn.net/gdutxiaoxu/article/details/8073858 ...

  2. java源码系列:HashMap底层存储原理详解——4、技术本质-原理过程-算法-取模具体解决什么问题

    目录 简介 取模具体解决什么问题? 通过数组特性,推导ascii码计算出来的下标值,创建数组非常占用空间 取模,可保证下标,在HashMap默认创建下标之内 简介 上一篇文章,我们讲到 哈希算法.哈希 ...

  3. 手撕OpenCV源码之resize(INTER_AREA)

    手撕OpenCV源码之resize<INTER_AREA> resize在modules/imgproc/src/文件件中,首先看resize API的函数实现: void resize( ...

  4. 华为OD机试(21-40)老题库解析Java源码系列连载ing

    华为OD机试算法题新老题库练习及源码 老题库 21.字符串序列判定 22.最长的指定瑕疵度的元音子串 23.处理器问题 24.单向链表中间节点 25.字符串重新排列.字符串重新排序 26.完美走位 2 ...

  5. JavaWeb三大组件之一——Filter过滤器源码解析(全面手撕Filter源码,需要耐心看完)

    什么是Filter? 本文所说的Filter是JavaWeb中常见常使用的过滤器.Filter的作用是拦截前端发送给后端的请求,一般是用于权限过滤.日志记录.图片转换.加密.数据压缩等操作. 大致流程 ...

  6. 【手撕MyBatis源码】MyBatis映射体系

    文章目录 映射工具MetaObject 基本功能 底层结构 获取属性值的流程 ResultMap结果集映射 手动映射 自动映射 嵌套子查询 循环依赖 懒加载 原理 内部结构 Bean代理过程 联合查询 ...

  7. JAVA源码系列-ArrayList

    前言 ArrayList是一个基于数组的数据结构,Java1.8版本加入了Lambda匿名内部类的新特性.而ArrayList实现了java.util.function的接口,进而为了支持Lambda ...

  8. 手撕LongAdder-add()源码

    最近喜欢上阅读国外大牛写的源码,真的感觉每一步都是精华,膜拜膜拜. 特此将代码的每一步解析总结出来分享给大家~~~ /*** Adds the given value.** @param x the ...

  9. 手撕Iterator源码

    public abstract class AbstractList<E>{//操作数(操作了添加数据.删除数据,操作数都会++)protected transient int modCo ...

最新文章

  1. json qbytearray 串 转_如何通过QByteArray在JSON中存储QPixmap?
  2. 用好idea这几款插件,可以帮你少写30%的代码!
  3. mysql acer_Acer电脑【no bootable device】引导修复
  4. Memcache面试题
  5. 是时候对XSLT说“Goodbye”了吗?
  6. 【Objective-C学习笔记】变量和基本的数据类型
  7. 【Tools】Visual Studio 2010下载和安装
  8. 【大话Hibernate】hibernate事务管理
  9. 当我们在谈论内存时,我们在谈论什么
  10. .NET 5.0正式发布,有什么功能特性(翻译)
  11. mysql怎么初始化自增值_MySQL 重置自增值
  12. python 单向链表逆序_python实现单链表反转(经典笔试题)
  13. 国密SM2系列算法验证工具
  14. oracle dbf文件是什么,.ora文件、.dbf文件和.dat文件的区别
  15. 阿里云如何购买mysql_如何选购配置阿里云数据库RDS MySQL的流程 新手必看
  16. 酸狗带你进入JAVA世界
  17. 天池竞赛——工业蒸汽量预测(完整代码分享)
  18. 这是初次的感觉 好象天空般晴朗
  19. 麟龙指标通达信指标公式源码_通达信麟龙指标套二主图+副图指标 贴图
  20. 这36张图包含了高中英语所有生活用词,果断收藏!

热门文章

  1. Spring循环依赖问题解决
  2. java switch 表达式_switch case语句错误:case表达式必须是常量表达式
  3. 3D游戏建模到底难不难?零基础学6个月,能否找到工作?
  4. 关于视频编码器的作用详细介绍
  5. 浅谈jQuery技术的ajax框架async同步和异步执行原理
  6. JDK环境变量配置以及误删Path变量后恢复方法
  7. 2023年全国最新高校辅导员精选真题及答案28
  8. return的用法?
  9. LVDS信号采集,Pal制模拟视频输出
  10. android教育游戏设计方案,一个简单Android游戏的设计 详细设计