equals、hashcode和==的区别

在介绍HashMap之前,我想先阐述一下我对这三者的理解,equals这个方法呢,就是在判断是否为同一对象(注意,这里的同一对象和相同的内存地址是不同的),是否为同一对象其实看一看做一种我们对事物的主观定义,如果我是个佛系青年,认为世间万物都是相同的,那么我只需要在equals里只return一个true。hashcode我们可以看做是一个对象的表示符,同一对象的表示符肯定是一样的,不同对象的表示符理论上来说应该是不同的,但是现实永远不太尽如人意,不同的对象hashcode相同,就是所谓的冲突。所以当我们重写Object中的equals方法的时候,一定要记得重写hashcode方法==很好理解,它就表示是不是同一内存地址。

接下来我们就从原理和源码两方面去介绍一下hashcode,并且对hashcode的非线程安全进行一些简单的讨论,下文参考了https://www.cnblogs.com/softidea/p/7261111.html这篇文章

一、HashMap原理

在最初的HashMap中,其底层的实现数组加链表的数据结构,基本单元为Entry,但是在java8之后进行了优化,增加了红黑树,底层结构也由Entry变成了Node和TreeNode组合完成,但是TreeNode其实还是继承自Node。

数组的优缺点:优点是根据下标进行查找,十分迅速,缺点是在数组中插入元素,删除元素效率极低

链表的优缺点:优点是对于增删操作非常方便 ,但是查找起来却很慢

红黑树优缺点:优点是查找非常迅速,缺点是插入元素的时候又费时间又费空间

HashMap就是综合了以上几点,构成的一种数据结构:首先用一个数组来构成散列表,然后用链表来解决冲突,当冲突项大于默认值8时,会将链表转化成一颗红黑树,提高查询效率(链表就是一颗退化树)。

如下图所示:

从上图我们可以发现HashMap是由Entry数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

二、源码分析

1、底层数据类

  1. /**
  2. * 这里可见,Node是实现了Entry
  3. */
  4. static class Node<K,V> implements Map.Entry<K,V> {
  5. /**
  6. * 注意hash和key都是final修饰的,说明作为key需要是不可变值,比如String很常用
  7. * 如果采用自己创建的对象
  8. */
  9. final int hash;
  10. final K key;
  11. //value是可变的
  12. V value;
  13. //指向下一个节点的指针
  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.  
  23. public final K getKey() { return key; }
  24. public final V getValue() { return value; }
  25. public final String toString() { return key + "=" + value; }
  26.  
  27. /**
  28. * key的hash值和value的hash值做与操作,所以key和value需要重写hashCode方法
  29. */
  30. public final int hashCode() {
  31. return Objects.hashCode(key) ^ Objects.hashCode(value);
  32. }
  33.  
  34. //这里设置新值的时候会返回旧值
  35. public final V setValue(V newValue) {
  36. V oldValue = value;
  37. value = newValue;
  38. return oldValue;
  39. }
  40.  
  41. //这里需要注意要重写equals方法
  42. public final boolean equals(Object o) {
  43. if (o == this)
  44. return true;
  45. if (o instanceof Map.Entry) {
  46. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  47. if (Objects.equals(key, e.getKey()) &&
  48. Objects.equals(value, e.getValue()))
  49. return true;
  50. }
  51. return false;
  52. }
  53. }
  1. /**
  2. * 这里介绍一下红黑树的五条性质
  3. * 1、节点是红色或者是黑色;
  4. * 2、根节点是黑色;
  5. * 3、每个叶节点(NIL或空节点)是黑色;
  6. * 4、每个红色节点的两个子节点都是黑色的(也就是说不存在两个连续的红色节点);
  7. * 5、从任一节点到其没个叶节点的所有路径都包含相同数目的黑色节点;
  8. */
  9. static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
  10. TreeNode<K,V> parent; // red-black tree links
  11. TreeNode<K,V> left;
  12. TreeNode<K,V> right;
  13. TreeNode<K,V> prev; // needed to unlink next upon deletion
  14. boolean red;
  15. }

2、HashMap的常量和属性

  1. public class HashMap<K,V> extends AbstractMap<K,V>
  2. implements Map<K,V>, Cloneable, Serializable {
  3.  
  4. /**
  5. * 默认初始化容量,必须是2的次方。这个容量就是table的长度
  6. */
  7. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  8.  
  9. /**
  10. * 这里指定了一个容量的上限,如果自己指定的值大于上限的话,就采用该默认值
  11. */
  12. static final int MAXIMUM_CAPACITY = 1 << 30;
  13.  
  14. /**
  15. * 默认加载因子,当没有指定加载因子的时候会采用该值,这个值的意义在于,当有效值比容量大于加载因子时
  16. * 会扩容table数组
  17. */
  18. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  19.  
  20. /**
  21. * 这个是一个链表在多长时转化为红黑树,默认为8
  22. */
  23. static final int TREEIFY_THRESHOLD = 8;
  24.  
  25. /**
  26. * 当一颗树的节点少于6个的时候,将这棵树转化为链表
  27. */
  28. static final int UNTREEIFY_THRESHOLD = 6;
  29.  
  30. /**
  31. * 当哈希表中的容量大于这个值时,表中的桶才能进行树形化 否则桶内元素太多时会扩容,而不是树形化 为了
  32. * 避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
  33. */
  34. static final int MIN_TREEIFY_CAPACITY = 64;
  35. /**
  36. * 这个数组会在第一次使用时初始化,并且在条件合适的时候重构,它的长度一定是2的整数次方
  37. */
  38. transient Node<K,V>[] table;
  39.  
  40. /**
  41. * 一个包含所有节点的Set
  42. */
  43. transient Set<Map.Entry<K,V>> entrySet;
  44.  
  45. /**
  46. * Map中的当前元素数量
  47. */
  48. transient int size;
  49.  
  50. /**
  51. * 这个是Map当中元素的修改次数(这里的修改只是说增加和减少元素时,该量会加一)
  52. */
  53. transient int modCount;
  54.  
  55. /**
  56. * 当大于这个值的时候会执行重构数组操作(capacity * load factor).
  57. */
  58. int threshold;
  59.  
  60. /**
  61. * 自定义的加载因子
  62. */
  63. final float loadFactor;
  64. }

