一.HashMap介绍

1.哈希表(hash table)

  • 在哈希表中进行添加,删除,查找等操作,时间复杂度为O(1)

  • 存储位置 = f(关键字)

    其中,这个函数f一般称为哈希函数,这个函数的设计好坏会直接影响到哈希表的优劣

    将key通过哈希算法计算出哈希值,把哈希值作为数组下标

    通过该方法建立的数组就叫做哈希表

  • 哈希冲突

    当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。

    • 解决方案:

      • 开放定址法

        开放地执法有一个公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k<=m-1)

        其中,m为哈希表的表长。di 是产生冲突的时候的增量序列。如果di值可能为1,2,3,…m-1,称线性探测再散列。

      • 再散列函数法

        当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增加。

      • 链地址法(拉链法)

        将所有关键字为同义词的记录存储在同一线性链表中

2.HashMap实现原理

2.1 Map

  • HashMap是Map的主要实现类,线程不安全的,效率高;可以存储null的key和value
  • Map就是用于存储键值对(<key,value>)的集合类,也可以说是一组键值对的映射(数学概念),在java中map是一个接口,是和collection接口同一等级的集合根接口。
  • Map特点:
    • key键值不可以重复
    • 每个key只能对应一个value,多个key可以对应一个value
    • key,value 都可以是任何引用类型(包括 null)的数据(只能是引用类型)

2.2 HashMap

  • HashMap是用数组 + 单链表 + 红黑树实现的map类。

  • HashMap 的实现不是同步的,这意味着它不是线程安全的。

  • 扩容机制和哈希函数越合理,空间成本越小,哈希函数计算结果越分散均匀。越分散发生哈希冲突的几率就越小

3.红黑树

、、、、、、、、

二.源码部分

1.基本属性

AbstractMap<K, V> :AbstractMap 提供了 Map 的基本实现,使得我们以后要实现一个 Map 不用从头开始,只需要继承 AbstractMap, 然后按需求实现/重写对应方法即可。

Map是Java集合框架的根接口,另一个是Collection接口

Cloneable接口是一个标记接口,也就是没有任何内容

