文章目录

    • 概论
      • Hasmap 的继承关系
      • hashmap 的原理
        • 解决Hash冲突的方法
          • 开放定址法
          • 再哈希法
          • 链地址法
          • 建立公共溢出区
        • hashmap 最终的形态
      • Hashmap 的返回值
    • HashMap 的关键内部元素
      • 存储容器 table;
      • size 元素个数
      • Node
      • TreeNode
      • modCount
      • 阈值 threshold
      • 实际存储元素个数 size
    • debug 源码 插入元素的过程
      • 调用put()方法
      • 调用 putval()
        • 调用hash() 方法
      • 进入 putval()
        • 判断数组是否为空,需不需要调用resize 方法
          • table 为空首次初始化
          • table 不为空 不是首次初始化
        • 判断当前位置是否有元素
          • 1 没有 直接放入当前位置
          • 2 有 将当前节点记做p
            • 判断直接覆盖(判断是否是同一个key)
            • 判断插入红黑树
            • 判断插入链表
            • 最后 返回oldValue 完成新值替换
        • 最后 判断是否需要扩容 返回null 值
      • 单独讲解resize 方法
        • 第一部分初始化新数组
        • 第二部分数据迁移
        • 第三部分 返回新的数组
      • 单独讲解树化treeifyBin方法
    • 获取元素的过程
    • 总结
      • resize 方法总结
        • resize(扩容) 的上限
        • resize 方法只有三种情况下调用
        • resize 的返回值
      • key 的判断
      • 树化
      • for 循环遍历链表而不是while
      • 你觉得Hashmap 还有什么可以改进的地方吗,欢迎讨论
    • 番外篇
      • hash 方法的实现方式
      • 链表法导致的链表过深问题为什么不用二叉查找树代替
      • jdk8中对HashMap做了哪些改变
      • Hashmap 的容量大小为什么要求是2n

概论

  • HashMap 是无论在工作还是面试中都非常常见常考的数据结构。比如 Leetcode 第一题 Two Sum 的某种变种的最优解就是需要用到 HashMap 的,高频考题 LRU Cache 是需要用到 LinkedHashMap 的。HashMap 用起来很简单,所以今天我们来从源码的角度梳理一下Hashmap
  • 随着JDK(Java Developmet Kit)版本的更新,JDK1.8对HashMap底层的实现进行了优化,例如引入红黑树的数据结构和扩容的优化等。
  • HashMap:它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。
  • HashMap最多只允许一条记录的键为null,允许多条记录的值为null。
  • HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap

Hasmap 的继承关系

image-20201126201445602

hashmap 的原理

  1. 对于 HashMap 中的每个 key,首先通过 hash function 计算出一个 hash 值,这个hash值经过取模运算就代表了在 buckets 里的编号 buckets 实际上是用数组来实现的,所以把这个hash值模上数组的长度得到它在数组的 index,就这样把它放在了数组里。
  2. 如果果不同的元素算出了相同的哈希值,那么这就是哈希碰撞,即多个 key 对应了同一个桶。这个时候就是解决hash冲突的时候了,展示真正技术的时候到了。
  3. 随着插入的元素越来越多,发生碰撞的概率就越大,某个桶中的链表就会越来越长,直到达到一个阈值,HashMap就受不了了,为了提升性能,会将超过阈值的链表转换形态,转换成红黑树的结构,这个阈值是 8 。也就是单个桶内的链表节点数大于 8 ,就会将链表有可能变身为红黑树。

解决Hash冲突的方法

开放定址法

这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式:

Hi=(H(key)+di)% m i=1,2,…,n

其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有三种 线性探测再散列,二次探测再散列,伪随机探测再散列

再哈希法

这种方法是同时构造多个不同的哈希函数

Hi=RH1(key) i=1,2,…,k

当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间

链地址法

这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。

链地址法适用于经常进行插入和删除的情况。

建立公共溢出区

这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。

hashmap 最终的形态

一顿操作猛如虎,搞得原本还是很单纯的hashmap 变得这么复杂,难倒了无数英雄好汉,由于链表长度过程,会导致查询变慢,所以链表慢慢最后演化出了红黑树的形态

HashMap主体上就是一个数组结构,每一个索引位置英文叫做一个 bin,我们这里先管它叫做桶,比如你定义一个长度为 8 的 HashMap,那就可以说这是一个由 8 个桶组成的数组。

当我们像数组中插入数据的时候,大多数时候存的都是一个一个 Node 类型的元素,Node 是 HashMap中定义的静态内部类

image-20201127171502527

Hashmap 的返回值

很多人以为Hashmap 是没有返回值的,或者也没有关注过Hashmap 的返回值,其实在你调用Hashmap的put(key,value) 方法 的时候,它会将当前key 已经有的值返回,然后把你的新值放到对应key 的位置上

