【UCB CS 61B SP24】Lecture 19 20: Hashing Hashing II 学习笔记
本文首先介绍了哈希表中的两大关键概念:哈希函数与哈希码,并使用 Java 实现了一个通过链地址法解决哈希冲突的哈希表。
1. 哈希函数与哈希码
1.1 动态多列表实现整数集合
我们在 Lecture 11 中第一次介绍了集合(Set),并且用数组实现了一个 ArraySet
。但是每次查询都需要遍历一边数组,有没有办法在这中数据结构的基础上对效率进行优化?
假设我们现在考虑实现一个仅存放整数的集合,如果我们用 10 个数组来存放整数(记为 M = 10 M=10 M=10),根据键的最后一位数字将其放入对应的数组中:
这样如果我们的键大小在 0 ∼ 9 0\sim 9 0∼9 之间那么理论上插入与查找的效率为 O ( 1 ) O(1) O(1),如果键大小在 0 ∼ 99 0\sim 99 0∼99 之间那么理论上效率会慢 10 倍。
那么随着键大小范围的变化我们设定的 M M M 也跟着变化是不是能保持这种高效的效果?有没有通用的方式来表示类似“键的最后一位数字”这种说法?其实这在整数中的通用表示就是取模操作:
这样我们就能让这种方法适用于任何 M M M 的情况,即: k e y % M key\% M key%M。为了保持高效的时间复杂度,我们需要 N / M N/M N/M 小于某个常数 k k k。有两种方法来维持:
- 当最长的数组长度超过 k k k 时增加 M M M,这种办法一般会产生很多空数组,所以不使用;
- 当所有数组的平均大小超过 k k k 时,增加 M M M。
与以前讲到过的动态数组扩容原理相似,在增加 M M M 时,最好的方式就是每次将其翻倍,我们来看一个例子:
1.2 字符串集合
我们已经有方法将数字分别映射到不同的数组中了,如果我们像存字符串怎么办?其中一种方法是我们可以将字符串的首字符映射为数字(假设只考虑小写字母),例如: a = 0 , b = 1 , … , z = 25 a = 0, b = 1, \dots, z=25 a=0,b=1,…,z=25。那么此时如果我们要存入 cat
,就可以放入 2 号数组中。
如果扩容了集合,那么同理我们可以考虑前两个字符,例如: a a = 0 , a b = 1 , … , z z = 675 aa = 0, ab = 1, \dots, zz = 675 aa=0,ab=1,…,zz=675。但是这样做有什么问题?
- 这样并不满足随机分布,因为某些字母在单词中作为前缀的概率远大于其他字母,例如
aa
作为单词前缀基本很少; - 当扩容集合后,小于前缀长度的单词如何映射,例如单字符单词
a
。
这种方法最大的问题是我们让集合负责计算如何对字符串进行分类,但这不应该是集合的工作,否则集合就必须知道存在的每一个对象(包括尚未构建的对象),以及如何对它们进行分类。
集合在处理整型时是最灵活的,所以我们可以让集合只对整型起作用,那么我们就需要定义一个方法 int f(String s)
,该方法能够将字符串转换为整型,然后将字符串 s
存在 f(s)
对应的位置中。这种方法我们称其为哈希函数(Hash Function)。
常见哈希函数如下:
- 除留余数法:
hash(key) = key % capacity
(适合整数键)。 - 乘法哈希:
hash(key) = floor(capacity * (key * A % 1))
(A 为黄金分割比例)。 - 多项式滚动哈希:用于字符串(如
hash("abc") = a * P² + b * P + c
,P
为质数)。 - 加密哈希:如 SHA-256(用于分布式系统,但速度慢)。
将字符串映射到整数最佳的哈希函数为多项式滚动哈希,首先还是利用之前最初提到的思路,先用 26 进制将每个字母映射到 1 ∼ 26 1\sim 26 1∼26 这 26 个整数上,然后我们可以用下面这个公式进行映射,还是以 cat
为例:
f ( c a t ) = 3 ∗ 2 6 2 + 1 ∗ 2 6 1 + 20 ∗ 2 6 0 = 2074 f(cat) = 3 * 26^2 + 1 * 26^1 + 20 * 26^0 = 2074 f(cat)=3∗262+1∗261+20∗260=2074
这种哈希方式同样适用于整数字符串:
f ( 7901 ) = 7 ∗ 1 0 3 + 9 ∗ 1 0 2 + 0 ∗ 1 0 1 + 1 ∗ 1 0 0 = 7901 f(7901) = 7 * 10^3 + 9 * 10^2 + 0 * 10^1 + 1 * 10^0 = 7901 f(7901)=7∗103+9∗102+0∗101+1∗100=7901
但是我们比较整数与字符串,能够发现他们还是有一点区别的,那就是在整数中 000
与 0
相同,而在字符串中 aaa
与 a
明显不相同,如果 a = 0 a = 0 a=0 似乎不太合理, a = 1 a = 1 a=1 就能避免这个问题,因此可以将所有字符的映射关系加一,这就是为什么我们前面说将每个字母映射到 1 ∼ 26 1\sim 26 1∼26 上。
现在每个字符串就能唯一对应一个整数:
1.3 哈希码
但是字符串中除了小写字母之外可能还有大写字母、数字、标点符号之类的字符,如果我们希望将每个可能的字符串映射到唯一的整数上,那么就需要为每个可能的字符分配一个整数,我们可以之间用 ASCII 码来表示这种字符与整数的映射关系。
如果字符串还要兼容汉字字符怎么办?还有一种 Unicode 码能够表示这些字符。但是如果我们的字符串太长或者要使用 Unicode 这种大型的编码当字符串转换成整数后可能这个数会非常大,计算机无法表示这么大的数,这样就会发生整数溢出(Integer Overflow)。
例如在 Java 中,最大的整数为 2147483647
:
int x = 2147483647;
System.out.println(x); // 2147483647
System.out.println(x + 1); // -2147483648
我们需要将溢出的数转换到一个固定的更小的范围内,映射后的结果我们就称其为哈希码(Hash Code)。哈希码(Hash Code)是一个整数值,用于表示对象的某种“摘要”或“标识”。它是通过哈希函数(Hash Function)计算得到的,核心作用是将任意大小的数据映射到固定大小的整数空间。
哈希码的核心目的是为了快速定位对象在哈希表中的存储位置,而不关注对象之间的顺序关系(如 HashMap
、HashSet
的底层实现)。
完美的哈希码设计原则如下:
- 均匀性:不同对象的哈希码应尽可能分布均匀,减少冲突;
- 高效性:计算速度快,避免复杂计算(如频繁调用
hashCode()
时不应耗时); - 一致性:同一对象多次计算的哈希码必须相同(前提是对象未被修改);
- 与
equals()
一致:必须保证equals()
为true
的对象哈希码相同。
由于哈希码的范围是有限的,我们无法将每个字符串映射到唯一的值上,在 Java 中,获取字符串哈希码的默认方法如下:
public int hashCode() {int h = 0;for (char c : value) { // value 是 String 内部的字符数组h = 31 * h + c; // 31 是经验选择的质数}return h;
}
我们可以来测试一下字符串的默认哈希码:
package CS61B.Lecture19;public class HashCode {public static void main(String[] args) {System.out.println("cat".hashCode()); // 98262System.out.println('c' * (int) Math.pow(31, 2) + 'a' * 31 + 't'); // 98262}
}
为什么 Java 这里选择 31 来计算哈希码?31 是奇质数,乘法操作可优化为 (h << 5) - h
(即 31 * h = 32 * h - h
),提升计算效率;其次经验表明 31 能较好平衡冲突率和计算速度。
1.4 hashCode() 与 equals()
Java 中所有对象的哈希码默认基于对象的内存地址(但不完全等价于地址)获得,通过 Object.hashCode()
方法生成,该方法的默认实现是本地方法(Native):
public class Object {public native int hashCode(); // 默认实现与内存地址相关
}
其特性如下:
- 两个不同对象的默认哈希码大概率不同。
- 若未重写
hashCode()
,同一对象的哈希码在其生命周期内不变(即使对象内容变化)。
Java 规定,若重写 equals()
方法,必须同时重写 hashCode()
方法,且需满足以下条件:
- 一致性:若
a.equals(b) == true
,则a.hashCode() == b.hashCode()
。 - 逆否命题:若
a.hashCode() != b.hashCode()
,则a.equals(b)
必须为false
。 - 不唯一性:不同对象允许有相同哈希码(哈希冲突是允许的)。
既然所有对象都默认拥有哈希函数,为什么我们还要重写 hashCode()
方法?因为若两个对象 equals()
为 true
但哈希码不同,存入哈希表时会被分配到不同桶,导致无法正确检索或去重:
package CS61B.Lecture19;import java.util.HashSet;
import java.util.Set;class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic boolean equals(Object obj) {if (obj instanceof Person person)return name.equals(person.name) && age == person.age;return false;}@Overridepublic int hashCode() {return (name + age).hashCode();}
}public class HashCode {public static void main(String[] args) {Person p1 = new Person("Alice", 25);Person p2 = new Person("Alice", 25);// 重写 equals() 前为 false,重写 equals() 后为 trueSystem.out.println(p1.equals(p2));// 重写 hashCode() 前为 false,重写 hashCode() 后为 trueSystem.out.println(p1.hashCode() == p2.hashCode());Set<Person> set = new HashSet<>();set.add(p1);// 只有在同时重写 equals() 与 hashCode() 后为 trueSystem.out.println(set.contains(p2));}
}
注意最后的 set.contains(p2)
,只有在同时重写 equals()
与 hashCode()
后为 true
,这里就需要挖一下集合比较对象是否相等的逻辑了。
在 Java 中,集合(如 HashSet
)判断对象是否已存在的逻辑依赖于两个核心方法:hashCode()
和 equals()
。这两个方法必须同时满足特定规则,才能确保集合确识别重复对象。如果只重写其中一个方法,会导致逻辑不一致,集合无法正确判断对象的唯一性。
以 HashSet
为例,其底层实现是基于 HashMap
的键(Key)存储。HashSet
的添加、删除和查找操作,实际是通过 HashMap
的键操作实现的。HashMap
判断键是否重复的逻辑分为两步:
- 通过
hashCode()
定位桶(Bucket):计算键的哈希码,确定该键应存放在哪个桶中。 - 通过
equals()
比较桶内元素:如果多个键的哈希码相同(哈希冲突),则在同一个桶内遍历所有元素,调用equals()
方法逐一比较,判断是否存在重复键。
因此,集合的正确行为依赖于以下两点:
- 相同的对象必须产生相同的哈希码(
hashCode()
一致)。 - 相等的对象必须被
equals()
方法判定为true
。
2. 哈希表
2.1 基本概念
哈希表是一种通过哈希函数将键(Key)映射到数组中的特定位置(桶,Bucket)来存储键值对(Key-Value Pair)的数据结构。其核心组件如下:
- 哈希函数:将键转换为数组索引,理想情况下应均匀分布键以减少冲突。
- 冲突解决机制:处理多个键映射到同一索引的情况,常见方法包括:
- 链地址法(Chaining):每个桶维护一个链表或树结构,存储冲突的键值对。
- 开放寻址法(Open Addressing):通过线性探测、二次探测等方法寻找下一个可用桶。
- 负载因子(Load Factor):已用桶数与总桶数的比例,触发扩容的阈值(如 0.75)。
时间复杂度分析:
- 平均情况:插入、删除、查找操作均为 O ( 1 ) O(1) O(1)(假设哈希函数良好且负载因子合理)。
- 最坏情况:所有键冲突,退化为链表或线性探测序列,时间复杂度 O ( n ) O(n) O(n)。
相比于之前学过的搜索树,哈希表的平均时间复杂度极低(搜索树的平均操作时间为 O ( l o g n ) O(log n) O(logn)),适合高频单点查询,且实现简单,内存紧凑(尤其开放寻址法);但哈希表的数据是无序的,不支持范围查询,哈希函数设计也很影响性能,扩容成本高,在最坏情况下性能很差。
2.2 冲突解决机制
(1)链地址法(Separate Chaining)
- 原理:每个桶存储一个链表(或树),冲突的键值对追加到链表中。
- 实现步骤:
- 哈希函数计算索引
i = hash(key)
。 - 若桶
i
为空,直接插入键值对。 - 若冲突,遍历链表查找键是否存在,存在则更新值,不存在则追加新节点。
- 哈希函数计算索引
- 优化:链表转红黑树,即当链表长度超过阈值(如 Java 8 的
HashMap
中阈值为 8),转为红黑树,避免链表过长导致查询退化为 O ( n ) O(n) O(n)。 - 优点:实现简单,负载因子容忍度高(可超过 1)。
- 缺点:内存开销大(存储链表指针),缓存不友好。
(2)开放寻址法(Open Addressing)
- 原理:所有键值对直接存储在数组中,冲突时按规则探测下一个可用桶。
- 实现方式:
- 线性探测(Linear Probing)
- 探测公式:
index = (hash(key) + i) % capacity
(i
为探测次数)。 - 步骤:计算初始索引
i = hash(key)
,若桶i
为空,插入键值对;若被占用且键相同,更新值;若键不同,探测i + 1, i + 2, ...
直到找到空桶。 - 缺点:易产生聚集(Clustering),导致连续占用区域增大。
- 探测公式:
- 二次探测(Quadratic Probing)
- 探测公式:
index = (hash(key) + c1 * i + c2 * i²) % capacity
(c1, c2
为常数)。 - 优点:减少聚集现象。
- 缺点:可能导致无法找到空桶(即使数组未满)。
- 探测公式:
- 双重哈希(Double Hashing)
- 探测公式:
index = (hash1(key) + i * hash2(key)) % capacity
,使用第二个哈希函数hash2
计算步长。 - 优点:避免聚集,分布更均匀。
- 探测公式:
- 线性探测(Linear Probing)
(3)布谷鸟哈希(Cuckoo Hashing)
- 原理:使用两个哈希表
T1, T2
和两个哈希函数h1, h2
。 - 实现步骤:
- 计算
h1(key)
和h2(key)
。 - 若
T1[h1(key)]
为空,插入键值对。 - 若冲突,踢出
T1
中旧键old_key
,将其插入T2
的h2(old_key)
位置。 - 若再次冲突,递归踢出,直到无冲突或达到最大循环次数(触发扩容)。
- 计算
- 优点:查询最坏时间复杂度为 O ( 1 ) O(1) O(1)。
- 缺点:插入可能失败,需多次扩容。
2.3 Java 实现哈希表
现在我们使用链地址法(不考虑红黑树)实现一个哈希表:
package CS61B.Lecture19;public class MyHashMap<K extends Comparable<K>, V> {private static class Node<K, V> {K key;V value;Node<K, V> next;public Node(K key, V value, Node<K, V> next) {this.key = key;this.value = value;this.next = next;}}private static final int DEFAULT_INITIAL_CAPACITY = 16; // 默认初始容量private static final double DEFAULT_LOAD_FACTOR = 0.75; // 默认负载因子private Node<K, V>[] hashTable;private int size; // 当前容量private int threshold; // 扩容阈值public MyHashMap() {this(DEFAULT_INITIAL_CAPACITY);}public MyHashMap(int initialCapacity) {hashTable = (Node<K, V>[]) new Node[initialCapacity];size = 0;threshold = (int) (initialCapacity * DEFAULT_LOAD_FACTOR);}/** 核心操作:添加,若 key 已存在则更新 value 后返回旧的 value,否则添加新键值对后返回 null */public V put(K key, V value) {if (size >= threshold) resize(); // 当前容量达到阈值则先扩容int idx = hash(key) % hashTable.length; // 桶的索引,如果容量固定是 2 的幂次可以写成 hash(key) & (hashTable.length - 1) 加速取模运算Node<K, V> p = hashTable[idx];while (p != null) {if (p.key.equals(key)) { // key 已存在,只更新 valueV oldValue = p.value;p.value = value;return oldValue;}p = p.next;}// key 不存在,则使用头插法将新键值对插入到桶的首部,如果使用尾插法则每次插入都要遍历一次桶hashTable[idx] = new Node<>(key, value, hashTable[idx]);size++;return null;}/** 将容量扩容至原来的两倍 */private void resize() {int newCapacity = hashTable.length << 1;Node<K, V>[] newHashTable = (Node<K, V>[]) new Node[newCapacity];// 遍历旧哈希表中的所有元素,重新计算哈希码后添加到新哈希表中for (Node<K, V> p : hashTable) {while (p != null) {int newIdx = hash(p.key) % newCapacity;p.next = newHashTable[newIdx]; // 头插法newHashTable[newIdx] = p;p = p.next;}}threshold = (int) (newCapacity * DEFAULT_LOAD_FACTOR);hashTable = newHashTable;}/** 哈希函数 */private int hash(K key) {if (key == null) return 0; // 允许 null 键(存放在第 0 个桶)int h = key.hashCode();return h ^ (h >>> 16); // 高位与低位混合,以减少哈希冲突}/** 核心操作:查找,若 key 存在则返回其对应的 value,否则返回 null */public V get(K key) {int idx = hash(key) % hashTable.length;Node<K, V> p = hashTable[idx];while (p != null) {if (p.key.equals(key)) return p.value;p = p.next;}return null;}/** 核心操作:删除,若 key 存在则删除键值对后返回其 value,否则返回 null */public V remove(K key) {int idx = hash(key) % hashTable.length;Node<K, V> cur = hashTable[idx];Node<K, V> prev = null;while (cur != null) {if (cur.key.equals(key)) {if (prev == null) { // 注意需要判断删除的键是否为头节点hashTable[idx] = cur.next;} else {prev.next = cur.next;}size--;return cur.value;}prev = cur;cur = cur.next;}return null;}/** 获取大小 */public int size() {return size;}/** 是否为空 */public boolean isEmpty() {return size == 0;}/** 测试 */public static void main(String[] args) {MyHashMap<String, Integer> map = new MyHashMap<>();// 添加测试map.put("Apple", 5);map.put("Banana", 3);map.put("Cherry", 10);map.put("Banana", 30);System.out.println(map.get("Cherry")); // 10System.out.println(map.get("Banana")); // 30// 删除测试System.out.println(map.remove("Banana")); // 30System.out.println(map.get("Banana")); // nullSystem.out.println(map.size()); // 2// 扩容测试for (int i = 0; i < 26; i++) map.put(String.valueOf((char) (i + 'A')), i);System.out.println(map.get("K")); // 10System.out.println(map.size()); // 28}
}
注意我们的哈希函数 hash()
中使用 h ^ (h >>> 16)
(其中 >>>
为无符号右移运算,而 >>
为带符号右移运算)来求解哈希码的原因是因为将 hashCode()
方法获得的哈希码高位与低位混合,目的是将哈希码的高 16 位信息“扩散”到低 16 位,使得低位更随机,这样能够减少哈希冲突。使用 h >>> 16
可以确保高位补 0,无论原哈希码是正还是负,混合后的结果更均匀;如果使用 h >> 16
,负数的符号位会污染高位,导致混合后的值仍偏向负数。Java 8 的 HashMap
中的哈希函数如下:
static final int hash(Object key) {int h = key.hashCode();return (h == 0) ? 0 : h ^ (h >>> 16); // 高位与低位异或
}
其计算桶索引的流程如下,注意如果容量固定是 2 的幂次可以写成 hashCode & (hashTable.length - 1)
来加速取模运算:
3. 可变对象与不可变对象
可变对象(Mutable Objects)是指对象创建后,其状态可通过公共方法(如 setter
)被修改,例如 Java 中的 ArrayList
、StringBuilder
,可变对象的潜在风险如下:
- 状态意外修改:对象可能被外部代码或线程意外修改。
- 哈希表失效:若对象作为集合的键,修改后哈希码变化,导致无法正确检索。
- 并发问题:多线程同时修改状态可能导致数据竞争(Data Race)。
不可变对象(Immutable Objects)的状态在创建后无法被修改。所有字段被声明为 final
,且不提供修改内部状态的方法(如 setter
),例如 Java 中的 String
、Integer
、LocalDateTime
,不可变对象的特点如下:
- 线程安全:无需同步,多线程共享时不会出现状态不一致。
- 哈希表友好:哈希码在对象生命周期内不变,适合作为集合的键。
- 可缓存性:无需担心被修改,可安全缓存。
我们看一下如果在集合中存入的是可变对象会有什么后果:
package CS61B.Lecture19;import java.util.*;public class MutableObjectDemo {public static void main(String[] args) {List<Integer> items = new ArrayList<>(List.of(0, 1));Set<List<Integer>> st = new HashSet<>();st.add(items);st.add(List.of(1, 2));System.out.println(items.hashCode()); // 962items.add(7);System.out.println(items.hashCode()); // 29829System.out.println(st.contains(items)); // false}
}
我们首先将一个列表 [0, 1]
添加到集合中,这时候其哈希码为 962,假设此时我们的集合容量为 4,那么可以计算出桶的索引为: 962 % 4 = 2 962 \% 4 = 2 962%4=2。但是由于 List
是可变对象,我们将其加入集合后再往列表中添加一个元素后其哈希码就改变了,当我们再向集合中查询 [0, 1, 7]
时计算出的桶索引为: 29829 % 4 = 1 29829 \% 4 = 1 29829%4=1,而在 1 号桶中肯定是永远找不到列表 [0, 1, 7]
的:
相关文章:
【UCB CS 61B SP24】Lecture 19 20: Hashing Hashing II 学习笔记
本文首先介绍了哈希表中的两大关键概念:哈希函数与哈希码,并使用 Java 实现了一个通过链地址法解决哈希冲突的哈希表。 1. 哈希函数与哈希码 1.1 动态多列表实现整数集合 我们在 Lecture 11 中第一次介绍了集合(Set)࿰…...
一、图形图像的基本概念
文章目录 一、分辨率概念二、图形图像的区别三、位图和矢量图的区别 一、分辨率概念 图形显示计数中的分辨率概念有三种,即屏幕分辨率、显示分辨率和显卡分辨率。它们既有区别又有着密切的联系,对图形显示的处理有极大的影响。 1.屏幕分辨率 显示器分辨…...
【二.提示词工程与实战应用篇】【1.提示词工程入门:AI对话的艺术】
大家好,今天咱们来聊聊一个特别有意思的话题——提示词工程。你可能已经听说过这个词,或者在使用AI工具时不经意间接触过它。但提示词工程到底是什么?它为什么这么重要?咱们今天就来深入探讨一下,看看它是如何影响我们与AI的对话,以及如何在实际应用中发挥作用的。 什么…...
C# IComparer<T> 使用详解
总目录 前言 在 C# 编程中,排序操作是日常开发中不可或缺的一部分。当默认的排序逻辑无法满足需求时,IComparer<T> 提供了一种强大且灵活的解决方案。它允许我们为自定义类型提供特定的比较逻辑。这对于实现排序、搜索和其他需要基于特定规则进行…...
(十 三)趣学设计模式 之 模版方法模式!
目录 一、 啥是模板方法模式?二、 为什么要用模板方法模式?三、 模板方法模式的实现方式四、 模板方法模式的优缺点五、 模板方法模式的应用场景六、 总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式&a…...
KVM虚拟机磁盘创建探究-2
使用 virt-install 命令自动创建磁盘镜像和使用 qemu-img 手动创建磁盘镜像,在磁盘镜像本身格式和基本功能上是一致的,但在一些特性如初始占用磁盘空间、创建时的可配置性等方面存在区别,下面以 QCOW2 格式磁盘镜像为例进行详细说明。 初始占…...
vite创建vue项目
这里默认node已经安装好能使用npm 检查node版本node -v 执行npm create vitelatest 项目名,按提示选择Vue和语言 cd到项目名文件夹,或者直接用vscode等编辑器打开,执行npm install 启动项目npm run dev 成功界面...
js的简单介绍
一.javascript(是什么) 是一种运行在客户端(浏览器)的编程语言,实现人机交互效果 作用 网页特效(监听客户的一些行为让网页做出对应的反馈)表单验证(针对表格数据的合法性进行判断)数据交互(获取后台的数据…...
GitHub 语析 - 基于大模型的知识库与知识图谱问答平台
语析 - 基于大模型的知识库与知识图谱问答平台 GitHub 地址:https://github.com/xerrors/Yuxi-Know 📝 项目概述 语析是一个强大的问答平台,结合了大模型 RAG 知识库与知识图谱技术,基于 Llamaindex VueJS FastAPI Neo4j 构…...
Spark核心之02:RDD、算子分类、常用算子
spark内存计算框架 一、目标 深入理解RDD弹性分布式数据集底层原理掌握RDD弹性分布式数据集的常用算子操作 二、要点 ⭐️1. RDD是什么 RDD(Resilient Distributed Dataset)叫做**弹性分布式数据集,是Spark中最基本的数据抽象,…...
docker关闭mysql端口映射的使用
需求 项目中的数据库为mysql,如果将端口映射到宿主机上,容易被工具扫描出,且随着国产化的进程推进,mysql将不被允许。为了提高安全性与满足项目需求,这里采用隐藏mysql端口方式,不映射宿主机端口ÿ…...
《从入门到精通:蓝桥杯编程大赛知识点全攻略》(十八)-农夫约翰的奶酪块、蛋糕游戏、奶牛体检
前言 在算法竞赛和编程挑战中,博弈类问题往往要求我们充分理解参与者的行为模式和最优策略,从而提出合理的解法。在这篇博客中,我们将探讨三个有趣且富有挑战性的算法题:农夫约翰的奶酪块、蛋糕游戏和奶牛体检。这些问题涉及不同…...
蓝桥杯 之 图形规律
文章目录 分析组成,找到规律数正方形 在蓝桥杯中,常常会有一些图形的规律的题目需要我们去解决,所以我们需要学会其中的一些方法,我们这样才能解决对应的问题 方法1:直接对n进行拆分方法2:使用递归的思路&a…...
Django 项目模块化开发指南:实现 Vue 风格的组件化
在 Django 项目中,我们经常需要 复用 HTML 代码,避免重复编写相同的模板。例如,博客系统中,博客列表页 和 文章详情页 可能都有相同的 导航栏、模态框、页脚 等。如何像 Vue 一样进行 模块化开发,让代码更加清晰、可维护呢? 本文将详细介绍 Django 的模板继承 和 {% incl…...
在kali linux中kafka的配置和使用
官方文档 一、安装依赖 删除原有的jdk sudo apt remove --purge openjdk-\* sudo apt clean安装 Java (JDK 11) sudo apt install openjdk-11-jdk -y # 验证安装 java -version二、下载并解压 Kafka 下载 Kafka wget https://dlcdn.apache.org/kafka/3.9.0/kafka_2.13-3.9.0.t…...
Spring Bean 作用域设置为prototype在并发场景下是否是线程安全的
在并发场景下,将 Spring Bean 作用域设置为 prototype 通常能在一定程度上保证线程安全,但这并不意味着绝对的线程安全 1. prototype 作用域的特点 在 Spring 中,Bean 的作用域定义了 Bean 的生命周期和可见性。prototype 作用域表示每次从…...
Linux系统编程(三)--Linux环境基础开发工具
文章目录 前言1.软件包的管理1.1 Linux下安装软件的方式1.2 什么是软件包(yum)1.3 yum具体操作 2. 编辑器vim2.1 vim的基本概念2.2 vim下各模式的切换vim命令模式各命令汇总 2.4批量化注释和批量化去注释2.5 vim配置2.6 普通用户使用sudo提权 3. 编译器g…...
Apache Shiro 反序列化漏洞全解析(Shiro-550 Shiro-721)
一、前言 Apache Shiro 是一个强大的 Java 安全框架,广泛用于用户认证、授权、加密和会话管理。然而,由于 Shiro 在某些版本中存在反序列化漏洞,攻击者可以通过特定手法实现远程代码执行(RCE),进而获取服务…...
playbin之Source插件加载流程源码剖析
之前我们有讲解过uridecodebin的setup_source中会创建source插件,关键函数: /* create and configure an element that can handle the uri */ source gen_source_element (decoder); /** Generate and configure a source element.** Returns: (tra…...
调用的子组件中使用v-model绑定数据以及使用@调用方法
实例: 子组件my-date-picker: <!--* description: 日期组件二次封装* 解决 “日期为区间时,后端不支持传数组,而要传#分割的字符串” --> <template><el-date-pickerclass"comp-my-date-picker"v-mode…...
指纹细节提取(Matlab实现)
指纹细节提取概述指纹作为人体生物特征识别领域中应用最为广泛的特征之一,具有独特性、稳定性和便利性。指纹细节特征对于指纹识别的准确性和可靠性起着关键作用。指纹细节提取,即从指纹图像中精确地提取出能够表征指纹唯一性的关键特征点,是…...
爱普生可编程晶振 SG-8101CE 在智能家居领域展现出的优势
在智能家居的全场景应用中,设备间的协同效率、数据传输的稳定性以及系统运行的可靠性,成为衡量用户体验的核心标准。爱普生 SG-8101CE 可编程晶振以其卓越的性能,为智能门锁、传感器、中控系统等设备提供核心动力,助力厂商打造更可…...
DeepSeek掘金——DeepSeek-R1图形界面Agent指南
DeepSeek掘金——DeepSeek-R1图形界面Agent指南 本文将指导你完成设置 DeepSeek R1 和 Browser Use 的过程,以创建能够执行复杂任务的 AI 代理,包括 Web 自动化、推理和自然语言交互。 开源大型语言模型 (LLM) 的兴起使得创建可与 OpenAI 的 ChatGPT Operator 等专有解决方案…...
Linux知识-第一天
Linux的目录机构为一个树型结构 其没有盘符这个概念,只有一个根目录,所有文件均在其之下 在Linux系统中,路径之间的层级关系 使用 / 开头表示根目录,后面的表示层级关系 Linux命令入门 Linux命令基础 Linux命令通用格式 comman…...
通过多线程分别获取高分辨率和低分辨率的H264码流
目录 一.RV1126 VI采集摄像头数据并同时获取高分辨率码流和低分辨率码流流程 编辑 1.1初始化VI模块: 1.2初始化RGA模块: 1.3初始化高分辨率VENC编码器、 低分辨率VENC编码器: 1.4 VI绑定高分辨率VENC编码器,VI绑定RGA模块…...
【前端】在WebStorm中安装Node.js与nvm与npm的详细过程
文章目录 一、Node.js安装二、nvm安装三、验证安装成功总结 一、Node.js安装 首先到node.js官网下载安装文件。 https://nodejs.org/zh-cn 直接运行安装文件进行安装: 跳过继续安装: 完成安装: 完成后的安装路径: 环境变量的…...
飞书考勤Excel导入到自己系统
此篇主要用于记录Excel一行中,单条数据的日期拿取,并判断上下班打卡情况。代码可能满足不了大部分需求,目前只够本公司用,如果需要,可以参考。 需要把飞书月度汇总的考勤表导入系统中可以参考下。 下图为需要获取的年…...
Android Flow 示例
在Android开发的世界里,处理异步数据流一直是一个挑战。随着Kotlin的流行,Flow作为Kotlin协程库的一部分,为开发者提供了一种全新的方式来处理这些问题。今天,我将深入探讨Flow的设计理念,并通过具体的例子展示如何在实…...
vue videojs使用canvas截取视频画面
前言 刚开始做的时候太多坑,导致一直报错: Uncaught (in promise) TypeError: Failed to execute ‘drawImage’ on ‘CanvasRenderingContext2D’: The provided value is not of type ‘(CSSImageValue or HTMLCanvasElement or HTMLImageElement or H…...
Android 获取jks的SHA1值:java.io.IOException: Invalid keystore format
命令生成 keytool -list -v -keystore 全路径.jks -alias 别名 -storepass 密码 -keypass 密码 1、遇到 的问题: 通过快捷键 ‘win r’ 启动的小黑框运行上面的命令会出现下面这个错误keytool 错误: java.io.IOException: Invalid keystore format 2、解决问题 …...
CMake学习-生成库文件来链接生成可执行文件
生成库文件的目的就是为了复用代码与功能有一个Complex类,正常会与main.cpp一起经过.o的编译过程后,生成可执行文件demo但如果想要复用Complex类,就需要将其编译为一个库,main.cpp在运行时链接这个库 生成库文件: gcc …...
Vue 3 中 unref 的作用与 Vue Router currentRoute 的知识
目录 前言1. unref2. Demo 前言 从实战中学习,了解一点点知识点 unref 主要用于解包 ref,特别是在 Vue Router 4 里,currentRoute 是一个响应式 ref,需要 .value 或 unref 来访问具体字段 1. unref unref 是 Vue 3 提供的工具函…...
YOLOv12:目标检测新时代的破局者
目录 一、YOLOv12 横空出世二、YOLOv12 的性能飞跃2.1 多规模优势2.2 对比超越 三、技术创新与原理剖析3.1 区域注意力模块(Area Attention,A2)3.2 残差高效层聚合网络(R-ELAN)3.3 架构优化细节 四、实验验证与结果分析…...
网络安全法与等级保护 PPT 精华汇总
资源描述 本资源文件为《网络安全法与等级保护》的PPT精华汇总,内容涵盖了网络安全法与等级保护的总体框架及相关标准规范。该PPT详细介绍了网络安全法与等级保护的各个章节和条款,并提供了基础类和应用类的相关标准文件,帮助读者全面了解和…...
coze生成的工作流,发布后,利用cmd命令行执行。可以定时发日报,周报等。让他总结你飞书里面的表格。都可以
coze生成的工作流,发布后,利用cmd命令行执行。可以定时发日报,周报等。让他总结你飞书里面的表格。都可以。 很简单。 准备工作,先发布你的工作流,和发布应用。 然后,点击扣子API 。 申请一个࿰…...
K8S学习之基础六:k8s中pod亲和性
Pod节点亲和性和反亲和性 podaffinity:pod节点亲和性指的是pod会被调度到更趋近与哪个pod或哪类pod。 podunaffinity:pod节点反亲和性指的是pod会被调度到远离哪个pod或哪类pod 1. Pod节点亲和性 requiredDuringSchedulingIgnoredDuringExecution&am…...
从统计学视角看机器学习的训练与推理
从统计学视角看机器学习的训练与推理 目录 引言:统计学与机器学习的奇妙缘分训练与推理:你得先学会“看数据”再“用数据”最大似然估计(MLE):从直觉到数学证明 3.1 伯努利分布的MLE3.2 单变量高斯分布的MLE3.3 多元…...
《论数据分片技术及其应用》审题技巧 - 系统架构设计师
论数据分片技术及其应用写作框架 一、考点概述 本论题“论数据分片技术及其应用”主要考察的是软件工程中数据分片技术的理解、应用及其实际效果分析。考点涵盖以下几个方面: 首先,考生需对数据分片的基本概念有清晰的认识,理解数据分片是…...
【鸿蒙Next】鸿蒙与flutter使用自定义iconfont的ttf字体库对比总结
ttf的iconfont库如何获取 1、自己创建 第一步、 iconfont-阿里巴巴矢量图标库 打开网址 第二步、搜索自己的需要的图标、并且加购到购物车 第三步、点击购物车,添加至项目 第四步、添加至项目或者新建项目再添加 第五步、下载至本地 就得到了ttf文件 2、设计…...
Redis实战篇《黑马点评》8 附近商铺
8.附近商户 8.1GEO数据结构的基本用法 GEO就是Geolocation的简写形式,代表地理坐标。Redis在3.2版本中加入了对GEO的支持,允许存储地理坐标信息,帮助我们根据经纬度来检索数据,常见的命令有 GEOADD:添加一个地理空间…...
【大厂AI实践】美团:美团智能客服核心技术与实践
【大厂AI实践】美团:美团智能客服核心技术与实践 🌟 嗨,你好,我是 青松 ! 🌈 自小刺头深草里,而今渐觉出蓬蒿。 NLP Github 项目推荐: 【AI 藏经阁】:https://gitee.com…...
标签的ref属性 vue中为什么不用id标记标签
标签的ref属性 vue中为什么不用id标记标签 假设有一对父子组件,如果父组件和子组件中存在id相同的标签,会产生冲突。通过id获取标签会获取到先加载那个标签。 标签的ref属性的用法 在父组件App中,引入了子组件Person。 并使用ref标记了Pe…...
期权帮|股指期货3月合约交割该如何做?
锦鲤三三每日分享期权知识,帮助期权新手及时有效地掌握即市趋势与新资讯! 股指期货3月合约交割该如何做? 股指期货的交割日通常是合约到期月份的第三个星期五。 对于3月合约,若当月无特殊节假日,交割日就是3月的第三…...
Collab-Overcooked:专注于多智能体协作的语言模型基准测试平台
2025-02-27,由北京邮电大学和理想汽车公司联合创建。该平台基于《Overcooked-AI》游戏环境,设计了更具挑战性和实用性的交互任务,目的通过自然语言沟通促进多智能体协作。 一、研究背景 近年来,基于大型语言模型的智能体系统在复…...
[Computer Vision]实验七:图像检索
目录 一、实验内容 二、实验过程 2.1 准备数据集 2.2 SIFT特征提取 2.3 学习“视觉词典”(vision vocabulary) 2.4 建立图像索引并保存到数据库中 2.5 用一幅图像查询 三、实验小结 一、实验内容 实现基于颜色直方图、bag of word等方法的以图搜…...
访问控制列表(ACL)思科、华为
访问控制列表(ACL) 一、ACL的基本概念 随着网络的飞速发展,网络安全和网络服务质量QoS(Quality of Service)问题日益突出。 企业重要服务器资源被随意访问,企业机密信息容易泄露,造成安全隐患。…...
linux磁盘满了怎么安全删除文件
df -h 通过df -h /dir 查看被占满的目录,dir替换为你的文件目录 du -sh * 进入被占满的目录,执行 du -sh * ,查看哪些文件占的磁盘大 查看占用磁盘最大的文件 du -sh * | sort -rh | head -n N N通常可以设置为10 有的docker容器文件太…...
2025国家护网HVV高频面试题总结来了04(题目+回答)
网络安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 一、HVV行动面试题分类 根据面试题的内容,我们将其分为以下几类: 漏洞利用与攻击技术 …...
jenkins使用插件在Build History打印基本信息
1、插件安装 分别是description setter plugin插件和user build vars插件,下面介绍一下这两个插件: description setter plugin:作用是在 Build 栏下方增加一个功能块,用于填写自定义信息,也就是 Build history 中需要显示的文字…...
线程池的工作流程
线程池的工作流程主要包括任务提交、线程分配、任务执行和线程回收等环节,以下是对其详细的描述: 任务提交 当有任务需要执行时,用户通过线程池提供的提交方法,如execute()或submit()方法,将任务(通常是实现…...