Serializable 接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。

  1. public class HashMap<K,V> extends AbstractMap<K,V>
  2. implements Map<K,V>, Cloneable, Serializable {
  3. private static final long serialVersionUID = 362498820763181265L;
  4. /**
  5. * The default initial capacity - MUST be a power of two.
  6. * 默认的初始容量--必须是2的幂
  7. */
  8. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  9. /**
  10. * The maximum capacity, used if a higher value is implicitly specified
  11. * by either of the constructors with arguments.
  12. * MUST be a power of two <= 1<<30.
  13. * 哈希表的最大容量为2^30
  14. */
  15. static final int MAXIMUM_CAPACITY = 1 << 30;
  16. /**
  17. * The load factor used when none specified in constructor.
  18. * 在构造函数中未指定时使用的负载系数,负载因子默认为0.75f
  19. * 当元素的总个数>当前数组的长度 * 负载因子。数组会进行扩容,扩容为原来的两倍
  20. */
  21. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  22. /**
  23. * The bin count threshold for using a tree rather than list for a
  24. * bin. Bins are converted to trees when adding an element to a
  25. * bin with at least this many nodes. The value must be greater
  26. * than 2 and should be at least 8 to mesh with assumptions in
  27. * tree removal about conversion back to plain bins upon
  28. * shrinkage.
  29. * 当链表结点到达8时转换为红黑树
  30. */
  31. static final int TREEIFY_THRESHOLD = 8;
  32. /**
  33. * The bin count threshold for untreeifying a (split) bin during a
  34. * resize operation. Should be less than TREEIFY_THRESHOLD, and at
  35. * most 6 to mesh with shrinkage detection under removal.
  36. * 当红黑树结点小于6时重新转化为链表
  37. */
  38. static final int UNTREEIFY_THRESHOLD = 6;
  39. /**
  40. * The smallest table capacity for which bins may be treeified.
  41. * 可以树形化容器的最小表容量。
  42. * (Otherwise the table is resized if too many nodes in a bin.)
  43. * 否则,如果容器中的节点太多,则会调整表的大小
  44. * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
  45. * between resizing and treeification thresholds.
  46. * 当Table所有元素超过该值,才会进行树化(为了防止前期阶段频繁扩容和树化过程冲突)
  47. */
  48. static final int MIN_TREEIFY_CAPACITY = 64;
  1. /* ---------------- Fields -------------- */
  2. /**
  3. * The table, initialized on first use, and resized as
  4. * necessary. When allocated, length is always a power of two.
  5. * (We also tolerate length zero in some operations to allow
  6. * bootstrapping mechanics that are currently not needed.)
  7. * node数组
  8. */
  9. transient Node<K,V>[] table;
  10. /**
  11. * Holds cached entrySet(). Note that AbstractMap fields are used
  12. * for keySet() and values().
  13. * 存放键值对的集合
  14. */
  15. transient Set<Map.Entry<K,V>> entrySet;
  16. /**
  17. * The number of key-value mappings contained in this map.
  18. *键-值映射的数量
  19. */
  20. transient int size;
  21. /**
  22. * The number of times this HashMap has been structurally modified
  23. * 这个HashMap被结构修改的次数
  24. * Structural modifications are those that change the number of mappings in
  25. * the HashMap or otherwise modify its internal structure
  26. * 结构修改是HashMap中那些改变映射数量的修改或修改其内部结构
  27. * (e.g.,
  28. * rehash). This field is used to make iterators on Collection-views of
  29. * the HashMap fail-fast. (See ConcurrentModificationException).
  30. */
  31. transient int modCount;
  32. /**
  33. * The next size value at which to resize (capacity * load factor).
  34. *要调整大小的下一个大小值,边界值
  35. * @serial
  36. */
  37. // (The javadoc description is true upon serialization.
  38. // Additionally, if the table array has not been allocated, this
  39. // field holds the initial array capacity, or zero signifying
  40. // DEFAULT_INITIAL_CAPACITY.)
  41. int threshold;
  42. /**
  43. * The load factor for the hash table.
  44. *哈希表的加载因子
  45. * @serial
  46. */
  47. final float loadFactor;

2.结构

  • 如2.2中的图,顶层为动态数组,每个元素为一个node

    而每个node又是一个链表的头节点

    node的next指向下个hash值相同的结点
  1. /**
  2. * Basic hash bin node, used for most entries. (See below for
  3. * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
  4. * node内部类,node
  5. */
  6. static class Node<K,V> implements Map.Entry<K,V> {
  7. //当前的hash值
  8. final int hash;
  9. //键值
  10. final K key;
  11. //数据值
  12. V value;
  13. //指向node
  14. Node<K,V> next;
  15. //有参构造
  16. Node(int hash, K key, V value, Node<K,V> next) {
  17. this.hash = hash;
  18. this.key = key;
  19. this.value = value;
  20. this.next = next;
  21. }
  22. //返回node键值的get方法
  23. public final K getKey() { return key; }
  24. //返回node数据的get方法
  25. public final V getValue() { return value; }
  26. public final String toString() { return key + "=" + value; }
  27. public final int hashCode() {
  28. return Objects.hashCode(key) ^ Objects.hashCode(value);
  29. }
  30. //value的set方法
  31. public final V setValue(V newValue) {
  32. V oldValue = value;
  33. value = newValue;
  34. return oldValue;
  35. }
  36. public final boolean equals(Object o) {
  37. if (o == this)
  38. return true;
  39. if (o instanceof Map.Entry) {
  40. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  41. if (Objects.equals(key, e.getKey()) &&
  42. Objects.equals(value, e.getValue()))
  43. return true;
  44. }
  45. return false;
  46. }
  47. }
  1. static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
  2. TreeNode<K,V> parent; // red-black tree links
  3. TreeNode<K,V> left;
  4. TreeNode<K,V> right;
  5. TreeNode<K,V> prev; // needed to unlink next upon deletion
  6. boolean red;
  7. TreeNode(int hash, K key, V val, Node<K,V> next) {
  8. super(hash, key, val, next);
  9. }

3.构造函数

  1. /**
  2. * Constructs an empty <tt>HashMap</tt> with the specified initial
  3. * capacity and load factor.
  4. *构造一个手动设置初始容量和负载因子的空hashmap
  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. public HashMap(int initialCapacity, float loadFactor) {
  11. //如果设置的初始容量小于0,抛出异常
  12. if (initialCapacity < 0)
  13. throw new IllegalArgumentException("Illegal initial capacity: " +
  14. initialCapacity);
  15. //如果手动设置的初始容量大于最大容量,则设置为最大容量,即2^30 = 1,073,741,824
  16. if (initialCapacity > MAXIMUM_CAPACITY)
  17. initialCapacity = MAXIMUM_CAPACITY;
  18. //如若设置的负载因子<=0 或者
  19. //NaN全称是Not a Number,意思是“不是一个数字”,代表一种不合法,即float值和double值不合法。
  20. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  21. throw new IllegalArgumentException("Illegal load factor: " +
  22. loadFactor);
  23. //将负载因子赋值
  24. this.loadFactor = loadFactor;
  25. //将初始容量的2次幂值赋给边界值
  26. this.threshold = tableSizeFor(initialCapacity);
  27. }
  1. /**
  2. * Constructs an empty <tt>HashMap</tt> with the specified initial
  3. * capacity and the default load factor (0.75).
  4. *使用指定的初始容量构造一个空的HashMap
  5. * 负载因子为默认的0.75
  6. * @param initialCapacity the initial capacity.
  7. * @throws IllegalArgumentException if the initial capacity is negative.
  8. */
  9. public HashMap(int initialCapacity) {
  10. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  11. }
  1. /**
  2. * Constructs an empty <tt>HashMap</tt> with the default initial capacity
  3. * (16) and the default load factor (0.75).
  4. * 无参构造
  5. * 初始容量为默认值 16
  6. * 默认负载因子也为0.75
  7. */
  8. public HashMap() {
  9. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  10. }
  1. /**
  2. * Constructs a new <tt>HashMap</tt> with the same mappings as the
  3. * specified <tt>Map</tt>. The <tt>HashMap</tt> is created with
  4. * default load factor (0.75) and an initial capacity sufficient to
  5. * hold the mappings in the specified <tt>Map</tt>.
  6. *根据传入的map,使用相同的映射构造一个新的HashMap
  7. * 负载因子为0.75
  8. * @param m the map whose mappings are to be placed in this map
  9. * @throws NullPointerException if the specified map is null
  10. */
  11. public HashMap(Map<? extends K, ? extends V> m) {
  12. this.loadFactor = DEFAULT_LOAD_FACTOR;
  13. putMapEntries(m, false);
  14. }
  1. /**
  2. * Returns a power of two size for the given target capacity.
  3. * 返回给定目标容量的2次幂大小
  4. * 由于HashMap的capacity都是2的幂,因此这个方法用于找到大于等于initialCapacity的最小的2的幂(initialCapacity如果就是2的幂,则返回的还是这个数)。
  5. */
  6. static final int tableSizeFor(int cap) {
  7. //cap-1是当进入的数本身为二次幂数而进行转换
  8. //让最高位的1后面的位全变为1,最后加1,就得到了2次幂大小
  9. //当32为都为1的时候,容量为MAXIMUM_CAPACITY
  10. int n = cap - 1;
  11. n |= n >>> 1;
  12. n |= n >>> 2;
  13. n |= n >>> 4;
  14. n |= n >>> 8;
  15. n |= n >>> 16;
  16. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  17. }

4.hashcode

  • Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。

  • 由于和(length-1)运算,length 绝大多数情况小于2的16次方。所以始终是hashcode 的低16位(甚至更低)参与运算。要是高16位也参与运算,会让得到的下标更加散列。

    1. static final int hash(Object key) {
    2. int h;
    3. //h >>> 16是用来取出h的高16,(>>>是无符号右移)
    4. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    5. }

5.get方法

  1. public V get(Object key) {
  2. Node<K,V> e;
  3. //找不到键映射的值,返回null,否则返回value
  4. return (e = getNode(hash(key), key)) == null ? null : e.value;
  5. }
  1. /**
  2. * Implements Map.get and related methods
  3. *get操作是通过调用getNode方法
  4. * @param hash hash for key
  5. * @param key the key
  6. * @return the node, or null if none
  7. */
  8. final Node<K,V> getNode(int hash, Object key) {
  9. //
  10. Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
  11. //在table不为空的情况下
  12. if ((tab = table) != null && (n = tab.length) > 0 &&
  13. (first = tab[(n - 1) & hash]) != null) {
  14. //哈希碰撞几率小,为了效率考虑,总是检查第一个节点是不是我们想要的
  15. if (first.hash == hash && // **always check first node**
  16. ((k = first.key) == key || (key != null && key.equals(k))))
  17. return first;
  18. //以下为first node不是目标结点的情况
  19. if ((e = first.next) != null) {
  20. //判断是否为红黑树对象
  21. //instanceof是Java中的二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回true;否则,返回false。
  22. if (first instanceof TreeNode)
  23. return ((TreeNode<K,V>)first).getTreeNode(hash, key);
  24. //不是红黑树的话,链表向下查询即可
  25. do {
  26. if (e.hash == hash &&
  27. ((k = e.key) == key || (key != null && key.equals(k))))
  28. return e;
  29. } while ((e = e.next) != null);
  30. }
  31. }
  32. //没找到,返回null
  33. return null;
  34. }

instanceof是Java中的二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回true;否则,返回false。

6.put方法

  1. /**
  2. * Associates the specified value with the specified key in this map.
  3. * If the map previously contained a mapping for the key, the old
  4. * value is replaced.
  5. * 将键/值对添加到 hashMap 中
  6. * 如果先前的映射包含一个键的映射,则旧的value被替换。
  7. * @param key key with which the specified value is to be associated
  8. * @param value value to be associated with the specified key
  9. * @return the previous value associated with <tt>key</tt>, or
  10. * <tt>null</tt> if there was no mapping for <tt>key</tt>.
  11. * (A <tt>null</tt> return can also indicate that the map
  12. * previously associated <tt>null</tt> with <tt>key</tt>.)
  13. */
  14. public V put(K key, V value) {
  15. //false: onlyIfAbsent,表示改变现有值
  16. //true: 不处于创建模式
  17. return putVal(hash(key), key, value, false, true);
  18. }
  1. /**
  2. * Implements Map.put and related methods
  3. *实现了地图Map.put及相关方法
  4. * @param hash hash for key
  5. * @param key the key
  6. * @param value the value to put
  7. * @param onlyIfAbsent if true, don't change existing value
  8. * 如果为true,则不更改现有值
  9. * @param evict if false, the table is in creation mode.
  10. * 如果为false,则表示表处于创建模式
  11. * @return previous value, or null if none
  12. */
  13. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  14. boolean evict) {
  15. Node<K,V>[] tab; Node<K,V> p; int n, i;
  16. //如果table为空,则进行扩容操作
  17. if ((tab = table) == null || (n = tab.length) == 0)
  18. //n 为扩容后的长度
  19. n = (tab = resize()).length;
  20. //如果 tab[i]为空,则新建结点
  21. if ((p = tab[i = (n - 1) & hash]) == null)
  22. tab[i] = newNode(hash, key, value, null);
  23. else {
  24. Node<K,V> e; K k;
  25. //n = (tab = resize()).length
  26. //p目前指向tab[i = (n - 1) & hash],也就是数组的最后一个元素
  27. //如果p的hash有相同的,且key != null,说明键值已经存在了
  28. if (p.hash == hash &&
  29. ((k = p.key) == key || (key != null && key.equals(k))))
  30. //把p存储在e中
  31. e = p;
  32. //如果p结点为红黑树结点
  33. else if (p instanceof TreeNode)
  34. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  35. else {
  36. //遍历链表
  37. for (int binCount = 0; ; ++binCount) {
  38. //如果p结点没有后继结点,则新建链表节点
  39. if ((e = p.next) == null) {
  40. p.next = newNode(hash, key, value, null);
  41. //插入结点后判断边界值,检查是否需要把链表转化为红黑树
  42. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  43. treeifyBin(tab, hash);
  44. break;
  45. }
  46. //判断链表中的结点是否有相同键值
  47. if (e.hash == hash &&
  48. ((k = e.key) == key || (key != null && key.equals(k))))
  49. break;
  50. //p = p.next;
  51. p = e;
  52. }
  53. }
  54. //如果e不为空说明hashmap本来就存储了键值
  55. if (e != null) { // existing mapping for key
  56. //把原来的vlue保存起来
  57. V oldValue = e.value;
  58. //当相同可以修改的时候,进行重新赋值操作
  59. if (!onlyIfAbsent || oldValue == null)
  60. e.value = value;
  61. //继承HashMap的LinkedHashMap类服务的
  62. afterNodeAccess(e);
  63. return oldValue;
  64. }
  65. }
  66. ++modCount;
  67. //在进行一次扩容判断
  68. if (++size > threshold)
  69. resize();
  70. //继承HashMap的LinkedHashMap类服务的
  71. afterNodeInsertion(evict);
  72. return null;
  73. }

