jdk 集合大家族之Map

前言:

之前章节复习了Collection接口相关,此次我们来一起回顾一下Map相关

。本文基于jdk1.8。

1. HashMap

1.1 概述

  • HashMap相对于List的数据结构而言,它是键值对的集合。主要通过提供key值来取相对应的value的值。而不是通过遍历来查找所需要的值。
  • key值允许一个为null value不限制
    • key通常使用String Integer这种不可变类作为key
  • 通过数组链表红黑树来实现,如下图所示

1.2 源码分析

  • 成员变量
  1. /**
  2. * The default initial capacity - MUST be a power of two.
  3. */
  4. // 数组长度默认的容量为16 必须是2的幂
  5. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  6. /**
  7. * The maximum capacity, used if a higher value is implicitly specified
  8. * by either of the constructors with arguments.
  9. * MUST be a power of two <= 1<<30.
  10. */
  11. // 最大容量 为2^30
  12. static final int MAXIMUM_CAPACITY = 1 << 30;
  13. /**
  14. * The load factor used when none specified in constructor.
  15. */
  16. // 默认的负载因子,当构造时没有指定负载因子时使用此值
  17. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  18. /**
  19. * The bin count threshold for using a tree rather than list for a
  20. * bin. Bins are converted to trees when adding an element to a
  21. * bin with at least this many nodes. The value must be greater
  22. * than 2 and should be at least 8 to mesh with assumptions in
  23. * tree removal about conversion back to plain bins upon
  24. * shrinkage.
  25. */
  26. // 链表的长度大于8时转换为红黑树
  27. static final int TREEIFY_THRESHOLD = 8;
  28. /**
  29. * The bin count threshold for untreeifying a (split) bin during a
  30. * resize operation. Should be less than TREEIFY_THRESHOLD, and at
  31. * most 6 to mesh with shrinkage detection under removal.
  32. */
  33. // 链表中的长度小于6时从红黑树转换会链表
  34. static final int UNTREEIFY_THRESHOLD = 6;
  35. /**
  36. * The smallest table capacity for which bins may be treeified.
  37. * (Otherwise the table is resized if too many nodes in a bin.)
  38. * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
  39. * between resizing and treeification thresholds.
  40. */
  41. // 链表转换为红黑树也不是随便转换的,需要满足Map中最少有MIN_TREEIFY_CAPACITY个Node才能允许树形化(将链表转化为红黑树)。
  42. // 否则表中的过多节点会选择扩容
  43. // 为了避免扩容和树形化的冲突的选择冲突,此值最少为 4 * TREEIFY_THRESHOLD
  44. // 数组长度小于64也不会将链表转为红黑树(链表长度大于8但是小于64选择扩容数组,重新hash)
  45. static final int MIN_TREEIFY_CAPACITY = 64;
  46. /**
  47. * The table, initialized on first use, and resized as
  48. * necessary. When allocated, length is always a power of two.
  49. * (We also tolerate length zero in some operations to allow
  50. * bootstrapping mechanics that are currently not needed.)
  51. */
  52. //
  53. transient Node<K,V>[] table;
  54. /**
  55. * Holds cached entrySet(). Note that AbstractMap fields are used
  56. * for keySet() and values().
  57. */
  58. transient Set<Map.Entry<K,V>> entrySet;
  59. /**
  60. * The number of key-value mappings contained in this map.
  61. */
  62. // Map中键值对的数量
  63. transient int size;
  64. /**
  65. * The number of times this HashMap has been structurally modified
  66. * Structural modifications are those that change the number of mappings in
  67. * the HashMap or otherwise modify its internal structure (e.g.,
  68. * rehash). This field is used to make iterators on Collection-views of
  69. * the HashMap fail-fast. (See ConcurrentModificationException).
  70. */
  71. // 修改次数
  72. transient int modCount;
  73. /**
  74. * The next size value at which to resize (capacity * load factor).
  75. *
  76. * @serial
  77. */
  78. // (The javadoc description is true upon serialization.
  79. // Additionally, if the table array has not been allocated, this
  80. // field holds the initial array capacity, or zero signifying
  81. // DEFAULT_INITIAL_CAPACITY.)
  82. // 下一次扩容的阈值 Map数组长度 * 负载因子
  83. int threshold;
  84. /**
  85. * The load factor for the hash table.
  86. *
  87. * @serial
  88. */
  89. // 负载因子
  90. final float loadFactor;
  • 构造函数
  1. /**
  2. * Constructs an empty <tt>HashMap</tt> with the specified initial
  3. * capacity and load factor.
  4. *
  5. * @param initialCapacity the initial capacity
  6. * @param loadFactor the load factor
  7. * @throws IllegalArgumentException if the initial capacity is negative
  8. * or the load factor is nonpositive
  9. */
  10. // 构造指定容量 指定负载因子的HashMap
  11. public HashMap(int initialCapacity, float loadFactor) {
  12. if (initialCapacity < 0)
  13. throw new IllegalArgumentException("Illegal initial capacity: " +
  14. initialCapacity);
  15. if (initialCapacity > MAXIMUM_CAPACITY)
  16. initialCapacity = MAXIMUM_CAPACITY;
  17. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  18. throw new IllegalArgumentException("Illegal load factor: " +
  19. loadFactor);
  20. this.loadFactor = loadFactor;
  21. this.threshold = tableSizeFor(initialCapacity); // tableSizeFor 在大于等于initialCapacity 范围内找最接近initialCapacity的2的n次幂
  22. }
  23. static final int tableSizeFor(int cap) {
  24. int n = cap - 1; // 防止cap为2的n次幂 造成翻倍现象
  25. n |= n >>> 1;
  26. n |= n >>> 2;
  27. n |= n >>> 4;
  28. n |= n >>> 8;
  29. n |= n >>> 16;
  30. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  31. }
  32. /**
  33. * Constructs an empty <tt>HashMap</tt> with the specified initial
  34. * capacity and the default load factor (0.75).
  35. *
  36. * @param initialCapacity the initial capacity.
  37. * @throws IllegalArgumentException if the initial capacity is negative.
  38. */
  39. // 构造指定容量 负载因子为默认0.75的HashMap
  40. public HashMap(int initialCapacity) {
  41. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  42. }
  43. /**
  44. * Constructs an empty <tt>HashMap</tt> with the default initial capacity
  45. * (16) and the default load factor (0.75).
  46. */
  47. // 构造默认容量为16 负载因子为默认0.75的HashMap
  48. public HashMap() {
  49. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  50. }
  51. /**
  52. * Constructs a new <tt>HashMap</tt> with the same mappings as the
  53. * specified <tt>Map</tt>. The <tt>HashMap</tt> is created with
  54. * default load factor (0.75) and an initial capacity sufficient to
  55. * hold the mappings in the specified <tt>Map</tt>.
  56. *
  57. * @param m the map whose mappings are to be placed in this map
  58. * @throws NullPointerException if the specified map is null
  59. */
  60. public HashMap(Map<? extends K, ? extends V> m) {
  61. this.loadFactor = DEFAULT_LOAD_FACTOR;
  62. putMapEntries(m, false);
  63. }
  • 内部键值对的存储 Node 和 TreeNode
  1. /**
  2. * Basic hash bin node, used for most entries. (See below for
  3. * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
  4. */
  5. static class Node<K,V> implements Map.Entry<K,V> {
  6. final int hash;
  7. final K key;
  8. V value;
  9. Node<K,V> next;
  10. Node(int hash, K key, V value, Node<K,V> next) {
  11. this.hash = hash;
  12. this.key = key;
  13. this.value = value;
  14. this.next = next;
  15. }
  16. public final K getKey() { return key; }
  17. public final V getValue() { return value; }
  18. public final String toString() { return key + "=" + value; }
  19. public final int hashCode() {
  20. return Objects.hashCode(key) ^ Objects.hashCode(value);
  21. }
  22. public final V setValue(V newValue) {
  23. V oldValue = value;
  24. value = newValue;
  25. return oldValue;
  26. }
  27. public final boolean equals(Object o) {
  28. if (o == this)
  29. return true;
  30. if (o instanceof Map.Entry) {
  31. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  32. if (Objects.equals(key, e.getKey()) &&
  33. Objects.equals(value, e.getValue()))
  34. return true;
  35. }
  36. return false;
  37. }
  38. }
  39. /**
  40. * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
  41. * extends Node) so can be used as extension of either regular or
  42. * linked node.
  43. */
  44. // 继承了Node(LinkedHashMap.Entry继承了HashMap.Node)
  45. static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
  46. TreeNode<K,V> parent; // red-black tree links
  47. TreeNode<K,V> left;
  48. TreeNode<K,V> right;
  49. TreeNode<K,V> prev; // needed to unlink next upon deletion
  50. boolean red;
  51. TreeNode(int hash, K key, V val, Node<K,V> next) {
  52. super(hash, key, val, next);
  53. }
  54. }
  • 添加元素 put方法
  1. //
  2. public V put(K key, V value) {
  3. return putVal(hash(key), key, value, false, true);
  4. }
  5. // 计算hashcode 无符号右移16位然后做异或运算,比取余效率高
  6. static final int hash(Object key) {
  7. int h;
  8. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  9. }
  10. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  11. boolean evict) {
  12. Node<K,V>[] tab; Node<K,V> p; int n, i;
  13. // 为空 或者数组长度为0 进行扩容
  14. if ((tab = table) == null || (n = tab.length) == 0)
  15. n = (tab = resize()).length;
  16. // (n-1)&hash 对数组长度减一与运算计算map数组中的下表 位运算比取余效率高
  17. // 如果数组的桶为空则直接放到桶内
  18. if ((p = tab[i = (n - 1) & hash]) == null)
  19. tab[i] = newNode(hash, key, value, null);
  20. else { // 桶内已经有其他数据了,进行比较 扩容判断等操作
  21. // e用于原来的值的返回
  22. Node<K,V> e; K k;
  23. // p点为桶中第一个节点 如果与桶内第一个节点hash相同 ,判断是否key是否equals 相同则把原来的值赋值给它
  24. if (p.hash == hash &&
  25. ((k = p.key) == key || (key != null && key.equals(k))))
  26. e = p;
  27. else if (p instanceof TreeNode) // 如果是树节点则存入
  28. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  29. else { // 桶中为链表
  30. for (int binCount = 0; ; ++binCount) { // 遍历链表
  31. if ((e = p.next) == null) { // 如果遍历到结尾了 则加到结尾
  32. p.next = newNode(hash, key, value, null);
  33. // 遍历到大于等于8时 进行转换红黑树
  34. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  35. treeifyBin(tab, hash);
  36. break;
  37. }
  38. if (e.hash == hash &&
  39. ((k = e.key) == key || (key != null && key.equals(k)))) // 如果遍历到key hash相同 并且equals时跳出循环
  40. break;
  41. // 继续向后遍历
  42. p = e;
  43. }
  44. }
  45. // 存在相同值的key 替换并返回旧的value
  46. if (e != null) { // existing mapping for key
  47. V oldValue = e.value;
  48. if (!onlyIfAbsent || oldValue == null)
  49. e.value = value;
  50. // 默认为空实现,允许我们修改完成后做一些操作
  51. afterNodeAccess(e);
  52. return oldValue;
  53. }
  54. }
  55. ++modCount;
  56. // 达到 负载因子*capacity 进行扩容
  57. if (++size > threshold)
  58. resize();
  59. // 默认也是空实现,允许我们插入完成后做一些操作
  60. afterNodeInsertion(evict);
  61. return null;
  62. }
  • 转换成树
  1. final void treeifyBin(Node<K,V>[] tab, int hash) {
  2. int n, index; Node<K,V> e;
  3. // 此处如果数组的长度小于64 只是进行扩容 rehash 并未进行红黑树操作
  4. if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
  5. resize();
  6. else if ((e = tab[index = (n - 1) & hash]) != null) {
  7. TreeNode<K,V> hd = null, tl = null;
  8. do { // 将节点的数据结构转成TreeNode 形成一个新的链表
  9. TreeNode<K,V> p = replacementTreeNode(e, null);
  10. if (tl == null)
  11. hd = p;
  12. else {
  13. p.prev = tl;
  14. tl.next = p;
  15. }
  16. tl = p;
  17. } while ((e = e.next) != null);
  18. // 将新的树结点链表赋给第index个桶
  19. if ((tab[index] = hd) != null)
  20. // 真正的转成红黑树
  21. hd.treeify(tab);
  22. }
  23. }
  24. final void treeify(Node<K,V>[] tab) {
  25. TreeNode<K,V> root = null;
  26. // 遍历链表中的每一个TreeNode 当前的节点为x
  27. for (TreeNode<K,V> x = this, next; x != null; x = next) {
  28. next = (TreeNode<K,V>)x.next;
  29. x.left = x.right = null;
  30. // 当root为空 当前节点设置成根节点 根节点设置成黑色(x.red=false)
  31. if (root == null) {
  32. x.parent = null;
  33. x.red = false;
  34. root = x;
  35. }
  36. else {
  37. K k = x.key;
  38. int h = x.hash;
  39. Class<?> kc = null;
  40. // 从根节点开始遍历 找到x插入的位置
  41. for (TreeNode<K,V> p = root;;) {
  42. int dir, ph;
  43. K pk = p.key;
  44. // 如果当前结点的hash值小于根结点的hash值,方向dir = -1;
  45. if ((ph = p.hash) > h)
  46. dir = -1;
  47. // 如果当前结点的hash值大于根结点的hash值,方向dir = 1;
  48. else if (ph < h)
  49. dir = 1;
  50. // 如果x结点的key没有实现comparable接口,或者其key和根结点的key相等(k.compareTo(x) == 0)仲裁插入规则
  51. // 只有k的类型K直接实现了Comparable<K>接口,才返回K的class,否则返回null,间接实现也不行。
  52. else if ((kc == null &&
  53. (kc = comparableClassFor(k)) == null) ||
  54. (dir = compareComparables(kc, k, pk)) == 0)
  55. // 仲裁插入规则
  56. dir = tieBreakOrder(k, pk);
  57. TreeNode<K,V> xp = p;
  58. // 如果p的左右结点都不为null,继续for循环,否则执行插入
  59. if ((p = (dir <= 0) ? p.left : p.right) == null) {
  60. x.parent = xp;
  61. if (dir <= 0)
  62. xp.left = x;
  63. else
  64. xp.right = x;
  65. // 插入后进行树的调整
  66. root = balanceInsertion(root, x);
  67. break;
  68. }
  69. }
  70. }
  71. }
  72. moveRootToFront(tab, root);
  73. }