3、HashMap的resize

  1. final Node<K,V>[] resize() {
  2. //将原来的table指针保存
  3. Node<K,V>[] oldTab = table;
  4. //获取原来数组的长度,oldTab为null说明还没有进行初始化
  5. int oldCap = (oldTab == null) ? 0 : oldTab.length;
  6. //保存以前重构table的阈值
  7. int oldThr = threshold;
  8. int newCap, newThr = 0;
  9. //oldCap > 0表示已经初始化过了
  10. if (oldCap > 0) {
  11. //当原来的容量已经达到最大容量的时候,将阈值设置为Integer.MAX_VALUE,这样就不会再发生重构的情况
  12. if (oldCap >= MAXIMUM_CAPACITY) {
  13. threshold = Integer.MAX_VALUE;
  14. return oldTab;
  15. }
  16. //否则将旧的容量扩大两倍,当它小于最大容量,并且旧的容量大于初始化最小容量的时候,将新的阈值设置为旧的阈值的两倍
  17. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
  18. oldCap >= DEFAULT_INITIAL_CAPACITY)
  19. newThr = oldThr << 1; // double threshold
  20. }
  21. else if (oldThr > 0) //虽然还没有初始化,但是设置过了阈值,将旧的阈值设置为新的容量
  22. newCap = oldThr;
  23. else { //没有初始化阈值的时候采用默认算法计算阈值
  24. newCap = DEFAULT_INITIAL_CAPACITY;
  25. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  26. }
  27. if (newThr == 0) {//对应oldCap = 0 && oldThr > 0的情况
  28. float ft = (float)newCap * loadFactor;
  29. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
  30. (int)ft : Integer.MAX_VALUE);
  31. }
  32. threshold = newThr;
  33. @SuppressWarnings({"rawtypes","unchecked"})
  34. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
  35. table = newTab;
  36. if (oldTab != null) {
  37. //将旧数组中的元素全部取出,重新映射到新数组中
  38. for (int j = 0; j < oldCap; ++j) {
  39. Node<K,V> e;
  40. if ((e = oldTab[j]) != null) {
  41. oldTab[j] = null;
  42. if (e.next == null)
  43. //(newCap - 1)是一个尾部全部为1的数
  44. newTab[e.hash & (newCap - 1)] = e;
  45. else if (e instanceof TreeNode)//判断旧的节点是一个树节点,则对树进行操作,重构树或者变成链表等等
  46. ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
  47. else {
  48. // 对原来的链表部分进行重构
  49. Node<K,V> loHead = null, loTail = null;
  50. Node<K,V> 所以新索引要么是原索引,要不就是原索引+oldCap = null, hiTail = null;
  51. Node<K,V> next;
  52. do {
  53. next = e.next;
  54. if ((e.hash & oldCap) == 0) {
  55. if (loTail == null)
  56. loHead = e;
  57. else
  58. loTail.next = e;
  59. loTail = e;
  60. }
  61. else {
  62. if (hiTail == null)
  63. hiHead = e;
  64. else
  65. hiTail.next = e;
  66. hiTail = e;
  67. }
  68. } while ((e = next) != null);
  69. //在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit
  70. //对原来的链表部分进行重构
  71. if (loTail != null) {
  72. loTail.next = null;
  73. newTab[j] = loHead;
  74. }
  75. if (hiTail != null) {
  76. hiTail.next = null;
  77. newTab[j + oldCap] = hiHead;
  78. }
  79. }
  80. }
  81. }
  82. }
  83. return newTab;
  84. }