7.resize()方法

  1. /**
  2. * Initializes or doubles table size. If null, allocates in
  3. * accord with initial capacity target held in field threshold.
  4. * Otherwise, because we are using power-of-two expansion, the
  5. * elements from each bin must either stay at same index, or move
  6. * with a power of two offset in the new table.
  7. * 初始化或加倍表大小
  8. * @return the table
  9. */
  10. final Node<K,V>[] resize() {
  11. Node<K,V>[] oldTab = table;
  12. //旧容量
  13. int oldCap = (oldTab == null) ? 0 : oldTab.length;
  14. //旧阈值
  15. int oldThr = threshold;
  16. int newCap, newThr = 0;
  17. //如果旧的容量大于0,则以2的幂次加倍table的容量
  18. if (oldCap > 0) {
  19. //如果容量已经为最大值时,将边界值也赋值成最大值,返回原来的table
  20. if (oldCap >= MAXIMUM_CAPACITY) {
  21. threshold = Integer.MAX_VALUE;
  22. return oldTab;
  23. }
  24. //newCap * 2 小于最大容量,且oldCap大于等于默认容量(已经初始化过的table),阈值 * 2
  25. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
  26. oldCap >= DEFAULT_INITIAL_CAPACITY)
  27. newThr = oldThr << 1; // double threshold
  28. }
  29. //oldCap <= 0,但是oldThr > 0,说明构造hashtable对象时,手动设置了容量
  30. else if (oldThr > 0) // initial capacity was placed in threshold 初始容量设置为threshold
  31. newCap = oldThr;
  32. else { // zero initial threshold signifies using defaults 零初始阈值表示使用默认值,16
  33. newCap = DEFAULT_INITIAL_CAPACITY;
  34. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  35. }
  36. //??
  37. if (newThr == 0) {
  38. float ft = (float)newCap * loadFactor;
  39. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
  40. (int)ft : Integer.MAX_VALUE);
  41. }
  42. //边界值更改
  43. threshold = newThr;
  44. //告诉编译器忽略指定的警告,不用在编译完成后出现警告信息。
  45. @SuppressWarnings({"rawtypes","unchecked"})
  46. //扩容后,新建数组保存旧数组
  47. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
  48. table = newTab;
  49. //如果原数组不为空,则需要将新的数组都逐个拷贝到新的数组中
  50. if (oldTab != null) {
  51. for (int j = 0; j < oldCap; ++j) {
  52. Node<K,V> e;
  53. //如果 j 结点不为空,则将其先保存在e中,并将原结点设置为null
  54. if ((e = oldTab[j]) != null) {
  55. oldTab[j] = null;
  56. //如果该结点没有后继结点(树,链表),直接把这个结点放在新的数组中
  57. //根据当前节点的hash值与新数组容量减1做&运算,得到新数组的插入位置
  58. if (e.next == null)
  59. newTab[e.hash & (newCap - 1)] = e;
  60. //不只有一个结点,首先判断是否为树节点
  61. else if (e instanceof TreeNode)
  62. ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
  63. else { // preserve order 维护秩序
  64. Node<K,V> loHead = null, loTail = null;
  65. Node<K,V> hiHead = null, hiTail = null;
  66. Node<K,V> next;
  67. do {
  68. next = e.next;
  69. //新的位置在原位置
  70. if ((e.hash & oldCap) == 0) {
  71. //目前数组为空
  72. if (loTail == null)
  73. loHead = e;
  74. else
  75. //尾插法
  76. loTail.next = e;
  77. loTail = e;
  78. }
  79. else {
  80. //新的位置在原长度+原位置的位置
  81. if (hiTail == null)
  82. hiHead = e;
  83. else
  84. hiTail.next = e;
  85. hiTail = e;
  86. }
  87. } while ((e = next) != null);
  88. if (loTail != null) {
  89. //再将最后的结点赋值为null
  90. loTail.next = null;
  91. //再将数组头节点赋给数组
  92. newTab[j] = loHead;
  93. }
  94. //高位同样操作
  95. if (hiTail != null) {
  96. hiTail.next = null;
  97. newTab[j + oldCap] = hiHead;
  98. }
  99. }
  100. }
  101. }
  102. }
  103. //返回新的数组
  104. return newTab;
  105. }

