万字源码解析!彻底搞懂 HashMap【一】:概念辨析与构造方法源码解析

HashMap 的底层原理和扩容机制一直都是面试的时候经常被问到的问题,同时也是集合源码中最难阅读的一部分😢,之前更新的 ArrayList 源码阅读收获了很多朋友的喜欢,也给了我很多自信;本次我准备完成一个关于 HashMap 源码阅读和解析的专栏,共分为四部分内容:

  1. 概念辨析、属性和构造方法的源码阅读
  2. putVal() 和 resize() 方法的源码分析
  3. 详细讲解红黑树
  4. HashMap 中与树有关的方法

通过阅读这些内容相信大家一定可以彻底的搞懂 HashMap,如果喜欢文章的话不要忘记订阅专栏和关注。😊

前言:
本篇是专栏的第一部分:概念辨析、属性和构造方法的源码阅读,大家重点需要关注属性和常量的含义,还有对几种构造方法都初始化了哪些部分有了解和记忆,为下一篇更难的源码阅读打好基础。

1.基础理解

1.1 数组、链表、散列表

1)数组

数组是一种线性数据结构,由一组连续的内存单元组成,每个元素都有固定的索引位置。

数组的优点是可以通过索引快速访问元素,时间复杂度为O(1)。

但是因为数组的长度是指定的,所以进行插入和扩容的操作非常麻烦,需要将原本数组中的内容转移到新数组中才能完成扩容的操作。

2)链表

链表也是一种线性数据结构,由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。

链表的优点是插入和删除操作可以在O(1)时间内完成,无需移动其他元素。

缺点是访问元素时需要遍历整个链表,时间复杂度为O(n),并且相比数组占用更多的内存空间。

3)散列表

与上面的两种数据结构不同的是,散列表是用来存储 键值对(key-value),在实现方式上,散列表像是数组 + 链表的一个结合,也就是基本的结构是一个数组,但数组中存储的元素是一个链表。

提到数组,最显著的特点就是 索引,散列表是通过 哈希算法 将键(key)映射成一个 长度固定 的二进制数,然后通过 路由算法 将其映射到数组的索引中;在查找和修改的时候可以通过相同的算法快速的找到对应的数组的索引。

散列表的缺点是可能会发生哈希冲突,即经过映射之后映射到了相同的位置,需要解决冲突的方法,比如链地址法,也就是上面提到的,在数组中存储一个链表,如果产生哈希冲突就将数据拼接到数组索引位置的链表中。

4)哈希算法

在这里插入图片描述

Hash 的中文释义为散列,一般音译为哈希。它的基本原理就是把 任意长度 的输入,通过哈希算法转变为固定长度的输出。

映射的规则成为哈希算法,原始数据通过映射之后得到的二进制串就是哈希值

哈希有如下的几个特点:

  1. 无法通过哈希值反推出原始的数据,且数据一点微小的变化都会得到完全不同的哈希值,相同的数据会得到完全相同的哈希值,这两个特点使得哈希算法在安全方面也有广泛的应用,比如 https 的数字证书的签名认证等。
  2. 哈希算法的执行效率很高效,长文本也能很快的算出对应的哈希值
  3. 由于是将任意长度是输出映射为固定长度的输出,也就是一种 无限 => 有限 的一种对应关系,就会导致所谓的哈希冲突,即两个不同的数据输入映射为了相同的哈希值,如何处理哈希冲突是使用哈希函数的时候必须要解决的问题。

1.2 HashMap 中使用的数据结构和算法

在这里插入图片描述

1)数据结构

Java 中的 HashMap 就是基于 散列表 实现的,其底层是通过数组+链表(Java8 引入了红黑树)来存储数据的。

1)数组HashMap 内部维护了一个数组(table),这个数组的每个元素称为桶(bucket)。数组的长度是固定的(有扩容机制),如果不指定的话默认为 16。

2)链表或红黑树:在 Java 8 中,HashMap 使用了数组+链表+红黑树的结构,以应对哈希冲突。每个桶可以存储一个链表或红黑树。当哈希冲突发生时(即多个键映射到了同一个桶),新的键值对会被插入到对应位置的链表或红黑树中,具体来说,当链表中的元素个数超过一定阈值(默认为8)时,链表会转换为红黑树,以提高查找、插入、删除操作的效率,因为红黑树的平均时间复杂度为 O(log n) 而链表则是 O(n)

3)在 Java 8 之前的版本中,HashMap 只使用链表来解决冲突,而在 Java 8 中引入了红黑树的优化,使得 HashMap 在处理大量数据时性能更加稳定。