举个例子(例子来自https://blog.csdn.net/lianhuazy167/article/details/66967698

4、修改方法

4.1、put

  1. public V put(K key, V value) {
  2. return putVal(hash(key), key, value, false, true);
  3. }
  4.  
  5. /**
  6. * @param hash 计算出来key的hash值
  7. * @param key key的值
  8. * @param value value的值
  9. * @param onlyIfAbsent 当为true的时候,如果key对应有值,则不修改这个值
  10. * @param evict 当为false时,表示这个处于创建模式,现在由于afterNodeInsertion中什么都没有,这里没有实际意
  11. * evict参数用于LinkedHashMap中的尾部操作
  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为null或者tab的长度为0时,即table尚未初始化,此时通过resize()方法得到初始化的table
  17. if ((tab = table) == null || (n = tab.length) == 0)
  18. n = (tab = resize()).length;
  19. //当p为null时,表明tab[i]上没有任何元素,那么接下来就new第一个Node节点,调用newNode方法返回新节点赋值给tab[i]
  20. if ((p = tab[i = (n - 1) & hash]) == null)
  21. tab[i] = newNode(hash, key, value, null);
  22. else {
  23. Node<K,V> e; K k;
  24. //HashMap中判断key相同的条件是key的hash相同,并且符合equals方法。这里判断了p.key是否和插入的key相等,如果相等,则将p的引用赋给e
  25. //这里为什么要把p赋值给e,而不是直接覆盖原值呢?答案很简单,现在我们只判断了第一个节点,后面还可能出现key相同,所以需要在最后一并处理
  26. if (p.hash == hash &&
  27. ((k = p.key) == key || (key != null && key.equals(k))))
  28. e = p;
  29. else if (p instanceof TreeNode)
  30. //现在开始了第一种情况,p是红黑树节点,那么肯定插入后仍然是红黑树节点,所以我们直接强制转型p后调用TreeNode.putTreeVal方法,返回的引用赋给e
  31. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  32. else {
  33. for (int binCount = 0; ; ++binCount) {
  34. //最后一个参数为新节点的next,这里传入null,保证了新节点继续为该链表的末端
  35. if ((e = p.next) == null) {
  36. p.next = newNode(hash, key, value, null);
  37. //插入成功后,要判断是否需要转换为红黑树,因为插入后链表长度加1,而binCount并不包含新节点,所以判断时要将临界阈值减1
  38. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  39. treeifyBin(tab, hash);
  40. break;
  41. }
  42. //在遍历链表的过程中,我之前提到了,有可能遍历到与插入的key相同的节点,此时只要将这个节点引用赋值给e,最后通过e去把新的value覆盖掉就可以了
  43. if (e.hash == hash &&
  44. ((k = e.key) == key || (key != null && key.equals(k))))
  45. break;
  46. p = e;
  47. }
  48. }
  49. //左边注释为jdk自带注释,说的很明白了,针对已经存在key的情况做处理
  50. if (e != null) { // existing mapping for key
  51. V oldValue = e.value;
  52. if (!onlyIfAbsent || oldValue == null)
  53. e.value = value;
  54. afterNodeAccess(e);
  55. return oldValue;
  56. }
  57. }
  58. //收尾工作,值得一提的是,对key相同而覆盖oldValue的情况,在前面已经return,不会执行这里,所以那一类情况不算数据结构变化,并不改变modCount值
  59. ++modCount;
  60. //当HashMap中存在的node节点大于threshold时,hashmap进行扩容
  61. if (++size > threshold)
  62. resize();
  63. afterNodeInsertion(evict);
  64. return null;
  65. }

4.2、putAll

  1. public void putAll(Map<? extends K, ? extends V> m) {
  2. putMapEntries(m, true);
  3. }
  4.  
  5. /**
  6. * @param m 需要放入的Map
  7. * @param evict 在此处并无意义
  8. */
  9. final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
  10. //获取添加元素的数量
  11. int s = m.size();
  12. //添加的Map中有元素
  13. if (s > 0) {
  14. //table == null表达现在还没有被初始化
  15. if (table == null) { // pre-size
  16. //通过加载因子计算出大概需要初始化的空间
  17. float ft = ((float)s / loadFactor) + 1.0F;
  18. //检查这个需要的空间有没有大于最大容量MAXIMUM_CAPACITY
  19. int t = ((ft < (float)MAXIMUM_CAPACITY) ?
  20. (int)ft : MAXIMUM_CAPACITY);
  21. //如果大于了当前resize()的阈值,就要重新计算
  22. if (t > threshold)
  23. //这里会得到一个比t大的最小的2的整数次幂的值
  24. threshold = tableSizeFor(t);
  25. }
  26. else if (s > threshold)//在已经创建Map的情况下,s如果直接大于阈值,直接重构现在的Map
  27. resize();
  28. //将传入的Map的每个值都插入到现在的Map中
  29. for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
  30. K key = e.getKey();
  31. V value = e.getValue();
  32. putVal(hash(key), key, value, false, evict);
  33. }
  34. }
  35. }

4.3、remove

  1. public V remove(Object key) {
  2. Node<K,V> e;
  3. return (e = removeNode(hash(key), key, null, false, true)) == null ?
  4. null : e.value;
  5. }
  6.  
  7. /**
  8. * @param hash key的hash值
  9. * @param key key的值
  10. * @param value 传入匹配的value值,如果matchValue=false,直接忽略
  11. * @param matchValue 为true时,会去进一步匹配value
  12. * @param movable if false do not move other nodes while removing
  13. * @return the node, or null if none
  14. */
  15. final Node<K,V> removeNode(int hash, Object key, Object value,
  16. boolean matchValue, boolean movable) {
  17. Node<K,V>[] tab; Node<K,V> p; int n, index;
  18. //确定table已经被初始化,并且其中有元素,并且对应的 hash值有元素
  19. if ((tab = table) != null && (n = tab.length) > 0 &&
  20. (p = tab[index = (n - 1) & hash]) != null) {
  21. Node<K,V> node = null, e; K k; V v;
  22. //判断当前这个找到的元素是不是目标元素,如果是的话赋值给node
  23. if (p.hash == hash &&
  24. ((k = p.key) == key || (key != null && key.equals(k))))
  25. node = p;
  26. //不是的话,就从相同hash值的所有元素中去查找
  27. else if ((e = p.next) != null) {
  28. if (p instanceof TreeNode)
  29. //该列表已经转换成红黑树的情况
  30. node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
  31. else {
  32. //在列表中查找的情况
  33. do {
  34. if (e.hash == hash &&
  35. ((k = e.key) == key ||
  36. (key != null && key.equals(k)))) {
  37. node = e;
  38. break;
  39. }
  40. p = e;
  41. } while ((e = e.next) != null);
  42. }
  43. }
  44. //找到了目标节点
  45. if (node != null && (!matchValue || (v = node.value) == value ||
  46. (value != null && value.equals(v)))) {
  47. if (node instanceof TreeNode)//是红黑树节点的情况
  48. ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
  49. else if (node == p)//是一个链表开头元素的情况
  50. tab[index] = node.next;
  51. else
  52. p.next = node.next;//是一个链表中间元素的情况
  53. ++modCount;//结构改变,需要加一
  54. --size;
  55. afterNodeRemoval(node);
  56. return node;
  57. }
  58. }
  59. return null;
  60. }

4.4、clear

  1. public void clear() {
  2. Node<K,V>[] tab;
  3. modCount++;//对table结构修改加一
  4. if ((tab = table) != null && size > 0) {
  5. size = 0;
  6. //释放数组的每一个指针
  7. for (int i = 0; i < tab.length; ++i)
  8. tab[i] = null;
  9. }
  10. }