8.遍历方法

  • 先遍历key,再取出value

    1. /**
    2. * Returns a {@link Set} view of the keys contained in this map.
    3. * The set is backed by the map, so changes to the map are
    4. * reflected in the set, and vice-versa. If the map is modified
    5. * while an iteration over the set is in progress (except through
    6. * the iterator's own <tt>remove</tt> operation), the results of
    7. * the iteration are undefined. The set supports element removal,
    8. * which removes the corresponding mapping from the map, via the
    9. * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
    10. * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
    11. * operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
    12. * operations.
    13. * 返回 hashMap 中所有 key 组成的集合视图
    14. * @return a set view of the keys contained in this map
    15. */
    16. public Set<K> keySet() {
    17. Set<K> ks = keySet;
    18. if (ks == null) {
    19. ks = new KeySet();
    20. keySet = ks;
    21. }
    22. return ks;
    23. }

    然后通过get方法都得到value

  • 直接遍历value

    1. /**
    2. * Returns a {@link Collection} view of the values contained in this map.
    3. * The collection is backed by the map, so changes to the map are
    4. * reflected in the collection, and vice-versa. If the map is
    5. * modified while an iteration over the collection is in progress
    6. * (except through the iterator's own <tt>remove</tt> operation),
    7. * the results of the iteration are undefined. The collection
    8. * supports element removal, which removes the corresponding
    9. * mapping from the map, via the <tt>Iterator.remove</tt>,
    10. * <tt>Collection.remove</tt>, <tt>removeAll</tt>,
    11. * <tt>retainAll</tt> and <tt>clear</tt> operations. It does not
    12. * support the <tt>add</tt> or <tt>addAll</tt> operations.
    13. * 返回 hashMap 中存在的所有 value 值。
    14. * @return a view of the values contained in this map
    15. */
    16. public Collection<V> values() {
    17. Collection<V> vs = values;
    18. if (vs == null) {
    19. vs = new Values();
    20. values = vs;
    21. }
    22. return vs;
    23. }
  • 通过遍历entry来取key和value

    1. /**
    2. * Returns a {@link Set} view of the mappings contained in this map.
    3. * The set is backed by the map, so changes to the map are
    4. * reflected in the set, and vice-versa. If the map is modified
    5. * while an iteration over the set is in progress (except through
    6. * the iterator's own <tt>remove</tt> operation, or through the
    7. * <tt>setValue</tt> operation on a map entry returned by the
    8. * iterator) the results of the iteration are undefined. The set
    9. * supports element removal, which removes the corresponding
    10. * mapping from the map, via the <tt>Iterator.remove</tt>,
    11. * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt> and
    12. * <tt>clear</tt> operations. It does not support the
    13. * <tt>add</tt> or <tt>addAll</tt> operations.
    14. * 返回键值对
    15. * @return a set view of the mappings contained in this map
    16. */
    17. public Set<Map.Entry<K,V>> entrySet() {
    18. Set<Map.Entry<K,V>> es;
    19. return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    20. }
  • 通过foreach方法直接遍历key和value

    1. //对 hashMap 中的每个映射执行指定的操作。
    2. @Override
    3. // BiConsumer是一个功能接口。 它接受两个参数,但不返回任何内容
    4. public void forEach(BiConsumer<? super K, ? super V> action) {
    5. Node<K,V>[] tab;
    6. //如果action为0,则抛出异常
    7. if (action == null)
    8. throw new NullPointerException();
    9. //如果size大于0, 并且将table 赋给tab
    10. if (size > 0 && (tab = table) != null) {
    11. int mc = modCount;
    12. //遍历哈希表
    13. for (int i = 0; i < tab.length; ++i) {
    14. //遍历表上的链表节点或者树节点
    15. for (Node<K,V> e = tab[i]; e != null; e = e.next)
    16. action.accept(e.key, e.value);
    17. }
    18. if (modCount != mc)
    19. throw new ConcurrentModificationException();
    20. }
    21. }