2021最好懂的红黑树教程red black tree Tutorial - YouTube

红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,它在每个节点上都附加了一个额外的表示节点颜色的属性,可以是红色或黑色。红黑树满足以下性质:

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点是黑色的。
  3. 每个叶子节点(NIL节点,即空节点)都是黑色的。
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的。
  5. 从任一节点到其每个叶子节点的所有路径都包含相同数量的黑色节点(黑高相同)。

因为红黑树理解和讲解都比较复杂,之后单独出一篇博客来讲,这里优先关注 HashMap 的底层结构。

2)路由算法

通过 hashCode() 得到的整数是肯定无法直接作为下标的(Java 中获得的是一个32位的二进制数),此时就需要再经过一次映射来将哈希值对应为数组下标范围内的一个数字。

HashMap 将键映射为索引是通过了如下的步骤,比如说通过 map.put("key", "value") 插入一个键值对

  1. 首先通过指定的哈希算法,Java 中使用 hashCode() 调用本地方法(一般映射的是内存),或者调用重写的 hashCode() 来得到 “key” 对应的 Hash 值。

  2. 通过一个扰动函数,使得 Hash 值的分布更加分散,进一步降低哈希冲突的可能

  3. 通过路由算法计算出对应的索引,比如如下的方法:

    (table.length - 1) & node.hash
    

    是通过将 数组的长度 - 1 和得到的哈希值进行 按位与运算(&)得到的,在 HashMap 中,table.length 一定是 2 的幂次,其二进制的特点就是开头为 1 后面为全 0(比如 0001 0000),减一之后转换为二进制就会表现为全 1(0000 1111),此时进行位与运算,得到的其实就是哈希值的后几位,下方展示一个运算案例:
    在这里插入图片描述

3)HashMap 中使用的节点 Node

先来看一下源码:

	/*** Basic hash bin node, used for most entries.  (See below for* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)*/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;}

上面展示的就是 HashMap 中的静态内部类 Node,上方注释中解释的比较明确,这就是其中的 bin node(节点),这个静态内部类中有四个属性,分别为:

  • int hash:存储映射的哈希值,需要注意的是这个值并不是直接将通过 hashCode() 得到的值,而是经过扰动函数处理后的值
  • K key:本节点存储的键
  • V value:本节点存储的值
  • Node<K, V> next:存储下一个节点的位置

最后提供了一个有参的构造方法。

2.源码阅读第一部分-属性与构造方法

2.1 常量解读

下面就开始正式阅读 HashMap 的源码,我们先从类中的常量开始,源码中的常量通常与类的机制息息相关,理解常量对源码的阅读很有帮助

	/*** The default initial capacity - MUST be a power of two.*/static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

第一个常量是 DEFAULT_INITIAL_CAPACITY,这个常量的含义是:如果在初始化的时候没有指定初始容量(initialCapacity)的话,系统默认给的初始容量;上方的注释中提到,容量(table.length),必须是 2 的 n 次方;然后下面注释中 aka 的意思是 “also known as” 就是这个值也可以被称作为 16。


	/*** The maximum capacity, used if a higher value is implicitly specified* by either of the constructors with arguments.* MUST be a power of two <= 1<<30.*/static final int MAXIMUM_CAPACITY = 1 << 30;

这个常量指定的是数组(table)的最大长度,为 2 的 30 次方,是为了对数组的长度做一个限制,在后面的代码中经常作为判断条件使用。


	/*** The load factor used when none specified in constructor.*/static final float DEFAULT_LOAD_FACTOR = 0.75f;

这里指定的是默认的负载因子的值,为 0.75,负载因子(Load Factor)是指哈希表中已存储元素数量哈希表容量的比例。在哈希表中,负载因子用于衡量哈希表的填充程度,即已存储元素占哈希表容量的比例。通常情况下,负载因子是一个介于 0 到 1 之间的数值,在哈希表中,当负载因子达到一定阈值时,通常会触发哈希表的扩容操作,以保持哈希表的性能。常见的默认负载因子值为 0.75,这是一个经验值,可以在平衡内存占用和性能之间做出权衡。


之后的三个常量都是和树化(红黑树)相关的,这里放在一起讲:

/*** The bin count threshold for using a tree rather than list for a* bin.  Bins are converted to trees when adding an element to a* bin with at least this many nodes. The value must be greater* than 2 and should be at least 8 to mesh with assumptions in* tree removal about conversion back to plain bins upon* shrinkage.*/static final int TREEIFY_THRESHOLD = 8;/*** The bin count threshold for untreeifying a (split) bin during a* resize operation. Should be less than TREEIFY_THRESHOLD, and at* most 6 to mesh with shrinkage detection under removal.*/static final int UNTREEIFY_THRESHOLD = 6;/*** The smallest table capacity for which bins may be treeified.* (Otherwise the table is resized if too many nodes in a bin.)* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts* between resizing and treeification thresholds.*/static final int MIN_TREEIFY_CAPACITY = 64;

第一个和第三个常量配合来决定一个链表树化的时机;数组中的某一个链表中树化的条件是:

数组的长度达到 MIN_TREEIFY_CAPACITY(64)且数组中的一个链表的长度达到 TREEIFY_THRESHOLD(8)

此时就对这个链表进行树化操作,将其转化为红黑树来优化查询速度。

第二个常量(UNTREEIFY_THRESHOLD)是树降级为链表的阈值,当树中的元素因为删除而达到阈值 UNTREEIFY_THRESHOLD(6)的时候,会将树降级为链表。


总结一下,HashMap 底层是散列表的结构(数组+链表(红黑树)),常量的定义也围绕着这些展开,规定了数组的长度、链表树化的阈值等。

2.2 属性解读

然后将源码翻到 Fields 部分,来看一下具体的属性,HashMap 中维护了一个数组(table),所以对于它来说会有两个长度,一个是 table.length 就是散列表(哈希表)中数组的长度,还有一个长度为 size,指的是存储元素的个数。

	/*** The table, initialized on first use, and resized as* necessary. When allocated, length is always a power of two.* (We also tolerate length zero in some operations to allow* bootstrapping mechanics that are currently not needed.)*/transient Node<K,V>[] table;

首先就是 table,这就是存储数据的 Node 数组。


	/*** Holds cached entrySet(). Note that AbstractMap fields are used* for keySet() and values().*/transient Set<Map.Entry<K,V>> entrySet;

这个属性是用来缓存 HashMap 中的 entrySet() 方法返回的键值对集合。通过缓存 entrySet 属性,可以在多次需要访问 HashMap 中所有键值对的情况下提高性能,避免重复生成键值对集合,从而节省时间和资源。此属性与基本源码关系不大,这里先不做深入解释。


 	/*** The number of key-value mappings contained in this map.*/transient int size;

当前 Map 中存储的 元素 数量


	/*** The number of times this HashMap has been structurally modified* Structural modifications are those that change the number of mappings in* the HashMap or otherwise modify its internal structure (e.g.,* rehash).  This field is used to make iterators on Collection-views of* the HashMap fail-fast.  (See ConcurrentModificationException).*/transient int modCount;

modCount 字段很多集合类中都有,它存储的是该集合 结构 被修改的次数,比如插入和删除的操作;修改 value 这种不会修改结构的方式不会引发 modCount 的自增,这在后面的源码中有体现。


	/*** The next size value at which to resize (capacity * load factor).** @serial*/// (The javadoc description is true upon serialization.// Additionally, if the table array has not been allocated, this// field holds the initial array capacity, or zero signifying// DEFAULT_INITIAL_CAPACITY.)int threshold;

在 HashMap 中,threshold 属性存储了在何时需要对哈希表进行扩容操作的阈值。当 HashMap 中的 元素数量 达到 threshold 时,就会触发哈希表的扩容操作,以保持哈希表的性能;threshold 的值通常是容量(capacity)乘以负载因子(load factor)得到的结果。当 HashMap 中的元素数量达到 threshold 时,就会进行扩容操作。


	/*** The load factor for the hash table.** @serial*/final float loadFactor;

这里就是负载因子属性,一般我们使用默认的,也就是 0.75f

2.3 构造方法解读

HashMap 提供了四种构造方法,分别是:

  • 指定了初始容量和负载因子的构造方法
  • 指定了初始容量的构造方法
  • 默认的无参构造方法
  • 传入 Map 的构造方法

下面来按照这个顺序分别说明这些方法:

	/*** Constructs an empty <tt>HashMap</tt> with the specified initial* capacity and load factor.** @param  initialCapacity the initial capacity* @param  loadFactor      the load factor* @throws IllegalArgumentException if the initial capacity is negative*         or the load factor is nonpositive*/public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);this.loadFactor = loadFactor;this.threshold = tableSizeFor(initialCapacity);}