5、查询方法

  1. public V get(Object key) {
  2. Node<K,V> e;
  3. return (e = getNode(hash(key), key)) == null ? null : e.value;
  4. }
  5.  
  6. public boolean containsKey(Object key) {
  7. return getNode(hash(key), key) != null;
  8. }
  9.  
  10. /**
  11. * Implements Map.get and related methods
  12. *
  13. * @param hash hash for key
  14. * @param key the key
  15. * @return the node, or null if none
  16. */
  17. final Node<K,V> getNode(int hash, Object key) {
  18. Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
  19. //根据输入的hash值,可以直接计算出对应的下标(n - 1)& hash,缩小查询范围,如果存在结果,则必定在table的这个位置上
  20. if ((tab = table) != null && (n = tab.length) > 0 &&
  21. (first = tab[(n - 1) & hash]) != null) {
  22. //判断第一个存在的节点的key是否和查询的key相等。如果相等,直接返回该节点
  23. if (first.hash == hash && // always check first node
  24. ((k = first.key) == key || (key != null && key.equals(k))))
  25. return first;
  26. //遍历该链表/红黑树直到next为null
  27. if ((e = first.next) != null) {
  28. //当这个table节点上存储的是红黑树结构时,在根节点first上调用getTreeNode方法,在内部遍历红黑树节点,查看是否有匹配的TreeNode
  29. if (first instanceof TreeNode)
  30. return ((TreeNode<K,V>)first).getTreeNode(hash, key);
  31. //当这个table节点上存储的是链表结构时,方法同上
  32. do {
  33. if (e.hash == hash &&
  34. ((k = e.key) == key || (key != null && key.equals(k))))
  35. return e;
  36. } while ((e = e.next) != null);
  37. }
  38. }
  39. return null;
  40. }

6、静态方法

  1. /**
  2. * 对于这个函数,我想说两点,第一点是支持key==null,返回位置为0
  3. * 第二点是(h = key.hashCode()) ^ (h >>> 16),一个int32bit
  4. * 这里正好将高16位移到了低16位,然后产生的hash值即包含了高位信息又包含了低位信息
  5. * 还解决了地址空间不够引起的冲突问题
  6. */
  7. static final int hash(Object key) {
  8. int h;
  9. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  10. }
  11.  
  12. /**
  13. * 当x的类型为X,且X直接实现了Comparable接口(比较类型必须为X类本身)时,返回x的运行时类型;否则返回null。
  14. */
  15. static Class<?> comparableClassFor(Object x) {
  16. if (x instanceof Comparable) {// 判断是否实现了Comparable接口
  17. Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
  18. if ((c = x.getClass()) == String.class) // bypass checks
  19. return c; // 如果是String类型,直接返回String.class
  20. if ((ts = c.getGenericInterfaces()) != null) {// 判断是否有直接实现的接口
  21. for (int i = 0; i < ts.length; ++i) { // 遍历直接实现的接口
  22. if (((t = ts[i]) instanceof ParameterizedType) &&// 该接口实现了泛型
  23. ((p = (ParameterizedType)t).getRawType() ==// 获取接口不带参数部分的类型对象
  24. Comparable.class) &&// 该类型是Comparable
  25. (as = p.getActualTypeArguments()) != null && // 获取泛型参数数组
  26. as.length == 1 && as[0] == c) // 只有一个泛型参数,且该实现类型是该类型本身
  27. return c;
  28. }
  29. }
  30. }
  31. return null;
  32. }
  33.  
  34. /**
  35. * kc是k的类型,并且可以比较
  36. */
  37. @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
  38. static int compareComparables(Class<?> kc, Object k, Object x) {
  39. return (x == null || x.getClass() != kc ? 0 :
  40. ((Comparable)k).compareTo(x));
  41. }
  42.  
  43. /**
  44. * 返回不小于cap的最小的2的整次幂
  45. */
  46. static final int tableSizeFor(int cap) {
  47. int n = cap - 1;
  48. n |= n >>> 1;
  49. n |= n >>> 2;
  50. n |= n >>> 4;
  51. n |= n >>> 8;
  52. n |= n >>> 16;
  53. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  54. }

7、迭代器

  1. /**
  2. * 这里我感觉设计的还挺好的,这里是编写了一个Node节点的迭代器,这是一个抽象类,虽然是个抽象类,但是没哟抽象方法
  3. * 留下了next方法去给子类实现,因为子类只有nest返回的东西是不同的
  4. */
  5. abstract class HashIterator {
  6. Node<K,V> next; // next entry to return
  7. Node<K,V> current; // current entry
  8. int expectedModCount; // for fast-fail
  9. int index; // current slot
  10.  
  11. HashIterator() {
  12. expectedModCount = modCount;
  13. Node<K,V>[] t = table;
  14. current = next = null;
  15. index = 0;
  16. if (t != null && size > 0) { // advance to first entry
  17. do {} while (index < t.length && (next = t[index++]) == null);
  18. }
  19. }
  20.  
  21. public final boolean hasNext() {
  22. return next != null;
  23. }
  24.  
  25. final Node<K,V> nextNode() {
  26. Node<K,V>[] t;
  27. Node<K,V> e = next;
  28. //注意,这里在生成迭代器后,如果原来的图不是通过迭代器进行对图结构修改,那么就会报错
  29. if (modCount != expectedModCount)
  30. throw new ConcurrentModificationException();
  31. if (e == null)
  32. throw new NoSuchElementException();
  33. if ((next = (current = e).next) == null && (t = table) != null) {
  34. do {} while (index < t.length && (next = t[index++]) == null);
  35. }
  36. return e;
  37. }
  38.  
  39. public final void remove() {
  40. Node<K,V> p = current;
  41. if (p == null)
  42. throw new IllegalStateException();
  43. //在不是通过remove修改之前,通过其他方式是不允许修改图的结构的
  44. if (modCount != expectedModCount)
  45. throw new ConcurrentModificationException();
  46. current = null;
  47. K key = p.key;
  48. removeNode(hash(key), key, null, false, false);
  49. expectedModCount = modCount;
  50. }
  51. }
  52.  
  53. final class KeyIterator extends HashIterator
  54. implements Iterator<K> {
  55. public final K next() { return nextNode().key; }
  56. }
  57.  
  58. final class ValueIterator extends HashIterator
  59. implements Iterator<V> {
  60. public final V next() { return nextNode().value; }
  61. }
  62.  
  63. final class EntryIterator extends HashIterator
  64. implements Iterator<Map.Entry<K,V>> {
  65. public final Map.Entry<K,V> next() { return nextNode(); }
  66. }