9.remove方法

  1. /**
  2. * Removes the mapping for the specified key from this map if present.
  3. * 删除 hashMap 中指定键 key 的映射关系
  4. * @param key key whose mapping is to be removed from the map
  5. * @return the previous value associated with <tt>key</tt>, or
  6. * <tt>null</tt> if there was no mapping for <tt>key</tt>.
  7. * (A <tt>null</tt> return can also indicate that the map
  8. * previously associated <tt>null</tt> with <tt>key</tt>.)
  9. */
  10. public V remove(Object key) {
  11. Node<K,V> e;
  12. //调用removeNode方法,若无则返回null,否则返回value
  13. return (e = removeNode(hash(key), key, null, false, true)) == null ?
  14. null : e.value;
  15. }
  1. /**
  2. * Implements Map.remove and related methods
  3. * 实现了MAP移除及相关方法
  4. * @param hash hash for key
  5. * @param key the key
  6. * @param value the value to match if matchValue, else ignored
  7. * @param matchValue if true only remove if value is equal
  8. * 如果为true,只在value相等时移除
  9. * @param movable if false do not move other nodes while removing
  10. * 如果为false,则在移除时不要移动其他节点
  11. * @return the node, or null if none
  12. */
  13. final Node<K,V> removeNode(int hash, Object key, Object value,
  14. boolean matchValue, boolean movable) {
  15. Node<K,V>[] tab; Node<K,V> p; int n, index;
  16. //同样的在复制的同时,做一个合法性检查
  17. if ((tab = table) != null && (n = tab.length) > 0 &&
  18. (p = tab[index = (n - 1) & hash]) != null) {
  19. Node<K,V> node = null, e; K k; V v;
  20. //如果头节点就是目标结点,就将这个节点保存起来
  21. if (p.hash == hash &&
  22. ((k = p.key) == key || (key != null && key.equals(k))))
  23. node = p;
  24. //如果不是,就遍历链表或者红黑树
  25. else if ((e = p.next) != null) {
  26. if (p instanceof TreeNode)
  27. node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
  28. else {
  29. do {
  30. //判断key值相等,则为目标节点
  31. if (e.hash == hash &&
  32. ((k = e.key) == key ||
  33. (key != null && key.equals(k)))) {
  34. node = e;
  35. break;
  36. }
  37. p = e;
  38. //直到结点遍历完
  39. } while ((e = e.next) != null);
  40. }
  41. }
  42. //在结点找到,且matchValue为false 或者 两个value相等的时候,则删除结点
  43. if (node != null && (!matchValue || (v = node.value) == value ||
  44. (value != null && value.equals(v)))) {
  45. //当该结点为红黑树的时候
  46. if (node instanceof TreeNode)
  47. ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
  48. //如果node为头节点,则将其next变成头节点
  49. else if (node == p)
  50. tab[index] = node.next;
  51. else
  52. //这时p为e的前驱结点,就将node删除
  53. p.next = node.next;
  54. ++modCount;
  55. --size;
  56. afterNodeRemoval(node);
  57. //返回结点
  58. return node;
  59. }
  60. }
  61. return null;
  62. }