上面的代码中,首先通过三个 if 语句对传入的参数进行检验:

  • 初始容量是否大于等于零
  • 初始容量是否大于table的最大容量(2的30次方)
  • 传入的负载因子的准确性,其中 Float.isNaN(loadFactor 用于判断一个浮点数是否为 NaN(Not a Number))。

最后指定负载因子为传入的负载因子,然后通过 tableSizeFor() 方法来计算 threshold(扩容阈值),这个方法的作用是获取一个 2 的幂次方的值,这个值是在大于等于传入的 cap 中最小的那个值;比如传入的是 3,最终得到的结果就是 4,传入的是 5 获得的则是 8;这个值会被赋值到 threshold 中。

通过这个构造方法,给如下的属性进行了赋值:

  • loadFactor:负载因子(传入的负载因子)
  • threshold:扩容阈值(经过处理后的 initialCapacity)

读到这里可以发现数组(table)还没有被初始化,table 的初始化被放在第一次插入元素的时候进行,这种延迟初始化的可以节省内存。


    /*** Constructs an empty <tt>HashMap</tt> with the specified initial* capacity and the default load factor (0.75).** @param  initialCapacity the initial capacity.* @throws IllegalArgumentException if the initial capacity is negative.*/public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}

然后就是指定了容量的方法,其实就是调用了上面的方法,传入的参数是指定的容量和默认的负载因子(0.75)

这个方法调用了上面的方法,所以经过这个构造方法,也是初始化了两个属性:

  • loadFactor:负载因子
  • threshold:扩容阈值

	/*** Constructs an empty <tt>HashMap</tt> with the default initial capacity* (16) and the default load factor (0.75).*/public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}

默认的构造方法中只指定了负载因子,初始容量为默认值,也就是 0。


	/*** Constructs a new <tt>HashMap</tt> with the same mappings as the* specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with* default load factor (0.75) and an initial capacity sufficient to* hold the mappings in the specified <tt>Map</tt>.** @param   m the map whose mappings are to be placed in this map* @throws  NullPointerException if the specified map is null*/public HashMap(Map<? extends K, ? extends V> m) {this.loadFactor = DEFAULT_LOAD_FACTOR;putMapEntries(m, false);}