8、并行遍历迭代器

  1. /**
  2. * jdk1.8发布后,对于并行处理的能力大大增强,Spliterator就是为了并行遍历元素而设计的一个迭代器,
  3. * jdk1.8中的集合框架中的数据结构都默认实现了spliterator
  4. *
  5. */
  6. static class HashMapSpliterator<K,V> {
  7. final HashMap<K,V> map;
  8. Node<K,V> current; // 当前节点
  9. int index; // 当前节点下标,advance/split会修改这个值
  10. int fence; // 最后一个节点的下标,注意这里不是元素个数,而是数组下标
  11. int est; // 预测还有多少个元素
  12. int expectedModCount; // 得到当前Map的结构修改次数
  13.  
  14. HashMapSpliterator(HashMap<K,V> m, int origin,
  15. int fence, int est,
  16. int expectedModCount) {
  17. this.map = m;
  18. this.index = origin;
  19. this.fence = fence;
  20. this.est = est;
  21. this.expectedModCount = expectedModCount;
  22. }
  23.  
  24. final int getFence() { // initialize fence and size on first use
  25. int hi;
  26. if ((hi = fence) < 0) {//当小于0的时候说明还没有初始化
  27. HashMap<K,V> m = map;
  28. est = m.size;
  29. expectedModCount = m.modCount;
  30. Node<K,V>[] tab = m.table;
  31. hi = fence = (tab == null) ? 0 : tab.length;//这里可以看到给出的是数组长度
  32. }
  33. return hi;
  34. }
  35.  
  36. public final long estimateSize() {
  37. getFence(); // 这里是防止还没有初始化的情况
  38. return (long) est;
  39. }
  40. }
  41.  
  42. static final class KeySpliterator<K,V>
  43. extends HashMapSpliterator<K,V>
  44. implements Spliterator<K> {
  45. KeySpliterator(HashMap<K,V> m, int origin, int fence, int est,
  46. int expectedModCount) {
  47. super(m, origin, fence, est, expectedModCount);
  48. }
  49.  
  50. /**
  51. * 这个方法相当于把未遍历的元素分成两半,然后将前一半生成一个KeySpliterator,当前这个KeySpliterator
  52. * 处理后一半数据
  53. */
  54. public KeySpliterator<K,V> trySplit() {
  55. int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
  56. return (lo >= mid || current != null) ? null :
  57. new KeySpliterator<>(map, lo, index = mid, est >>>= 1,//est >>>= 1的意思是est = est >>> 1
  58. expectedModCount);
  59. }
  60.  
  61. /**
  62. * 这个方法就是通过action方法处理还没有处理的元素
  63. */
  64. public void forEachRemaining(Consumer<? super K> action) {
  65. int i, hi, mc;
  66. if (action == null)
  67. throw new NullPointerException();
  68. HashMap<K,V> m = map;
  69. Node<K,V>[] tab = m.table;
  70. if ((hi = fence) < 0) {//如果还没有被初始化
  71. mc = expectedModCount = m.modCount;
  72. hi = fence = (tab == null) ? 0 : tab.length;
  73. }
  74. else
  75. mc = expectedModCount;//这个mc就是为了在KeySpliterator调用的过程中确认没有通过其他的方式改变Map的结构
  76. if (tab != null && tab.length >= hi &&
  77. (i = index) >= 0 && (i < (index = hi) || current != null)) {
  78. Node<K,V> p = current;
  79. current = null;
  80. do {
  81. if (p == null)//这里是处理数组
  82. p = tab[i++];
  83. else {//这里是处理链表
  84. action.accept(p.key);
  85. p = p.next;
  86. }
  87. } while (p != null || i < hi);
  88. if (m.modCount != mc)
  89. throw new ConcurrentModificationException();
  90. }
  91. }
  92.  
  93. //单个对元素执行给定的动作,如果有剩下元素未处理返回true,否则返回false
  94. public boolean tryAdvance(Consumer<? super K> action) {
  95. int hi;
  96. if (action == null)
  97. throw new NullPointerException();
  98. Node<K,V>[] tab = map.table;
  99. if (tab != null && tab.length >= (hi = getFence()) && index >= 0) {
  100. while (current != null || index < hi) {
  101. if (current == null)
  102. current = tab[index++];
  103. else {
  104. K k = current.key;
  105. current = current.next;
  106. action.accept(k);
  107. if (map.modCount != expectedModCount)
  108. throw new ConcurrentModificationException();
  109. return true;
  110. }
  111. }
  112. }
  113. return false;
  114. }
  115.  
  116. //返回当前对象有哪些特征值
  117. public int characteristics() {
  118. return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) |
  119. Spliterator.DISTINCT;
  120. }
  121. }
  122.  
  123. //下面的就不再赘述,只是用方法处理的对象不一样,其他的都和上面一样
  124. static final class ValueSpliterator<K,V>
  125. extends HashMapSpliterator<K,V>
  126. implements Spliterator<V> {
  127. ValueSpliterator(HashMap<K,V> m, int origin, int fence, int est,
  128. int expectedModCount) {
  129. super(m, origin, fence, est, expectedModCount);
  130. }
  131.  
  132. public ValueSpliterator<K,V> trySplit() {
  133. int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
  134. return (lo >= mid || current != null) ? null :
  135. new ValueSpliterator<>(map, lo, index = mid, est >>>= 1,
  136. expectedModCount);
  137. }
  138.  
  139. public void forEachRemaining(Consumer<? super V> action) {
  140. int i, hi, mc;
  141. if (action == null)
  142. throw new NullPointerException();
  143. HashMap<K,V> m = map;
  144. Node<K,V>[] tab = m.table;
  145. if ((hi = fence) < 0) {
  146. mc = expectedModCount = m.modCount;
  147. hi = fence = (tab == null) ? 0 : tab.length;
  148. }
  149. else
  150. mc = expectedModCount;
  151. if (tab != null && tab.length >= hi &&
  152. (i = index) >= 0 && (i < (index = hi) || current != null)) {
  153. Node<K,V> p = current;
  154. current = null;
  155. do {
  156. if (p == null)
  157. p = tab[i++];
  158. else {
  159. action.accept(p.value);
  160. p = p.next;
  161. }
  162. } while (p != null || i < hi);
  163. if (m.modCount != mc)
  164. throw new ConcurrentModificationException();
  165. }
  166. }
  167.  
  168. public boolean tryAdvance(Consumer<? super V> action) {
  169. int hi;
  170. if (action == null)
  171. throw new NullPointerException();
  172. Node<K,V>[] tab = map.table;
  173. if (tab != null && tab.length >= (hi = getFence()) && index >= 0) {
  174. while (current != null || index < hi) {
  175. if (current == null)
  176. current = tab[index++];
  177. else {
  178. V v = current.value;
  179. current = current.next;
  180. action.accept(v);
  181. if (map.modCount != expectedModCount)
  182. throw new ConcurrentModificationException();
  183. return true;
  184. }
  185. }
  186. }
  187. return false;
  188. }
  189.  
  190. public int characteristics() {
  191. return (fence < 0 || est == map.size ? Spliterator.SIZED : 0);
  192. }
  193. }
  194.  
  195. static final class EntrySpliterator<K,V>
  196. extends HashMapSpliterator<K,V>
  197. implements Spliterator<Map.Entry<K,V>> {
  198. EntrySpliterator(HashMap<K,V> m, int origin, int fence, int est,
  199. int expectedModCount) {
  200. super(m, origin, fence, est, expectedModCount);
  201. }
  202.  
  203. public EntrySpliterator<K,V> trySplit() {
  204. int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
  205. return (lo >= mid || current != null) ? null :
  206. new EntrySpliterator<>(map, lo, index = mid, est >>>= 1,
  207. expectedModCount);
  208. }
  209.  
  210. public void forEachRemaining(Consumer<? super Map.Entry<K,V>> action) {
  211. int i, hi, mc;
  212. if (action == null)
  213. throw new NullPointerException();
  214. HashMap<K,V> m = map;
  215. Node<K,V>[] tab = m.table;
  216. if ((hi = fence) < 0) {
  217. mc = expectedModCount = m.modCount;
  218. hi = fence = (tab == null) ? 0 : tab.length;
  219. }
  220. else
  221. mc = expectedModCount;
  222. if (tab != null && tab.length >= hi &&
  223. (i = index) >= 0 && (i < (index = hi) || current != null)) {
  224. Node<K,V> p = current;
  225. current = null;
  226. do {
  227. if (p == null)
  228. p = tab[i++];
  229. else {
  230. action.accept(p);
  231. p = p.next;
  232. }
  233. } while (p != null || i < hi);
  234. if (m.modCount != mc)
  235. throw new ConcurrentModificationException();
  236. }
  237. }
  238.  
  239. public boolean tryAdvance(Consumer<? super Map.Entry<K,V>> action) {
  240. int hi;
  241. if (action == null)
  242. throw new NullPointerException();
  243. Node<K,V>[] tab = map.table;
  244. if (tab != null && tab.length >= (hi = getFence()) && index >= 0) {
  245. while (current != null || index < hi) {
  246. if (current == null)
  247. current = tab[index++];
  248. else {
  249. Node<K,V> e = current;
  250. current = current.next;
  251. action.accept(e);
  252. if (map.modCount != expectedModCount)
  253. throw new ConcurrentModificationException();
  254. return true;
  255. }
  256. }
  257. }
  258. return false;
  259. }
  260.  
  261. public int characteristics() {
  262. return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) |
  263. Spliterator.DISTINCT;
  264. }
  265. }