public class JavaHashMap {public static void main(String[] args) {HashMap<String, String> map = new HashMap<String, String>();String oldValue = map.put("java大数据", "数据仓库");System.out.println(oldValue);oldValue = map.put("java大数据", "实时数仓");System.out.println(oldValue);}
}

运行结果如下,因为一开始是没有值的,所以返回null,后面有值了,put 的时候就返回了旧的值

image-20201126202457415

这里有一个问题需要注意一下,因为Map的Key,Value 的类型都是引用类型,所以在没有值的情况下一定返回的是null,而不是0 等初始值。

HashMap 的关键内部元素

存储容器 table;

因为HashMap内部是用一个数组来保存内容的, 它的定义 如下

transient Node<K,V>[] table

如果哈希桶数组很大,即使较差的Hash算法也会比较分散,如果哈希桶数组数组很小,即使好的Hash算法也会出现较多碰撞,所以就需要在空间成本和时间成本之间权衡,其实就是在根据实际情况确定哈希桶数组的大小,并在此基础上设计好的hash算法减少Hash碰撞。那么通过什么方式来控制map使得Hash碰撞的概率又小,哈希桶数组(Node[] table)占用空间又少呢?答案就是好的Hash算法和扩容机制。

在HashMap中,哈希桶数组table的长度length大小必须为2的n次方(一定是合数),这是一种非常规的设计,常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小于合数

size 元素个数

size这个字段其实很好理解,就是HashMap中实际存在的键值对数量。注意和table的长度length、容纳最大键值对数量threshold的区别

Node

 static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}
}
  • Node是HashMap的一个静态内部类。实现了Map.Entry接口,本质是就是一个映射(键值对),主要包括 hash、key、value 和 next 的属性。
  • 我们使用 put 方法像其中加键值对的时候,就会转换成 Node 类型。其实就是newNode(hash, key, value, null);

TreeNode

当桶内链表到达 8 的时候,会将链表转换成红黑树,就是 TreeNode类型,它也是 HashMap中定义的静态内部类。

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent;  // red-black tree linksTreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev;    // needed to unlink next upon deletionboolean red;TreeNode(int hash, K key, V val, Node<K,V> next) {super(hash, key, val, next);
}

说起TreeNode ,就不得不说其他三个相关参数 TREEIFY_THRESHOLD=8 和 UNTREEIFY_THRESHOLD=6 以及 MIN_TREEIFY_CAPACITY=64

TREEIFY_THRESHOLD=8 指的是链表的长度大于8 的时候进行树化, UNTREEIFY_THRESHOLD=6 说的是当元素被删除链表的长度小于6 的时候进行退化,由红黑树退化成链表

MIN_TREEIFY_CAPACITY=64 意思是数组中元素的个数必须大于等于64之后才能进行树化

modCount

modCount字段主要用来记录HashMap内部结构发生变化的次数,主要用于迭代的快速失败。强调一点,内部结构发生变化指的是结构发生变化,例如put新键值对,但是某个key对应的value值被覆盖不属于结构变化。

阈值 threshold

它是加在因子乘以初始值大小,后续扩容的时候和数组大小一样,2倍进行扩容

threshold = (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY)

实际存储元素个数 size

size 默认大小是0 ,它指的是数组存储的元素个数,而不是整个hashmap 的元素个数,对于下面这张图就是3 而不是11

transient int size;

image-20201127171502527

debug 源码 插入元素的过程

public class JavaHashMap {public static void main(String[] args) {HashMap<String, String> map = new HashMap<String, String>();String oldValue = map.put("java大数据", "数据仓库");}
}

调用put()方法

这个方法没什么好说的,是hashmap 提供给用户调用的方法,很简单

调用 putval()

Put 方法实际上调用的实 putval() 方法

image-20201126204454960

可以看出在进入putval() 方法之间,需要借助hash 方法先计算出key 的hash 值,然后将key 的hash值和key同时传入

调用hash() 方法

image-20201126204634472

  • 这个key的hashCode()方法得到其hashCode 值(该方法适用于每个Java对象),然后再通过Hash算法的后两步运算(高位运算和取模运算,下文有介绍)来定位该键值对的存储位置,有时两个key会定位到相同的位置,表示发生了Hash碰撞。当然Hash算法计算结果越分散均匀,Hash碰撞的概率就越小,map的存取效率就会越高。
  • 在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。

进入 putval()

进入putval 方法之后,整体数据流程如下,下面会详细介绍每一步