其他map相关集合的如下图所示,concurrentHashMap 更高性能的支持并发,可以单独讲一篇。

LinkdedHashMap

LinkedHashMap保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的,在遍历的时候会比HashMap慢,有HashMap的全部特性。

TreeMap

TreeMap将存储的键值对进行默认排序,并且还能够指定排序的比较器,是线程不安全的。TreeMap不允许键值为null

Hashtable

是线程安全的HashMap 但是他的线程安全控制是通过任意一个线程只能有一个线程写Hashtable,所以并发性不是很好。相比于ConcurrentHashMap使用的是分段锁,并发性更好。所以推荐使用ConcurrentHashMap。

参考资料

jdk 集合大家族之Map的更多相关文章

  1. jdk 集合大家族之Collection

    jdk 集合大家族之Collection 前言:   此处的集合指的是java集合框架中的实现了Collection接口相关的类.所以主要为List Set 和 Queue 其他章节会专门介绍Map相 ...

  2. java提高篇(二十)-----集合大家族

          在编写java程序中,我们最常用的除了八种基本数据类型,String对象外还有一个集合类,在我们的的程序中到处充斥着集合类的身影!java中集合大家族的成员实在是太丰富了,有常用的Arra ...

  3. java集合-集合大家族

    在编写 Java 程序中,我们最常用的除了八种基本数据类型,String 对象外还有一个集合类,在我们的的程序中到处充斥着集合类的身影!Java 中集合大家族的成员实在是太丰富了,有常用的 Array ...

  4. Java 集合系列 08 Map架构

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  5. java 集合大家族

    在编写java程序中,我们最常用的除了八种基本数据类型,String对象外还有一个集合类,在我们的的程序中到处充斥着集合类的身影!java中集合大家族的成员实在是太丰富了,有常用的ArrayList. ...

  6. 集合框架之Map接口

    Map是将键映射到值的对象.一个映射不能包含重复的键:每个键最多只能映射到一个值. Map 接口提供三种collection视图,允许以键集.值集或键-值映射关系集的形式查看某个映射的内容.映射顺序定 ...

  7. JDK(一)JDK集合框架

    JDK中的集合框架分为两大类:Collection和Map.Collection以一组Object的形式保存元素,Map以Key-Value对的形式保存元素. 上图列出的类并不完整,只列举了平时比较常 ...

  8. Java学习:集合双列Map

    数据结构 数据结构: 数据结构_栈:先进后出 入口和出口在同一侧 数据结构_队列:先进先出 入口和出口在集合的两侧 数据结构_数组: 查询快:数组的地址是连续的,我们通过数组的首地址可以找到数组,通过 ...

  9. Java集合框架之map

    Java集合框架之map. Map的主要实现类有HashMap,LinkedHashMap,TreeMap,等等.具体可参阅API文档. 其中HashMap是无序排序. LinkedHashMap是自 ...