9、JDK1.8新增的方法部分

  1. @Override
  2. public V getOrDefault(Object key, V defaultValue) {//如果没有key的情况下会返回defaultValue
  3. Node<K,V> e;
  4. return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
  5. }
  6.  
  7. @Override
  8. public V putIfAbsent(K key, V value) {//当存在key的时候,不会用value去覆盖
  9. return putVal(hash(key), key, value, true, true);
  10. }
  11.  
  12. @Override
  13. public boolean remove(Object key, Object value) {//当key和value都相同时,才去删除这个值
  14. return removeNode(hash(key), key, value, true, true) != null;
  15. }
  16.  
  17. @Override
  18. public boolean replace(K key, V oldValue, V newValue) {//当key和oldValue都相同时,用newValue去代替oldValue
  19. Node<K,V> e; V v;
  20. if ((e = getNode(hash(key), key)) != null &&
  21. ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
  22. e.value = newValue;
  23. afterNodeAccess(e);
  24. return true;
  25. }
  26. return false;
  27. }
  28.  
  29. @Override
  30. public V replace(K key, V value) {//这个和上面方法不同在于,不用确定旧的值,直接覆盖,并且返回oldValue
  31. Node<K,V> e;
  32. if ((e = getNode(hash(key), key)) != null) {
  33. V oldValue = e.value;
  34. e.value = value;
  35. afterNodeAccess(e);
  36. return oldValue;
  37. }
  38. return null;
  39. }
  40.  
  41. /**
  42. * 这个方法就类似于get方法,但是不同之处在于当key不存在时不时返回null,而是通过mappingFunction计算出一个值返回
  43. */
  44. @Override
  45. public V computeIfAbsent(K key,
  46. Function<? super K, ? extends V> mappingFunction) {
  47. if (mappingFunction == null)
  48. throw new NullPointerException();
  49. int hash = hash(key);
  50. Node<K,V>[] tab; Node<K,V> first; int n, i;
  51. int binCount = 0;
  52. TreeNode<K,V> t = null;
  53. Node<K,V> old = null;
  54. if (size > threshold || (tab = table) == null ||
  55. (n = tab.length) == 0)
  56. n = (tab = resize()).length;
  57. if ((first = tab[i = (n - 1) & hash]) != null) {
  58. if (first instanceof TreeNode)
  59. old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
  60. else {
  61. Node<K,V> e = first; K k;
  62. do {
  63. if (e.hash == hash &&
  64. ((k = e.key) == key || (key != null && key.equals(k)))) {
  65. old = e;
  66. break;
  67. }
  68. ++binCount;
  69. } while ((e = e.next) != null);
  70. }
  71. V oldValue;
  72. if (old != null && (oldValue = old.value) != null) {
  73. afterNodeAccess(old);
  74. return oldValue;
  75. }
  76. }
  77. V v = mappingFunction.apply(key);
  78. if (v == null) {
  79. return null;
  80. } else if (old != null) {
  81. old.value = v;
  82. afterNodeAccess(old);
  83. return v;
  84. }
  85. else if (t != null)
  86. t.putTreeVal(this, tab, hash, key, v);
  87. else {
  88. tab[i] = newNode(hash, key, v, first);
  89. if (binCount >= TREEIFY_THRESHOLD - 1)
  90. treeifyBin(tab, hash);
  91. }
  92. ++modCount;
  93. ++size;
  94. afterNodeInsertion(true);
  95. return v;
  96. }
  97.  
  98. //这个方法和尚一个方法不同的是,这个方法在key存在时,通过key和value算出一个新的value返回,如果不存在,返回null
  99. public V computeIfPresent(K key,
  100. BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
  101. if (remappingFunction == null)
  102. throw new NullPointerException();
  103. Node<K,V> e; V oldValue;
  104. int hash = hash(key);
  105. if ((e = getNode(hash, key)) != null &&
  106. (oldValue = e.value) != null) {
  107. V v = remappingFunction.apply(key, oldValue);
  108. if (v != null) {
  109. e.value = v;
  110. afterNodeAccess(e);
  111. return v;
  112. }
  113. else
  114. removeNode(hash, key, null, false, true);
  115. }
  116. return null;
  117. }
  118.  
  119. @Override
  120. //这个方法综合了上面的两个方法,都会带入remappingFunction进行运算新值,并且替换旧值
  121. public V compute(K key,
  122. BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
  123. if (remappingFunction == null)
  124. throw new NullPointerException();
  125. int hash = hash(key);
  126. Node<K,V>[] tab; Node<K,V> first; int n, i;
  127. int binCount = 0;
  128. TreeNode<K,V> t = null;
  129. Node<K,V> old = null;
  130. if (size > threshold || (tab = table) == null ||
  131. (n = tab.length) == 0)
  132. n = (tab = resize()).length;
  133. if ((first = tab[i = (n - 1) & hash]) != null) {
  134. if (first instanceof TreeNode)
  135. old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
  136. else {
  137. Node<K,V> e = first; K k;
  138. do {
  139. if (e.hash == hash &&
  140. ((k = e.key) == key || (key != null && key.equals(k)))) {
  141. old = e;
  142. break;
  143. }
  144. ++binCount;
  145. } while ((e = e.next) != null);
  146. }
  147. }
  148. V oldValue = (old == null) ? null : old.value;
  149. V v = remappingFunction.apply(key, oldValue);
  150. if (old != null) {
  151. if (v != null) {
  152. old.value = v;
  153. afterNodeAccess(old);
  154. }
  155. else
  156. removeNode(hash, key, null, false, true);
  157. }
  158. else if (v != null) {
  159. if (t != null)
  160. t.putTreeVal(this, tab, hash, key, v);
  161. else {
  162. tab[i] = newNode(hash, key, v, first);
  163. if (binCount >= TREEIFY_THRESHOLD - 1)
  164. treeifyBin(tab, hash);
  165. }
  166. ++modCount;
  167. ++size;
  168. afterNodeInsertion(true);
  169. }
  170. return v;
  171. }
  172.  
  173. @Override
  174. //这个函数就是将oldVAlue和value通过remappingFunction进行一下混合,然后代替旧值
  175. public V merge(K key, V value,
  176. BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
  177. if (value == null)
  178. throw new NullPointerException();
  179. if (remappingFunction == null)
  180. throw new NullPointerException();
  181. int hash = hash(key);
  182. Node<K,V>[] tab; Node<K,V> first; int n, i;
  183. int binCount = 0;
  184. TreeNode<K,V> t = null;
  185. Node<K,V> old = null;
  186. if (size > threshold || (tab = table) == null ||
  187. (n = tab.length) == 0)
  188. n = (tab = resize()).length;
  189. if ((first = tab[i = (n - 1) & hash]) != null) {
  190. if (first instanceof TreeNode)
  191. old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
  192. else {
  193. Node<K,V> e = first; K k;
  194. do {
  195. if (e.hash == hash &&
  196. ((k = e.key) == key || (key != null && key.equals(k)))) {
  197. old = e;
  198. break;
  199. }
  200. ++binCount;
  201. } while ((e = e.next) != null);
  202. }
  203. }
  204. if (old != null) {
  205. V v;
  206. if (old.value != null)
  207. v = remappingFunction.apply(old.value, value);
  208. else
  209. v = value;
  210. if (v != null) {
  211. old.value = v;
  212. afterNodeAccess(old);
  213. }
  214. else
  215. removeNode(hash, key, null, false, true);
  216. return v;
  217. }
  218. if (value != null) {
  219. if (t != null)
  220. t.putTreeVal(this, tab, hash, key, value);
  221. else {
  222. tab[i] = newNode(hash, key, value, first);
  223. if (binCount >= TREEIFY_THRESHOLD - 1)
  224. treeifyBin(tab, hash);
  225. }
  226. ++modCount;
  227. ++size;
  228. afterNodeInsertion(true);
  229. }
  230. return value;
  231. }
  232.  
  233. @Override
  234. //对于Map中的每一项,进行action运算
  235. public void forEach(BiConsumer<? super K, ? super V> action) {
  236. Node<K,V>[] tab;
  237. if (action == null)
  238. throw new NullPointerException();
  239. if (size > 0 && (tab = table) != null) {
  240. int mc = modCount;
  241. for (int i = 0; i < tab.length; ++i) {
  242. for (Node<K,V> e = tab[i]; e != null; e = e.next)
  243. action.accept(e.key, e.value);
  244. }
  245. if (modCount != mc)
  246. throw new ConcurrentModificationException();
  247. }
  248. }
  249.  
  250. @Override
  251. //Map中所有的值,都会被function(key,value)替换为新值
  252. public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
  253. Node<K,V>[] tab;
  254. if (function == null)
  255. throw new NullPointerException();
  256. if (size > 0 && (tab = table) != null) {
  257. int mc = modCount;
  258. for (int i = 0; i < tab.length; ++i) {
  259. for (Node<K,V> e = tab[i]; e != null; e = e.next) {
  260. e.value = function.apply(e.key, e.value);
  261. }
  262. }
  263. if (modCount != mc)
  264. throw new ConcurrentModificationException();
  265. }
  266. }