image-20201126204925231

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;// 判断是否需要初始化数组if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)// 当前位置为空,则直接插入,同时意味着不走else 最后直接返回nulltab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}// 可以看出只有当前key 的位置为空的时候才判断时候需要reszie 已经返回 null 其他情况下都走了else 的环节++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}

判断数组是否为空,需不需要调用resize 方法

第一次调用,这里table 是null,所以会走resize 方法

image-20201126205708504

resize 方法本身也是比较复杂的,因为这里是第一次调用,所以这里进行了简化

    final Node<K,V>[] resize() {Node<K,V>[] oldTab = table;int oldCap = (oldTab == null) ? 0 : oldTab.length;int oldThr = threshold;int newCap, newThr = 0;if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;else {               //  首次初始化 zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}threshold = newThr;@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;if (oldTab != null) {// 因为 oldTab 为null 所以不会进来这个if 判断,所以将这里的代码省略了}return newTab;}
table 为空首次初始化

如果是的话,初始化数组大小和threashold

newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

初始化之后,将新创建的数组返回,在返回之前完成了对变量table 的赋值

image-20201126211551514

table 不为空 不是首次初始化

如果不是的话就用当前数组的信息初始化新数组的大小

image-20201126211919741

最后完成table 的初始化,返回table ,这里其实还有数据迁移,但是为了保证文章的结构,所以将resize 方法的详细讲解单独提了出来

table = newTab;

判断当前位置是否有元素

1 没有 直接放入当前位置

image-20201126212145456

2 有 将当前节点记做p

当前节点记做p 然后进入else 循环

else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}
判断直接覆盖(判断是否是同一个key)

判断新的key 和老的key 是否相同,这里同时要求了hash 值和 实际的值是相等的情况下然后直接完成了e=p 的赋值,其实也就是完成了替换,因为key 是相同的。

如果不是同一个key 的话这里就要将当前元素插入链表或者红黑树了,因为是不同的key 了

判断插入红黑树

如果当前元素是一个 TreeNode 则将当前元素放入红黑树,然后
image-20201126220247642

判断插入链表
  • 如果不是同一key并且当前元素类型不是TreeNode 则将当前元素插入链表(因为key对应的位置已经有元素了,其实可以认为是链表的头元素)

  • 可以看出采用的是尾插法,循环过程中当下一个节点是null的时候则进行插入,插入完毕之后判断是否需要树化

    JDK 1.7 之前使用头插法、JDK 1.8 使用尾插法

  • 其实主要是根据(e=p.next)==null 进行判断进入哪一个if ,因为每个 if 都含有break 语句,所以只能进入一个 然后就退出循环了

    image-20201126220940075

       if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}
    

    1、这段代码也是上图中的第一个if这段代码的意思就是在遍历链表的过程中,一直都没有遇到和待插入key 相同的key(第二个if) 然后当前要插入的元素插入到了链表的尾部(当前if 语句)

    第二个if 的意思 如果有发生key冲突则停止 后续这个节点会被相同的key覆盖

    2、插入之后判断判断局部变量binCount 时候大于7(TREEIFY_THRESHOLD-1),这里需要注意的是binCount 是从0开始的,所以实际的意思是判断链表的长度在插入新元素之前是否大于等于8,如果是的话则进行树化

    3、并且这个时候变量e 的值是null ,因为是插入到链表的尾部的,所以这个时候key 是没有对应的oldValue 的,所以e是null 在最后面的判断返回中,也返回的是null

    4、关于树化,首先这是发生在插入链表的时刻,并且是插入链表尾部的时候,因为判断过程是在第一个if 中,为了保证文章的结构关于树化放在下面讲

    if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;
    // 这个赋值很有意思,它完成了你可以使用for 循环完成链表遍历的核心功能    
    p = e;
    

    1、这一段代码的意思是在遍历的过程中(e=p.next)!=null 的的时候,也就是在循环链表的过程中,判断是否有和当前key 相等的key,相等的话e 就是要覆盖的元素,如果不相等的话就继续循环,知道找到这样的e 或者是将链表循环结束,然后将元素插入到链表的尾部(第一个if)

    2、因为是当key 存在的时候则跳出循环,所以链表的长度没有发生变化,所以这里没有判断是否需要树化

最后 返回oldValue 完成新值替换
if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;
}

这个时候e 就指向原来p 的位置了,因为e=p, 然后用新的value 覆盖掉了oldValue 完成了插入,最后将 oldValue 返回。

最后 判断是否需要扩容 返回null 值

其实能走到这一步,是那就说明放入元素的时候,key 对应的位置是没有元素的,所以相当于数组中添加了一个新的元素,所以这里有判断是否需要resize 和返回空值。

 ++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;

单独讲解resize 方法

首选需要记住resize 方法是会返回扩容后的数组的

第一部分初始化新数组