三.总结

  1. HashMap的初始长度为什么是16?每次自动扩展或是手动初始化时,长度为什么是2的幂?

    • HashMap的默认长度为16,15的二进制为1111,做与运算的时候,降低了hash碰撞的几率
    • 容量为2的整数幂是为了让(2^n)-1的二进制是全1 的,符合hash均匀分布的原则
  2. 为什么要引入红黑树?

    为了提高查询效率,故在JDK1.8中引入了改进方法红黑树。此数据结构的平均查询效率为O(log n) 。

  3. 什么是加载因子、负载因子、边界值?

    • 加载因子:用于表示哈希表中元素填满的程度。

      • 加载因子越大,填满的元素越多,好处是,空间利用率高了,但,冲突的机会加大了.反之,亦同。

      • 冲突的机会越大,则查找的成本越高.反之,查找的成本越小.因而,查找时间就越小.

      • 默认的加载因子: static final float DEFAULT_LOAD_FACTOR = 0.75f;

    • 负载因子:等同于加载因子,也叫扩容因子

    • 边界值: threshold

      • threshold = capacity * loadFactory
  4. 怎么计算key值的存储位置?

    hash & (cap - 1)

    扩容时:e.hash & oldCap

HashMap(1.8)源码学习的更多相关文章

  1. Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结

    2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...

  2. HashMap与HashTable源码学习及效率比较分析

    一.个人学习后的见解: 首先表明学习源码后的个人见解,后续一次依次进行分析: 1.线程安全:HashMap是非线程安全的,HashTable是线程安全的(HashTable中使用了synchroniz ...

  3. HashMap(1.7)源码学习

    一. 1.7 和1.8区别 数据结构: 1.7: 数组 + 链表 1.8 : 数组 + 链表 + 红黑树 put: 1.7: 头插法 1.8: 尾插法 hash计算: 1.7 : Objects.ha ...

  4. hashMap源码学习记录

    hashMap作为java开发面试最常考的一个题目之一,有必要花时间去阅读源码,了解底层实现原理. 首先,让我们看看hashMap这个类有哪些属性 // hashMap初始数组容量 static fi ...

  5. 基于jdk1.8的HashMap源码学习笔记

    作为一种最为常用的容器,同时也是效率比较高的容器,HashMap当之无愧.所以自己这次jdk源码学习,就从HashMap开始吧,当然水平有限,有不正确的地方,欢迎指正,促进共同学习进步,就是喜欢程序员 ...

  6. HashSet源码学习,基于HashMap实现

    HashSet源码学习 一).Set集合的主要使用类 1). HashSet 基于对HashMap的封装 2). LinkedHashSet 基于对LinkedHashSet的封装 3). TreeS ...

  7. HashMap的源码学习以及性能分析

    HashMap的源码学习以及性能分析 一).Map接口的实现类 HashTable.HashMap.LinkedHashMap.TreeMap 二).HashMap和HashTable的区别 1).H ...

  8. 【JDK1.8】 Java小白的源码学习系列:HashMap

    目录 Java小白的源码学习系列:HashMap 官方文档解读 基本数据结构 基本源码解读 基本成员变量 构造器 巧妙的tableSizeFor put方法 巧妙的hash方法 JDK1.8的putV ...

  9. JDK1.8源码学习-HashMap

    JDK1.8源码学习-HashMap 目录 一.HashMap简介 HashMap 主要用来存放键值对,它是基于哈希表的Map接口实现的,是常用的Java集合之一. 我们都知道在JDK1.8 之前 的 ...