三、HashMap的非线程安全(见博客https://www.cnblogs.com/softidea/p/7261111.html

随笔3 HashMap<K,V>的更多相关文章

  1. Java集合源码分析(七)HashMap<K, V>

    一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap  ...

  2. Java源码 HashMap<K,V>

    HashMap类 https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html public class HashMap<K, ...

  3. 随笔4 Dictionary<K,V>

    本来说是想介绍一下Hashtable的,但是发现HashMap和Hashtable最开始的不同就是在于HashMap继承了AbstractMap,而Hashtable继承了Dictionary< ...

  4. 随笔2 AbstractMap<K,V>

    上一篇写了Map接口的源码分析,这一篇写一下Map接口的一个实现类AbstractMap,从名字就可以看出这是一个抽象类,提供了Map接口的骨架实现,为我们实现Map接口的时候提供了很大的便利.在这里 ...

  5. java:警告:[unchecked] 对作为普通类型 java.util.HashMap 的成员的put(K,V) 的调用未经检查

    java:警告:[unchecked] 对作为普通类型 java.util.HashMap 的成员的put(K,V) 的调用未经检查 一.问题:学习HashMap时候,我做了这样一个程序: impor ...

  6. 统计字符串中每种字符出现的评率(HashMap中getOrDefault(K, V)方法的使用)

    为了统计字符串中每种字符出现的频率,使用HashMap这种数据结构.其中,字符作为Key,出现的频率作为Value. 基本算法为: 1. 将字符串分成字符数组 2. (1)如果HashMap中的Key ...

  7. 随笔1 interface Map<K,V>

    第一次写笔记就从map开始吧,如上图所示,绿色的是interface,黄色的是abstract class,蓝色的是class,可以看出所有和图相关的接口,抽象类和类的起源都是interface ma ...

  8. 关于jsp利用EL和struts2标签来遍历ValueStack的东东 ------> List<Map<K,V>> 以及 Map<K,<List<xxx>>> 的结构遍历

    //第一种结构Map<K,<List<xxx>>> <body> <% //显示map<String,List<Object>& ...

  9. [编程语言][java][java se]java泛型中? T K V E含义(学习)

    ? 表示不确定的java类型,类型是未知的. T  表示java类型. K V 分别代表java键值中的Key Value. E 代表Element,特性是枚举. 1.意思     jdk中的K,V, ...

随机推荐

  1. [BZOJ1964]hull 三维凸包:计算几何

    分析 发现自己并不会计算几何. 所以先引用一下这位dalao的博客. 二维平面四个点求凸包面积->任选三个点面积之和/2 三维平面五个点求凸包体积->任选四个点体积之和/2 二维平面三个点 ...

  2. [CSP-S模拟测试]:string(线段树)

    题目描述 给定一个由小写字母组成的字符串$s$. 有$m$次操作,每次操作给定$3$个参数$l,r,x$. 如果$x=1$,将$s[l]~s[r]$升序排序: 如果$x=0$,将$s[l]~s[r]$ ...

  3. JavaScript modularity with RequireJS (from spaghetti code to ravioli code)

    http://netmvc.blogspot.com/2012/11/javascript-modularity-with-requirejs.html Today I would like to d ...

  4. iOS即时通讯之CocoaAsyncSocket源码解析四

    原文 前言: 本文为CocoaAsyncSocket源码系列中第二篇:Read篇,将重点涉及该框架是如何利用缓冲区对数据进行读取.以及各种情况下的数据包处理,其中还包括普通的.和基于TLS的不同读取操 ...

  5. Oracle-优化SQL语句

    建议不使用(*)来代替所有列名 用truncate代替delete 在SQL*Plus环境中直接使用truncate table即可:要在PL/SQL中使用,如: 创建一个存储过程,实现使用trunc ...

  6. curl发json

    linux 模拟post请求 curl -X POST \ -H "Content-Type: application/json" \ -H "token:GXJP1cl ...

  7. 小白学数据分析--聚类分析理论之K-means理论篇

    小白学数据分析--聚类分析理论之K-means理论篇 聚类分析是一类广泛被应用的分析方法,其算法众多,目前像SAS.Splus.SPSS.SPSS Modeler等分析工具均以支持聚类分析,但是如何使 ...

  8. 在VS Code中使用Jupyter Notebook

    一.安装配置 1.在扩展商店中安装官方的Python扩展包 2.系统已经安装了Jupyter Notebook 由于系统上的Python环境是用Anaconda安装的,已经有Jupyter Noteb ...

  9. Java-集合第五篇Map集合

    1.什么是Map集合. Map用于保存具有映射关系的数据.key和value都可以是任意引用类型,但key不允许重复,即同一个Map的任何两个key通过equals方法比较总是返回false. 从Ja ...

  10. [2019杭电多校第七场][hdu6646]A + B = C(hash)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6646 题意为求a*10x+b*10y=c*10z满足公式的任意一组解x,y,z. 因为c有可能会由a+ ...