这一部分不论是不是首次调用resize 方法,都会有的,但是数据迁移部分在首次调用的时候是没有的

Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
// 判断是oldCap 是否大于0 因为可能是首次resize,如果不是的话 oldCap
if (oldCap > 0) {// 到达扩容上限if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}// 这里是正常的扩容else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;
//第一次调用resize 方法,然后使用默认值进行初始化
else {// zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);
}
// 创建新的数组,下面
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
  1. 如果数组的大小 大于等于MAXIMUM_CAPACITY之后,则 threshold = Integer.MAX_VALUE; 然后不扩容直接返回当前数组,所以可以看出hashmap 的扩容上限就是MAXIMUM_CAPACITY(230
  2. 如果数组的大小 在扩容之后小于MAXIMUM_CAPACITY 并且原始大小大于DEFAULT_INITIAL_CAPACITY(16) 则进行扩容(DEFAULT_INITIAL_CAPACITY 的大小限制是为了防止该方法的调用是在树化方法里调用的,这个时候数组大大小可能小于DEFAULT_INITIAL_CAPACITY)
  3. 新的数组创建好之后,就可以根据老的数组是否有值决定是否进行数据迁移

第二部分数据迁移

oldTab 也就是老的数组不为空的时候进行迁移

 if (oldTab != null) {// 遍历oldTable,拿到每一个元素准备放入大新的数组中去for (int j = 0; j < oldCap; ++j) {Node<K,V> e;if ((e = oldTab[j]) != null) {oldTab[j] = null;// 当前元素只是单个元素,不是链表if (e.next == null)// 重新计算每个元素在数组中的位置newTab[e.hash & (newCap - 1)] = e;// 判断当前元素是否是树   else if (e instanceof TreeNode)((TreeNode<K,V>)e).split(this, newTab, j, oldCap);// 当前元素是链表,则遍历链表    else { // preserve orderNode<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}
  • 判断当前元素的next 是否为空,是则直接放入,其实就是只有一个元素,说明这是一个最正常的节点,不是桶内链表,也不是红黑树,这样的节点会重新计算索引位置,然后插入。
  • 是的话,判断是不是TreeNode,不是的话则直接遍历链表进行拷贝,保证链表的顺序不变。
  • 是的话则调用 TreeNode.split() 方法,如果是一颗红黑树,则使用 split方法处理,原理就是将红黑树拆分成两个 TreeNode 链表,然后判断每个链表的长度是否小于等于 6,如果是就将 TreeNode 转换成桶内链表,否则再转换成红黑树。
  • 完成数据的拷贝,返回新的数组

第三部分 返回新的数组

 return newTab;

只要没有到达扩容上限,这一部分是肯定会走的,至于走不走数据迁移,需要潘丹是不是首次resize()

单独讲解树化treeifyBin方法

 for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}
  • 首先判断是符满足链表长度大于8(binCount 是否大于等于7) ,需要注意的是插入到链表的尾部导致链表的长度发生了变化的情况下,才判断是否需要树化
  • 然后进入treeifyBin 方法中,进入树化方法之后又判断了,Hashmap 的大小是否大于64,如果不是的话,只是调用了resize 方法,让数组扩容,而不是树化
final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)resize();else if ((e = tab[index = (n - 1) & hash]) != null) {TreeNode<K,V> hd = null, tl = null;do {TreeNode<K,V> p = replacementTreeNode(e, null);if (tl == null)hd = p;else {p.prev = tl;tl.next = p;}tl = p;} while ((e = e.next) != null);if ((tab[index] = hd) != null)hd.treeify(tab);}
}

获取元素的过程

总结

resize 方法总结

resize(扩容) 的上限

resize 不是无限的,当到达resize 的上限,也就是230 之后,不再扩容

resize 方法只有三种情况下调用

  • 第一种 是在首次插入元素的时候完成数组的初始化
  • 第二种 是在元素插入完成后判断是否需要数组扩容,如果是的话则调用
  • 第三种 是在元素插入链表尾部之后,进入树化方法之后,如果不树化则进行resize

resize 的返回值

  • 第一种情况下 返回老的数组也就是没有resize 因为已经达到resize 的上限了
  • 第二种情况下 返回一个空的数组 也就是第一次调用resize方法
  • 第三章情况下 返回一个扩容后的数组 完成了数据迁移后的数组

key 的判断

  • 第一次判断是当前位置有元素的时候,如果两个key 相等则准备覆盖值
  • 第二次判断是遍历链表的时候,决定能否覆盖链表中间key 相等的值而不是链表的尾部