随机推荐

  1. PPT文档学习小练习链接

    1. <初识PPT2010> https://www.toutiao.com/i6486689592241029645/ 2. <PowerPoint2010实现折线图动态展示> ...

  2. STM32新建模板之寄存器

    创建寄存器的项目模板相对比较简单,这里是基于库文件的模板进行更改的,有不明白的小伙伴可以浏览STM32新建模板之库文件. 一.项目文件 拷贝库文件的工程模板重命名为"stm32f10x_re ...

  3. MATLAB中回归模型

    (1).一元线性回归:数学模型定义      模型参数估计   检验.预测及控制 1.回归模型:   可线性化的一元非线性回归    (2).多元线性回归:数学模型定义 模型参数估计 多元线性回归中检 ...

  4. 收到西门子发来的UG告知函怎么办?Solidworks盗版被查如何防范?厂商是怎么样查到公司在用盗版,有什么方法可以核实真假?……

    收到西门子发来的UG告知函怎么办?Solidworks盗版被查如何防范?厂商是怎么样查到公司在用盗版,有什么方法可以核实真假?--很多企业信息化管理leader或者老板都希望能够通过一些取巧的办法来防 ...

  5. SQLServer触发器调用JavaWeb接口

    这几天接到一个需求需要吧不同系统的数据库进行同步,需要我做一个中间平台进行连接,瞬间就想到了触发器调用接口然后通过API进行传递再写入另一个数据库. sqlServer触发器调用JavaWeb接口 1 ...

  6. 微信小程序云开发指南

    一.初识云开发 官方文档 小程序·云开发是微信团队联合腾讯云推出的专业的小程序开发服务. 开发者可以使用云开发快速开发小程序.小游戏.公众号网页等,并且原生打通微信开放能力. 开发者无需搭建服务器,可 ...

  7. 【pwn】DASCTF Sept 九月赛

    [pwn]DASCTF Sept 月赛 1.hehepwn 先查看保护,栈可执行,想到shellcode 这题需要注意shellcode的写法 拖入ida中分析 一直以为iso scanf不能栈溢出, ...

  8. AOP-底层原理

    AOP(底层原理) 1,AOP底层使用动态代理 (1)有两种情况动态代理 第一种 有接口情况,使用JDK动态代理 *创建接口实现类代理对象,增强类的方法 第二种 无接口情况,使用CGLIB动态代理 * ...

  9. 【webpack4.0】---webpack的基本使用(三)

    一.webpack-dev-server 1.安装 cnpm   install  webpack-dev-server  -D 2.作用 开启一个web服务,监听文件的变化并自动刷新网页,做到实时预 ...

  10. 使用 C# 开发 Kubernetes 组件,获取集群资源信息

    写什么呢 前段时间使用 C# 写了个项目,使用 Kubernetes API Server,获取信息以及监控 Kubernetes 资源,然后结合 Neting 做 API 网关. 体验地址 http ...