最后就是传入一个 Map 集合的方式,首先制定了默认的负载因子,然后调用了 putMapEntries() 方法,这个方法接收两个参数,分别是 Map 集合和是否执行驱逐操作(evict)。

	/*** Implements Map.putAll and Map constructor.** @param m the map* @param evict false when initially constructing this map, else* true (relayed to method afterNodeInsertion).*/final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {int s = m.size();if (s > 0) {if (table == null) { // pre-sizefloat ft = ((float)s / loadFactor) + 1.0F;int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);if (t > threshold)threshold = tableSizeFor(t);}else if (s > threshold)resize();for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {K key = e.getKey();V value = e.getValue();putVal(hash(key), key, value, false, evict);}}}

本方法比较复杂,需要先知道这个方法在哪里被使用到了,通过注释可以看出其主要使用的位置有两个:Map.putAll and Map constructor 就是 putAll 方法和构造方法,在这两个方法中调用本方法来将传入的集合加入到 Map 中。

putMapEntries() 方法首先将 s 设定为传入集合的长度,如果集合中有元素就要进行插入的操作:

首先来看第一个第一组内容:

    if (table == null) { // pre-sizefloat ft = ((float)s / loadFactor) + 1.0F;int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);if (t > threshold)threshold = tableSizeFor(t);}

执行这一段方法的条件是 s > 0 && table == null,也就是传入的集合长度大于零,且 table 并未被初始化的情况,此时就可以判断出本方法是在构造方法中被调用的

因为是在构造方法中被调用的,通过上面的构造方法可以得知,构造方法中仅制定了负载因子的大小,其他比如说 table 和 threshold(拓展阈值)等都是默认值。

首先计算值 ft,它代表着将这些元素放入 HashMap 之后,如果要满足负载因子的规范(0.75f),需要的数组容量为多少

然后去判断这个容量的合理性:是否大于最大容量(MAXIMUM_CAPACITY),如果大于这个值的话,就取这个值,反之就保留 ft 的 int 部分,将其复制到 t 中,此时 t 中存储的就是插入完这些元素后数组应该的长度;运用这个长度来计算拓展阈值,tableSizeFor() 方法上面提到过了,最终将这个值赋值给 threshold(扩展阈值),扩展阈值会在后面指定 table 的容量时使用

然后来看第二段内容,当发现 table 不是 null,那就是在 putAll() 方法中调用的本方法了,此时会先去判断 s 是否大于 threshold ,如果大于的话,此时就会去调用 resize() 方法,resize() 方法是当元素数量达到扩展阈值(threshold)的时候进行的拓展操作,会将数组拓展为原来的二倍长度,并指定新的扩展阈值,这个方法在后面会详细讲解。

else if (s > threshold)resize();

这一段的设计其实非常的严谨,首先来思考一个问题,什么时候会进行扩容操作?

是当前元素的数量大于threshold(扩容阈值)的时候进行扩容的操作。

那此时你可能会想,那不是 size + s > threshold 的时候就该进行扩容操作嘛?

其实这个时候是不严谨的,因为 s 中的 key 和原本 HashMap 中的 key 可能会出现重合现象,此时执行的是修改的逻辑,这样会导致插入完成后,最终的 size 其实是小于等于 size + threshold;而当 s 已经大于 threshold 则不同,此时插入完这个集合后 size 的大小一定会大于扩展阈值,此时就 一定 要进行扩容的操作,这就体现了源码的严谨性,只有当 s 大于 threshold 的时候才在这里进行扩容操作。

当完成上面两个部分之后,就执行增强 for 循环将内容插入到本 HashMap 中

for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {K key = e.getKey();V value = e.getValue();putVal(hash(key), key, value, false, evict);
}

putVal()resize 是两个非常关键且复杂的方法,这个放到后面详细讲解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/301197.html

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

相关文章

python练习三

模式A num int(input("请输入模式A的层数&#xff1a;")) for i in range(1, num 1):# 画数字for j in range(1, i 1):print(str(j) "\t", end"")print() 模式B num int(input("请输入模式B的层数&#xff1a;")) for i in ran…

九州金榜|孩子叛逆的原因是什么?

孩子随着年龄增长都会出现叛逆心理&#xff0c;很多家长不知道孩子为什么会出现叛逆心理&#xff0c;也不知道如何去引导孩子&#xff0c;下面九州金榜家庭教育就带大家了解一下孩子出现叛逆的原因。 一、心理需求增加 孩子对新事物的探索以及追求会随着人际交往扩大而增加&am…

2024年MathorCup妈妈杯数学建模思路D题思路解析+参考成品

1 赛题思路 (赛题出来以后第一时间在群内分享&#xff0c;点击下方群名片即可加群) 2 比赛日期和时间 报名截止时间&#xff1a;2024年4月11日&#xff08;周四&#xff09;12:00 比赛开始时间&#xff1a;2024年4月12日&#xff08;周五&#xff09;8:00 比赛结束时间&…

买卖股票的最佳时机IV

题目链接 买卖股票的最佳时机 IV 题目描述 注意点 1 < k < 1001 < prices.length < 10000 < prices[i] < 1000不能同时参与多笔交易&#xff08;必须在再次购买前出售掉之前的股票&#xff09;最多可以完成 k 笔交易 解答思路 本题与买卖股票的最佳时机…

单例模式--理解

单例模式 单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时&#xff0c;为了防止频繁地创建对象使得内存飙升&#xff0c;单例模式可以让程序仅在内存中创建一个对象&#xff0c;让所有需要调用的地方都共享这一单例对象。 单…

Vue - 你知道Vue组件之间是如何进行数据传递的吗

难度级别:中级及以上 提问概率:85% 这道题还可以理解为Vue组件之间的数据是如何进行共享的,也可以理解为组件之间是如何通信的,很多人叫法不同,但都是说的同一个意思。我们知道,在Vue单页面应用项目中,所有的组件都是被嵌套在App.vue内…

『VUE』14. Style绑定(详细图文注释)

目录 行内css动态样式对象引入数组引入代码演示总结 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 大体上和前面的class绑定是一致的,只是class换成了style. 请注意,实际开发中,我们一般建议用class,因为style的权重太高了,用sty…

能不能换DB吗?--抽象工厂模式

1.1 就不能不换DB吗&#xff1f; 都是换数据库惹的祸。 "我们团队前段时间用.net的C#来开发好一个项目&#xff0c;是给一家企业做的电子商务网站&#xff0c;是用SQL Server作为数据库的&#xff0c;应该说上线后除了开始有些小问题&#xff0c;基本都还可以。而后&#…

【2024】Rancher的安装与介绍

———————————————————————————— 记录一下rancher的学习与使用过程 本部分内容包括rancher的介绍、特点、与k8s关系和部署等内容 ———————————————————————————— Rancher是什么&#xff1f; 简单来说&#xff0c;Ranc…

ChatGPT 在做什么,为什么有效?

原文&#xff1a;What Is ChatGPT Doing … and Why Does It Work? 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 序言 这本简短的书试图从第一原理解释 ChatGPT 是如何工作的。在某种程度上&#xff0c;这是关于技术的故事。但它也是关于科学的故事。以及关于哲学…

MySQL的内连接和外连接

内连接 在之前的MySQL的复合查询时&#xff0c;我们能够通过给两个表做笛卡尔积查询和where子句限定条件来查询想查询的数据&#xff0c;不过MySQL还提供了内连接用来给两个表做笛卡尔积&#xff0c;对比之前的复合查询笛卡尔积来说语法更加简洁。 语法&#xff1a;select 字段…

基于51单片机的温度、烟雾、火焰检测设计

基于51单片机的火灾检测设计 &#xff08;仿真&#xff0b;程序&#xff0b;原理图设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.使用MQ-2烟雾采集&#xff0c;使用ADC0832将传感器输出的模拟信号转化为数字信号&#xff0c;再传给单片机。 2.使用DS18B20采集温度。…

Linux中shell脚本的学习第一天,编写脚本的规范,脚本注释、变量,特殊变量的使用等,包含面试题

4月7日没参加体侧的我自学shell的第一天 Shebang 计算机程序中&#xff0c;shebang指的是出现在文本文件的第一行前两个字符 #&#xff01; 1)以#!/bin/sh 开头的文件&#xff0c;程序在执行的时候会调用/bin/sh, 也就是bash解释器 2)以#!/usr/bin/python 开头的文件&#…

动态代理

动态代理 动态代理和静态代理角色一致。 代理类是动态生成的,不是我们直接写好的。 动态代理分为俩大类:基于接口的动态代理、基于类的动态代理 基于接口:JDK动态代理(以下示例就是这个) 基于类:cglib java字节码实现:javasist JDK动态代理 InvocationHandler Proxy …

记一次Cannot deploy POJO class [xxx$$EnhancerBySpringCGLIB$$xxx]的错误

最近项目上需要使用websocket做服务端&#xff0c;那好说啊&#xff0c;直接springboot集成的websocket 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><versi…

Qt 实现简易的视频播放器,功能选择视频,播放,暂停,前进,后退,进度条拖拉,视频时长显示

1.效果图 2.代码实现 2.1 .pro文件 QT core gui multimedia multimediawidgets 2.2 .h文件 #ifndef VIDEOPLAYING_H #define VIDEOPLAYING_H#include <QWidget> #include<QFileDialog>#include<QMediaPlayer> #include<QMediaRecorder> #in…

第十四届蓝桥杯C/C++大学B组题解(一)

1、日期统计 #include <bits/stdc.h> using namespace std; int main() {int array[100] {5, 6, 8, 6, 9, 1, 6, 1, 2, 4, 9, 1, 9, 8, 2, 3, 6, 4, 7, 7,5, 9, 5, 0, 3, 8, 7, 5, 8, 1, 5, 8, 6, 1, 8, 3, 0, 3, 7, 9,2, 7, 0, 5, 8, 8, 5, 7, 0, 9, 9, 1, 9, 4, 4, 6,…

manga-ocr漫画日文ocr

github 下载 解压 anaconda新建环境 conda create -n manga_ocr python3.8 激活环境 conda activate manga_ocr cd到解压目录 cd /d manga-ocr-master 安装依赖包 pip install -r requirements.txt pip3 install manga-ocr 下载离线model huggingface 123云盘 解压到一个目录…

实践笔记-03 docker buildx 使用

docker buildx 使用 1.启用docker buildx2.启用 binfmt_misc3.从默认的构建器切换到多平台构建器3.1创建buildkitd.toml文件&#xff08;私有仓库是http没有证书的情况下&#xff0c;需要配置&#xff09;3.2创建构建器并使用新创建的构建器 4.构建多架构镜像并推送至harbor仓库…

在虚拟机尝试一次用启动盘重装系统

在虚拟机尝试一次用启动盘重装系统 没有自己重装过系统&#xff0c;也不敢对自己的笔记本下手&#xff0c;用虚拟机重装玩玩试试。 先设置成u盘启动 从boot中选择相应的创建的硬盘即可&#xff08;刚刚突然发现图片不能上传了&#xff0c;经过乱七八糟的尝试后&#xff0c;开一…