树化

  • 树化是发生在元素插入链表之后,并且这里是插入到链表的尾部导致链表的长度发生了变化的情况下(也就是走的for循环里的第一个if 语句),而不是替换了链表里面的某一元素(也就是走的for循环里的第二个if 语句)

    image-20201127114314435

    final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)resize();else if ((e = tab[index = (n - 1) & hash]) != null) {TreeNode<K,V> hd = null, tl = null;do {TreeNode<K,V> p = replacementTreeNode(e, null);if (tl == null)hd = p;else {p.prev = tl;tl.next = p;}tl = p;} while ((e = e.next) != null);if ((tab[index] = hd) != null)hd.treeify(tab);}
    }
    

    其实这代码上面有一段注释的,这里也帖一下,在table 太小的情况下,使用resize 否则替换指的位置链表上的全部Nodes(其实就是替换成红黑树)

     /*** Replaces all linked nodes in bin at index for given hash unless* table is too small, in which case resizes instead.*/
    

    其实这里有一个隐含的意义,就是数组不大的时候,希望通过resize 的方法降低hash 冲突的概率,从而避免链表过长降低查询时间,但是当数组比较大的时候reszie 成本太高,则通过将链表转化成红黑树来降低查询时间

for 循环遍历链表而不是while

这是源代码里面的一段,上面也解释过了,这里使用for 循环遍历链表,利用for 循环的index 进行计数,这里进行了删减

for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {doSomething();break;p = e;
}

你觉得Hashmap 还有什么可以改进的地方吗,欢迎讨论

虽然java 源代码的山很高,如果你想跨越,至少你得有登山的勇气,这里我给出自己的一点点愚见,希望各位不吝指教

番外篇

这里如果你不感兴趣可以不阅读😙

hash 方法的实现方式

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

JDK 1.8 中,是通过 hashCode() 的高 16 位异或低 16 位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度,功效和质量来考虑的,减少系统的开销,也不会造成因为高位没有参与下标的计算,从而引起的碰撞

为什么要用异或运算符? 保证了对象的 hashCode 的 32 位值只要有一位发生改变,整个 hash() 返回值就会改变。尽可能的减少碰撞。

链表法导致的链表过深问题为什么不用二叉查找树代替

之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。

而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢

jdk8中对HashMap做了哪些改变

在java 1.8中,如果链表的长度超过了8,那么链表将转换为红黑树。(桶的数量必须大于64,小于64的时候只会扩容)

发生hash碰撞时,java 1.7 会在链表的头部插入,而java 1.8会在链表的尾部插入

在java 1.8中,Entry被Node替代(换了一个马甲)

Hashmap 的容量大小为什么要求是2n

这里首选要说明一个前提,那就是元素在数组中的位置的计算方式是 tab[i = (n - 1) & hash] 也就是通过对数组大小求模得到的,因为我们知道hash 的计算方式是 hashCode() 的高 16 位异或低 16 位实现的,32 位值只要有一位发生改变,整个 hash() 返回值就会改变,也就是说我们的hash 值发生冲突的概率是比较小的,也就是说hash 值是比较随机的

所以更多的冲突是发生在取模的时候,所以这个时候只要保证了我们的取模运算 (n - 1) & hash,尽量能保证hash 值的特性也就是随机性。因为我们知道与运算的特点是,两位同时为“1”,结果才为“1”,否则为0

所以这个时候我们只要 (n - 1) 让的二进制表示都是一串1,例如"011111" 就可以了,因为安位与1 结果是不变的,也就是可以延续hash 值的散列性

其实到这里就差不多了,然后我们看2n 的表示特点,然后就知道为什么要就hashmap 的大小是 2n了, 2n次方的二进制表示大家肯定都很清楚,2的6次方,就是从右向左 6 个 0,然后第 7 位是 1