随机推荐

  1. [视频] FFMpeg 基本组成和入门示例

    目录 FFmpeg基本组成 编解码工具 ffmpeg.exe ffmpeg.exe的工作流程 播放器 ffplay.exe 多媒体分析器 ffprobe FFmpeg基本组成 AVFormat 封装了 ...

  2. Gitlab 快速部署及日常维护 (一)

    一.GitLab简介GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的web服务 二.GitLab系统架构git用户的主目录通常是/home/git(~ ...

  3. 5.Fanout交换机之新订单通知商户场景

    标题 : 5.Fanout交换机之新订单通知商户场景 目录 : RabbitMQ 序号 : 5 const string newOrderQueueName = "neworder-queu ...

  4. TCP协议与UDP协议的区别以及与TCP/IP协议的联系

    先介绍下什么是TCP,什么是UDP. 1. 什么是TCP? TCP(Transmission Control Protocol,传输控制协议)是面向连接的.可靠的字节流服务,也就是说,在收发数据前,必 ...

  5. C# 类 (10) - 命名空间NameSpace

    NameSpace 命名空间是一系列 类型的集合,比如很多方法,很多类,集合在一个空间(myspace)里,谁想用就先 using myspace,然后直接用不using myspace的话,想用里面 ...

  6. 016.NET5_MVC_视图组件扩展定制

    视图组件 1. 呈现页面响应的某一部分而不是整个响应 2. 包括在控制器和视图之间发生的关注分类和可测试优势 3.可以具有参数和业务逻辑 4. 通常在页面局部调用 如何自定义视图组件? 1.Razor ...

  7. Chrome DevTools & performance & keywords

    Chrome DevTools & performance & keywords performance / 优化性能 https://developers.google.com/we ...

  8. 为什么 Koa 的官方文档那么丑呀?

    为什么 Koa 的官方文档那么丑呀? koa.js https://koajs.com/ 代码高亮 # $ nvm install 7, node.js v7.x.x+ $ yarn add koa ...

  9. Dart & data type(static / dynamic)

    Dart & data type(static / dynamic) Darts 飞镖 标枪 javelin/darts type https://dartpad.dartlang.org/ ...

  10. vue & vue router & dynamic router

    vue & vue router & dynamic router https://router.vuejs.org/guide/essentials/dynamic-matching ...