image-20201127184124095
其实这下我们就知道为什么了,因为只有数组的长度是2的次方了,n-1 的二进制才能尽可能多的是1

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. OAuth2.0简介

    一、引言&#xff1a; 我经常网购和外卖&#xff0c;每天都有快递员来送货。我必须找到一个办法&#xff0c;让快递员通过门禁系统&#xff0c;进入小区。 如果我把自己的密码&#xff0c;告诉快递员&#xff0c;他就拥有了与我同样的权限&#xff0c;这样好像不太合适。万一…...

    2024/5/1 3:00:28
  2. 多任务学习Topic推荐-AMiner

    AMiner平台&#xff08;https://www.aminer.cn&#xff09;由清华大学计算机系研发&#xff0c;拥有我国完全自主知识产权。平台包含了超过2.3亿学术论文/专利和1.36亿学者的科技图谱&#xff0c;提供学者评价、专家发现、智能指派、学术地图等科技情报专业化服务。系统2006年上…...

    2024/4/23 23:20:15
  3. 习题练习题-第三题

    类的封装 任务描述&#xff1a;编写一个学生类&#xff0c;并根据要求对学生类进行封装 练习目标 1.了解为什么要对类进行封装; 2. 掌握如何实现类的封装; 需求分析 对类进行封装&#xff0c;防止外界对类中的成员变量随意访问。 为了掌握类的封装&#xff0c;本练习将使用priv…...

    2024/4/8 20:29:47
  4. OpenStack基础环境安装配置,这一篇就够了!

    本文是OpenStack基础环境安装配置&#xff0c;在接下来的实验中需要安装配置服务组件&#xff0c;所以我们需要建立三台虚拟机&#xff0c;来分别安装OpenStack的一些组件。 1、环境说明 主机名IP地址controller(控制节点)192.168.23.101compute&#xff08;计算节点&#xf…...

    2024/3/23 15:06:49
  5. NC127—最长公共子串

    题意 给定两个字符串str1和str2,输出两个字符串的最长公共子串&#xff0c;如果最长公共子串为空&#xff0c;输出-1。 题解 思路与最长公共子序列类似&#xff0c;dp[i][j]表示以str1[i]和str2[j]为最后一个元素的最长公共子串的长度&#xff0c;只不过状态转移方程稍微变化…...

    2024/4/25 7:50:53
  6. Electron之初出茅庐——搭建环境并运行第一个程序

    最近在学习node.js的过程中&#xff0c;突然发现了electron这个宝藏开源框架。在学习过程中收获颇多&#xff0c;特此记录&#xff0c;方便知识整理。 参考文献&#xff1a;技术胖 目录一、Electron的定义二、Electron的运行原理2.1、不得不说到JavaScript的运行原理2.2、Elect…...

    2024/3/23 15:06:46
  7. flume到底会丢数据吗?

    先给出答案&#xff1a; 需要结合具体使用的source、channel和sink来分析&#xff0c;具体结果可看本文最后一节。 Flume事务 一提到事务&#xff0c;我们首先就想到的是MySQL中的事务&#xff0c;事务就是将一批操作做成原子性的&#xff0c;即这一批要么都成功&#xff0c;…...

    2024/3/23 15:06:46
  8. 常用面试答疑

    jmter参数化意义是什么? 你常用的参数化方式有? (至少说出2种) 可以答 我只会一个jmter里的设置线程数, 简单的脚本录制 设置线程数. 比如1000的线程数,1s 在平时测试工作中你是如何保障测试的质量的? 从需求阶段开始,会先理清楚产品的大致功能以及功能模块的联系,进而在…...

    2024/3/23 15:06:46
  9. 集成学习-Bagging集成学习算法随机森林(Random Forest)

    随机森林算法属性 随机森林顾名思义&#xff0c;是用随机的方式建立一个森林&#xff0c;森林里面有很多的决策树组成&#xff0c;随机森林的每一棵决策树之间是没有关联的。在得到森林之后&#xff0c;当有一个新的输入样本进入的时候&#xff0c;就让森林中的每一棵决策树分…...

    2024/4/27 4:25:41
  10. SeekBar的Accessibility

    SeekBar的Accessibility 最近在做公司项目的Accessibility&#xff0c;刚好做到了关于如果自定义seekbar的contentDescription。其实逻辑就是"偷龙转凤"的思想. 意思就是真正执行的Accessibility的是另外一个view&#xff0c;比如说你可以用一个Textview. 主要实现…...

    2024/3/23 15:20:58
  11. Python - 装机系列25 ubuntu 持久化ssh私钥

    说明 自己搭建了一个私有的git服务&#xff0c;但是算网内主机每次连接还是要临时添加秘钥。希望改为永久连接。 内容 原来的内容&#xff1a;临时的连接 #!/bin/bash# 将私钥考入&#xff0c;m1自己不能增加&#xff08;因为公钥也在这上面&#xff0c;认证无意义&#xf…...

    2024/3/23 15:20:57
  12. vscode 插件REST Client的使用(感谢wanandroid开放api)

    简介 REST Client 堪比是 Postman的替代品。REST Client 是一个 VS Code 扩展插件&#xff0c;可以通过写脚本的形式发送 HTTP 请求并直接在 VS Code 上查看响应结果。这样不仅能看到http的测试结果&#xff0c;测试的脚本文件还能得以保留。 参考&#xff1a; REST Client …...

    2024/4/6 17:55:58
  13. 青藤 #10232 Couple number

    题目描述 任何一个整数N都能表示成另外两个整数a和b的平方差吗&#xff1f;如果能&#xff0c;那么这个数N就叫做Couple number。你的工作就是判断一个数N是不是Couple number。 输入格式 仅一行&#xff0c;两个长整型范围内的整数n1和n2&#xff0c;之间用1个空格隔开。 输出…...

    2024/4/29 1:30:52
  14. IDEA使用小技巧_ 设置 项目包名分级、层级显示、IDEA常用的快捷键

    目录 设置 项目包名分级、层级显示、IDEA最常用的快捷键 一、 设置 项目包名分级、层级显示 在使用IDEA写Java项目的时候&#xff0c;需要对 业务代码 分级放入不同的包下&#xff0c;使业务代码之间的关系更加清晰 但是 特别是对于大的项目&#xff0c;搭建环境时需要创建 …...

    2024/3/23 15:20:55
  15. Mysql 启动报错ERROR 2002 (HY000): Can‘t connect to local MySQL server through socket ‘/var/lib/mysql/mys

    Mysql 启动报错ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/var/lib/mysql/mysql.sock’ (111) 问题解决 首先&#xff0c;查看日志mysqld.log&#xff08;我的配置在/var/log目录下&#xff09; 发现报错如下&#xff1a; 2020-11-27T08:5…...

    2024/4/24 20:50:49
  16. 问题:graphviz.backend.ExecutableNotFound: failed to execute [‘dot‘, ‘-Tpdf‘, ‘-O‘, ‘Source.gv‘], make

    graphviz.backend.ExecutableNotFound: failed to execute [‘dot’, ‘-Tpdf’, ‘-O’, ‘Source.gv’], make sure the Graphviz executables are on your systems’ PA !此处Python interpreter 要和 下图中Python interpreter保持一致的路径[在...

    2024/4/24 20:50:41
  17. js数组操作-数组扁平化

    js数组操作-数组扁平化 数组的扁平化&#xff0c;就是将一个嵌套多层的数组(嵌套可以是任何层数)转换为只有一层(或者指定层)的数组。这个操作在实际开发过程还是有一定的需求场景的。在es6中已经提供了实现这个功能的方法&#xff0c;本文讨论了模拟实现的方案。 关键字&#…...

    2024/4/24 20:50:38
  18. 开源IDS suricata源码分析(worker模式工作线程初始化及处理流程)

    这里写自定义目录标题开源IDS suricata源码分析&#xff08;worker模式工作线程初始化及处理流程&#xff09;初始化及处理流程接口调用开源IDS suricata源码分析&#xff08;worker模式工作线程初始化及处理流程&#xff09; 本文主要分析suricata在worker工作模式下&#xf…...

    2024/4/27 2:25:28
  19. ...

    2024/4/28 16:29:46
  20. 算法--图(遍历)

    深搜一般模板&#xff08;无权&#xff09; #include <iostream> #include <cstring> #include <vector> using namespace std;vector<int> G[10000]; bool vis[10000]; void dpf(int v)//一般模板 {vis[v]true;for(int i0;i<G[v].size();i){if(vi…...

    2024/4/21 16:23:03

最新文章

  1. K. 子串翻转回文串

    给一个串 s  s1s2... sn&#xff0c;你可以选定其一个非空子串&#xff0c;然后将该子串翻转。具体来说&#xff0c;若选定的子串区间为 [l, r]&#xff08;1 ≤ l ≤ r ≤ n&#xff09;&#xff0c;则翻转后该串变为 s1s2... sl - 1srsr - 1... slsr  1... sn…...

    2024/5/6 20:49:08
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/6 9:38:23
  3. ensp设置远程登录路由器

    [AR1]aaa #aaa服务专门存储账号密码 [AR1-aaa]local-user 用户名 privilege level 15(1~15) password cipher 123456 #预设账号密码 [AR1-aaa]local-user 用户名 service-type telnet [AR1]user-interface vty 0 4 #虚拟接口调用&#xff0c;0~4口&#xff0c;可同时…...

    2024/5/4 2:28:17
  4. ROS2高效学习第十章 -- ros2 高级组件之大型项目中的 launch 其二

    ros2 高级组件之大型项目中的 launch 1 前言和资料2 正文2.1 启动 turtlesim&#xff0c;生成一个 turtle &#xff0c;设置背景色2.2 使用 event handler 重写上节的样例2.3 turtle_tf_mimic_rviz_launch 样例 3 总结 1 前言和资料 早在ROS2高效学习第四章 – ros2 topic 编程…...

    2024/5/5 21:03:09
  5. 【嵌入式开发 Linux 常用命令系列 4.3 -- git add 不 add untracked file】

    请阅读【嵌入式开发学习必备专栏 】 文章目录 git add 不add untracked file git add 不add untracked file 如果你想要Git在执行git add .时不添加未跟踪的文件&#xff08;untracked files&#xff09;&#xff0c;你可以使用以下命令&#xff1a; git add -u这个命令只会加…...

    2024/5/5 8:53:25
  6. 416. 分割等和子集问题(动态规划)

    题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义&#xff1a;dp[i][j]表示当背包容量为j&#xff0c;用前i个物品是否正好可以将背包填满&#xff…...

    2024/5/6 18:23:10
  7. 【Java】ExcelWriter自适应宽度工具类(支持中文)

    工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...

    2024/5/6 18:40:38
  8. Spring cloud负载均衡@LoadBalanced LoadBalancerClient

    LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon&#xff0c;直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件&#xff0c;我们讨论Spring负载均衡以Spring Cloud2020之后版本为主&#xff0c;学习Spring Cloud LoadBalance&#xff0c;暂不讨论Ribbon…...

    2024/5/5 19:59:54
  9. TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案

    一、背景需求分析 在工业产业园、化工园或生产制造园区中&#xff0c;周界防范意义重大&#xff0c;对园区的安全起到重要的作用。常规的安防方式是采用人员巡查&#xff0c;人力投入成本大而且效率低。周界一旦被破坏或入侵&#xff0c;会影响园区人员和资产安全&#xff0c;…...

    2024/5/6 7:24:07
  10. VB.net WebBrowser网页元素抓取分析方法

    在用WebBrowser编程实现网页操作自动化时&#xff0c;常要分析网页Html&#xff0c;例如网页在加载数据时&#xff0c;常会显示“系统处理中&#xff0c;请稍候..”&#xff0c;我们需要在数据加载完成后才能继续下一步操作&#xff0c;如何抓取这个信息的网页html元素变化&…...

    2024/5/5 15:25:47
  11. 【Objective-C】Objective-C汇总

    方法定义 参考&#xff1a;https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...

    2024/5/6 6:01:13
  12. 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】

    &#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格…...

    2024/5/6 7:24:06
  13. 【ES6.0】- 扩展运算符(...)

    【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数&#xff0…...

    2024/5/6 1:08:53
  14. 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?

    文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕&#xff0c;各大品牌纷纷晒出优异的成绩单&#xff0c;摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称&#xff0c;在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁&#xff0c;多个平台数据都表现出极度异常…...

    2024/5/6 20:04:22
  15. Go语言常用命令详解(二)

    文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令&#xff0c;这些命令可以帮助您在Go开发中进行编译、测试、运行和…...

    2024/5/6 0:27:44
  16. 用欧拉路径判断图同构推出reverse合法性:1116T4

    http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b&#xff0c;我们在 a i a_i ai​ 和 a i 1 a_{i1} ai1​ 之间连边&#xff0c; b b b 同理&#xff0c;则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然&#xff0…...

    2024/5/6 7:24:04
  17. 【NGINX--1】基础知识

    1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息&#xff0c;并安装一些有助于配置官方 NGINX 软件包仓库的软件包&#xff1a; apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...

    2024/5/6 7:24:04
  18. Hive默认分割符、存储格式与数据压缩

    目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限&#xff08;ROW FORMAT&#xff09;配置标准HQL为&#xff1a; ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...

    2024/5/6 19:38:16
  19. 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法

    文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中&#xff0c;传感器和控制器产生大量周…...

    2024/5/6 7:24:03
  20. --max-old-space-size=8192报错

    vue项目运行时&#xff0c;如果经常运行慢&#xff0c;崩溃停止服务&#xff0c;报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中&#xff0c;通过JavaScript使用内存时只能使用部分内存&#xff08;64位系统&…...

    2024/5/5 17:03:52
  21. 基于深度学习的恶意软件检测

    恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞&#xff0c;例如可以被劫持的合法软件&#xff08;例如浏览器或 Web 应用程序插件&#xff09;中的错误。 恶意软件渗透可能会造成灾难性的后果&#xff0c;包括数据被盗、勒索或网…...

    2024/5/5 21:10:50
  22. JS原型对象prototype

    让我简单的为大家介绍一下原型对象prototype吧&#xff01; 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定&#xff0c;每一个构造函数都有一个 prototype 属性&#xff0c;指向另一个对象&#xff0c;所以我们也称为原型对象…...

    2024/5/6 7:24:02
  23. C++中只能有一个实例的单例类

    C中只能有一个实例的单例类 前面讨论的 President 类很不错&#xff0c;但存在一个缺陷&#xff1a;无法禁止通过实例化多个对象来创建多名总统&#xff1a; President One, Two, Three; 由于复制构造函数是私有的&#xff0c;其中每个对象都是不可复制的&#xff0c;但您的目…...

    2024/5/6 7:24:01
  24. python django 小程序图书借阅源码

    开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…...

    2024/5/5 17:03:21
  25. 电子学会C/C++编程等级考试2022年03月(一级)真题解析

    C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...

    2024/5/6 16:50:57
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57