穿越数据迷宫:C++哈希表的奇幻旅程
文章目录
- 前言
- 📔一、unordered系列关联式容器
- 📕1.1 `unordered` 容器概述
- 📕1.2 哈希表在 `unordered` 容器中的实现原理
- 📕1.3 `unordered` 容器的特点
- 📔二、`unordered_set ` 和 `unordered_map` 的基本操作
- 📕2.1 `unordered_set` 的基本用法
- 📖1. 创建和初始化
- 📖2. 插入元素
- 📖3. 查找元素
- 📖4. 删除元素
- 📖5. 大小和清空
- 📕2.2 `unordered_map` 的基本用法
- 📖1. 创建和初始化
- 📖2. 插入元素
- 📖3. 查找元素
- 📖4. 删除元素
- 📖5. 大小和清空
- 📔三、哈希表
- 📕3.1 哈希表的基本原理
- 📕3.2 哈希函数
- 📕3.3 解决冲突的几种办法
- 📖1. 开放地址法(Open Addressing)
- 📄a. 线性探测(Linear Probing)
- 📄b. 二次探测(Quadratic Probing)
- 📄c. 双重哈希(Double Hashing)
- 📖2. 拉链法(Chaining)
- 📔四、哈希表的模拟实现
- 📕4.1 开放地址法(线性探测)
- 📖1. `DefaultHashFunc` 哈希函数类
- 📖2. `HashData` 类
- 📖3. `HashTable` 类
- 📖4. 私有成员
- 🏷️总结
- 📕4.2 拉链法(哈希桶)
- 📖1. `HashNode` 类
- 📖2. `HashTable` 类
- 📄成员变量
- 📄构造函数和析构函数
- 📄插入操作 `Insert`
- 📄查找操作 `Find`
- 📄删除操作 `Erase`
- 📄打印操作 `Print`
- 🏷️总结
- 结语
前言
在C++的世界中,哈希表是一种高效、独特的数据结构,被广泛应用于需要快速查找、插入、删除的场景。通过哈希函数将数据映射到表中,它不仅提高了操作效率,还在解决冲突时展现了精巧的设计。哈希表在算法的应用中尤为重要,无论是缓存、字典处理还是集合操作,都离不开它的身影。本篇博客将带你一步步深入理解哈希表的实现原理及其应用,让你在编码之路上更上一层楼。
📔一、unordered系列关联式容器
在 C++ 标准库中,unordered
系列容器(如 unordered_map
和 unordered_set
)是一类基于哈希表实现的关联式容器。与传统的 map
和 set
容器不同,unordered
容器通过哈希函数实现了无序存储,能够在均摊 O ( 1 ) O(1) O(1) 时间复杂度内完成插入、查找和删除操作。这使得 unordered
系列容器在处理需要快速查找的场景中非常高效。
📕1.1 unordered
容器概述
unordered
容器包含以下几种类型,主要用于不同类型的键值对或集合操作:
unordered_set
:一个不包含重复元素的集合,存储的是唯一的键。unordered_multiset
:与unordered_set
相似,但允许包含重复元素。unordered_map
:一个键值对的容器,每个键只能出现一次,键是唯一的。unordered_multimap
:一个键值对的容器,允许键重复。
与传统的有序容器(如 map
和 set
)不同,unordered
容器中的元素没有特定的顺序。因此,unordered_map
和 unordered_set
更加适用于不关心元素顺序、仅关注快速访问的场景。
📕1.2 哈希表在 unordered
容器中的实现原理
unordered
容器的核心数据结构是哈希表,它利用哈希函数将键映射到表中的位置。在哈希表中,unordered
容器采用了一种**桶(bucket)**的机制来存储数据:
- 哈希函数:将键值映射到特定的哈希值,这个哈希值会决定键值对存储的位置(即桶)。
- 桶:哈希表中的每个位置称为一个桶,键值对根据哈希值分布在不同的桶中。
- 冲突处理:当多个键映射到同一个桶时,使用链表(或其他方法)来解决冲突。这种冲突解决方法通常称为拉链法。
📕1.3 unordered
容器的特点
- 均摊时间复杂度:在理想的情况下,插入、查找和删除的平均时间复杂度为 O ( 1 ) O(1) O(1)。
- 无序性:
unordered
容器不维护元素的顺序,元素顺序与插入顺序无关。 - 哈希函数与相等比较器:可以指定自定义的哈希函数和比较器,适用于自定义类型和不同的哈希策略。
📔二、unordered_set
和 unordered_map
的基本操作
📕2.1 unordered_set
的基本用法
unordered_set
是一个集合,用于存储唯一的元素,元素的顺序是无序的。主要用在需要快速查找元素的场景中。
📖1. 创建和初始化
#include <unordered_set>
#include <iostream>int main() {std::unordered_set<int> uset = {1, 2, 3, 4, 5}; // 初始化// 遍历输出元素for (const int& num : uset) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
📖2. 插入元素
使用 insert
方法插入元素。如果元素已存在,insert
不会插入重复元素。
uset.insert(6); // 插入元素6
uset.insert(3); // 3已存在,不插入
📖3. 查找元素
使用 find
方法查找元素,若找到则返回元素的迭代器,否则返回 end()
。
if (uset.find(3) != uset.end()) {std::cout << "3 存在于集合中" << std::endl;
}
📖4. 删除元素
使用 erase
方法删除指定元素,可以传入元素值或迭代器。
uset.erase(4); // 删除元素4
📖5. 大小和清空
size()
获取集合的大小。clear()
清空集合中的所有元素。
std::cout << "集合大小: " << uset.size() << std::endl;
uset.clear();
📕2.2 unordered_map
的基本用法
unordered_map
是一种键值对关联容器,使用哈希表实现。每个键是唯一的,可以通过键快速访问对应的值。
📖1. 创建和初始化
#include <unordered_map>
#include <iostream>
#include <string>int main() {std::unordered_map<std::string, int> umap = {{"apple", 3}, {"banana", 5}, {"orange", 2}}; // 初始化// 遍历键值对for (const auto& kv : umap) {std::cout << kv.first << " -> " << kv.second << std::endl;}return 0;
}
📖2. 插入元素
使用 insert
或 operator[]
插入键值对。operator[]
如果键不存在,会插入新键并初始化值为默认值。
umap["grape"] = 10; // 插入或更新键为 "grape" 的值
umap.insert({"melon", 8}); // 插入键值对 {"melon", 8}
📖3. 查找元素
使用 find
方法查找元素,若找到则返回指向键值对的迭代器,否则返回 end()
。
auto it = umap.find("banana");
if (it != umap.end()) {std::cout << "香蕉数量: " << it->second << std::endl;
}
📖4. 删除元素
使用 erase
方法删除指定键的元素。
umap.erase("orange"); // 删除键 "orange"
📖5. 大小和清空
size()
获取unordered_map
的键值对数量。clear()
清空所有元素。
std::cout << "键值对数量: " << umap.size() << std::endl;
umap.clear();
📔三、哈希表
哈希表是一种基于哈希函数的数据结构,用于快速存储和查找数据。它的核心思想是将键(Key)通过哈希函数转换为数组索引,从而实现快速的数据访问。哈希表被广泛用于字典、缓存、集合等需要高效查找的场景,是以上所介绍容器的底层结构。
📕3.1 哈希表的基本原理
哈希表包含以下几个核心概念:
- 哈希函数:将任意类型的键映射到数组中的一个索引位置。
- 桶(Bucket):哈希表的每个索引位置称为一个桶,存储一个或多个元素。
- 冲突(Collision):当不同的键被映射到相同的索引时发生冲突。
- 负载因子(Load Factor):表示哈希表的填充程度,用公式
负载因子 = 元素个数 / 桶的数量
表示。负载因子过高会影响性能,通常哈希表会在负载因子达到一定值时扩容。
哈希表的操作,如插入、删除、查找,在理想情况下的时间复杂度为 O ( 1 ) O(1) O(1)。这是因为哈希表可以通过哈希函数将键快速映射到对应的存储位置。
📕3.2 哈希函数
哈希函数是哈希表性能的核心,其目的是将键均匀地分布在哈希表的桶中,减少冲突的发生。好的哈希函数具有以下特性:
- 均匀性:不同的键被分布在不同的桶中,避免过多的冲突。
- 快速计算:哈希函数的计算速度应尽可能快,以提高哈希表操作的整体效率。
在 C++ 中,标准库提供了许多内置类型的哈希函数,如 std::hash<int>
、std::hash<std::string>
等。此外,用户也可以为自定义类型定义自己的哈希函数。
📕3.3 解决冲突的几种办法
📖1. 开放地址法(Open Addressing)
开放地址法是在哈希表中找一个新的空位来存储冲突元素。当发生冲突时,通过探测寻找下一个可用位置存放新元素。开放地址法常见的探测方式有:
📄a. 线性探测(Linear Probing)
在发生冲突时,线性探测通过固定步长(通常为 1)逐步查找下一个空位,直到找到可用位置。
- 公式:
hash(key) + i
,其中i
是冲突次数。 - 优点:实现简单。
- 缺点:容易出现“聚集”现象,即相邻的桶很容易连续被填满,降低查找效率。
int linearProbing(int hashValue, int i, int tableSize) {return (hashValue + i) % tableSize;
}
📄b. 二次探测(Quadratic Probing)
二次探测通过平方增长的步长来查找下一个位置,避免线性探测的“聚集”问题。
- 公式:
hash(key) + i^2
。 - 优点:减少了聚集现象。
- 缺点:在高负载因子下,探测序列可能重复循环导致找不到空位。
int quadraticProbing(int hashValue, int i, int tableSize) {return (hashValue + i * i) % tableSize;
}
📄c. 双重哈希(Double Hashing)
双重哈希采用两个哈希函数,在发生冲突时,根据第二个哈希函数的值决定步长。
- 公式:
hash1(key) + i * hash2(key)
。 - 优点:能有效降低聚集现象,冲突处理更加均匀。
- 缺点:需要设计两个独立的哈希函数,且对负载因子要求较高。
int doubleHashing(int hashValue, int i, int tableSize, int hash2) {return (hashValue + i * hash2) % tableSize;
}
📖2. 拉链法(Chaining)
拉链法将每个哈希表位置(桶)设计为一个链表,所有被哈希到同一位置的元素都存储在该链表中。插入新元素时,将其添加到链表中,查找时则遍历该链表。
- 优点:
- 实现简单,链表支持动态扩展,不受哈希表容量影响。
- 插入、删除操作简单,尤其适合不定长数据结构(如链表、树等)。
- 缺点:
- 冲突严重时,链表可能变长,影响查找效率,最坏情况为 O ( n ) O(n) O(n)。
- 需要额外的链表存储空间。
📔四、哈希表的模拟实现
📕4.1 开放地址法(线性探测)
📖1. DefaultHashFunc
哈希函数类
DefaultHashFunc
是一个通用的哈希函数模板类,用于将键转换为哈希值。代码对整型和字符串类型分别进行了不同的哈希计算:
- 对于整型或其他非字符串类型,直接将键转换为
size_t
类型。 - 对于
string
类型,定义了一个专门的特化版本,使用一种常见的哈希算法,将字符串中的每个字符逐步加入到哈希值中,并乘以一个质数(131)来增加分布的均匀性。
template<class K>
class DefaultHashFunc {
public:size_t operator()(const K& key) {return (size_t)key;}
};template<>
class DefaultHashFunc<string> {
public:size_t operator()(const string& str) {size_t hash = 0;for (auto ch : str) {hash *= 131;hash += ch;}return hash;}
};
📖2. HashData
类
HashData
类表示哈希表中的一个数据节点,包含一个键值对 _kv
和一个 STATE
状态 _state
。STATE
枚举类定义了三个状态:
- EXIST:表示该位置有数据。
- EMPTY:表示该位置为空。
- DELETE:表示该位置的数据已被删除。
这种状态管理方便在开放地址法中处理删除操作。
enum STATE {EXIST,EMPTY,DELETE
};template<class K, class V>
class HashData {
public:pair<K, V> _kv;STATE _state = EMPTY;
};
📖3. HashTable
类
HashTable
是哈希表的主体类,支持以下操作:
- 构造函数:初始化哈希表容量,默认大小为 10。
public:HashTable() {_table.resize(10);}
- 插入操作
Insert
:向哈希表插入一个键值对kv
。如果负载因子达到 0.7,进行扩容操作(即resize
)。- 扩容操作:创建一个新的哈希表,将所有旧数据重新插入到新表中。这样可以重新计算哈希值,以确保数据均匀分布。
- 线性探测:若哈希值对应的桶已经存在数据,使用线性探测法查找下一个空闲位置,直到找到空位。
bool Insert(const pair<K, V>& kv) {// 扩容,扩容之后映射关系变了,需要重新映射if ((double)_n / _table.size() >= 0.7) {size_t newSize = _table.size() * 2;// 遍历旧表,重新映射到新表HashTable<K, V, HashFunc> newHT;newHT._table.resize(newSize);for (size_t i = 0; i < _table.size(); ++i) {newHT.Insert(_table[i]._kv);}_table.swap(newHT._table);}// 线性探测// 起始位置,处理空间大小,是capacity还是sizeHashFunc hf;size_t hashi = hf(kv.first) % _table.size();while (_table[hashi]._state == EXIST) {++hashi;hashi %= _table.size();}_table[hashi]._kv = kv;_table[hashi]._state = EXIST;++_n;return true;
}
- 查找操作
Find
:通过哈希函数定位键在哈希表中的位置,若发生冲突,则使用线性探测逐步查找直到找到匹配的键或遇到空位置。
HashData<const K, V>* Find(const K& key) {// 线性探测HashFunc hf;size_t hashi = hf(key) % _table.size();while (_table[hashi]._state != EMPTY) {if (_table[hashi]._state == EXIST && _table[hashi]._kv.first == key) {return (HashData<const K, V>*) & _table[hashi];}++hashi;hashi %= _table.size();}return nullptr;
}
- 删除操作
Erase
:先通过Find
方法查找键的位置,如果找到则将该位置的状态标记为DELETE
,并减少元素计数_n
。
bool Erase(const K& key) {HashData<const K, V>* ret = Find(key);if (ret) {ret->_state = DELETE;--_n;return true;}return false;
}
📖4. 私有成员
_table
:存储HashData
的向量,作为哈希表的实际存储容器。_n
:存储有效数据的个数,用于计算负载因子并触发扩容操作。
private:vector<HashData<K, V>> _table;size_t _n = 0; // 存储有效数据的个数
};
🏷️总结
- 哈希函数:代码定义了默认的
DefaultHashFunc
,支持不同类型的键。 - 开放地址法(线性探测):处理冲突的方式,当发生哈希冲突时,逐步向后探测下一个空位。
- 扩容机制:当负载因子达到一定值时,动态扩展哈希表大小并重新分配数据,减少冲突。
📕4.2 拉链法(哈希桶)
这里我们继续沿用以上的DefaultHashFunc
哈希函数类。
📖1. HashNode
类
HashNode
类表示哈希表中的一个节点,包含一个键值对 _kv
和一个指向下一个节点的指针 _next
。该类用于构成链表,以解决哈希冲突。
template<class K, class V>
class HashNode {
public:pair<K, V> _kv;HashNode<K, V>* _next;HashNode(const pair<K, V>& kv): _kv(kv), _next(nullptr) {}
};
_kv
:存储键值对。_next
:指向链表中的下一个节点。
📖2. HashTable
类
HashTable
使用拉链法来处理哈希冲突。每个桶存储一个链表的头节点,当多个键映射到相同的哈希位置时,通过链表存储多个节点。
📄成员变量
_table
:std::vector
的每个位置存储一个链表头节点指针,表示一个桶。_n
:存储哈希表中当前有效数据的数量。
private:vector<Node*> _table; // 指针数组size_t _n = 0; // 存储了多少有效数据
📄构造函数和析构函数
- 构造函数:初始化哈希表大小为 10 个桶。
- 析构函数:遍历每个桶的链表并释放所有节点的内存。
HashTable() {_table.resize(10, nullptr);
}~HashTable() {for (size_t i = 0; i < _table.size(); ++i) {Node* cur = _table[i];while (cur) {Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}
}
📄插入操作 Insert
插入操作首先检查键是否已存在,若存在则返回 false
,避免重复插入。若键不存在,执行以下步骤:
- 扩容:若负载因子达到 1,则将哈希表容量扩大一倍。
- 新建一个更大的表,并将旧表中的元素重新哈希并插入新表。
- 计算哈希值:根据哈希函数
HashFunc
计算哈希值。 - 插入到链表:将新节点插入到对应桶的链表头部(头插法),方便且高效。
bool Insert(const pair<K, V>& kv) {if (Find(kv.first)) {return false;}HashFunc hf;if (_n == _table.size()) {size_t newSize = _table.size() * 2;vector<Node*> newTable;newTable.resize(newSize, nullptr);for (size_t i = 0; i < _table.size(); ++i) {Node* cur = _table[i];while (cur) {Node* next = cur->_next;size_t hashi = hf(cur->_kv.first) % newSize;cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newTable);}size_t hashi = hf(kv.first) % _table.size();Node* newnode = new Node(kv);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return true;
}
📄查找操作 Find
查找操作使用哈希函数计算键的位置,遍历该位置的链表,查找对应键的节点。如果找到则返回节点指针,否则返回 nullptr
。
Node* Find(const K& key) {HashFunc hf;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];while (cur) {if (cur->_kv.first == key) {return cur;}cur = cur->_next;}return nullptr;
}
📄删除操作 Erase
删除操作与查找类似,通过哈希值定位到对应的链表,找到目标节点后将其从链表中移除。
- 如果要删除的节点是链表头节点,直接更新桶头指针。
- 否则,将该节点从链表中删除。
bool Erase(const K& key) {HashFunc hf;size_t hashi = hf(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];while (cur) {if (cur->_kv.first == key) {if (prev == nullptr) {_table[hashi] = cur->_next;} else {prev->_next = cur->_next;}--_n;delete cur;return true;}prev = cur;cur = cur->_next;}return false;
}
📄打印操作 Print
打印哈希表中的内容,展示每个桶中的链表结构,用于观察哈希表的分布情况。
void Print() {for (size_t i = 0; i < _table.size(); ++i) {printf("[%d]->", i);Node* cur = _table[i];while (cur) {cout << cur->_kv.first << ":" << cur->_kv.second << "->";cur = cur->_next;}printf("NULL\n");}cout << endl;
}
🏷️总结
- 哈希函数:使用默认的
HashFunc
计算键的哈希值,映射到对应桶位置。 - 拉链法:每个桶使用链表来处理哈希冲突,链表结构灵活且易于扩展。
- 扩容:当负载因子达到 1 时,哈希表自动扩容以减少冲突。
结语
哈希表不仅仅是一个工具,更是一种思想。它带给我们的不仅是高效的算法,更是对数据结构和算法优化的深刻理解。掌握了哈希表的设计与实现,你将拥有在数据密集型任务中快速处理信息的能力。希望本篇博客能帮助你在C++的学习道路上更进一步,感受到编程的魅力。
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!
相关文章:

穿越数据迷宫:C++哈希表的奇幻旅程
文章目录 前言📔一、unordered系列关联式容器📕1.1 unordered 容器概述📕1.2 哈希表在 unordered 容器中的实现原理📕1.3 unordered 容器的特点 📔二、unordered_set 和 unordered_map 的基本操作📕2.1 un…...

SMT32 智能环境监测系统 嵌入式初学者课堂小组作业
一、应用知识 1,开发语言:C语言 2,开发工具:Keil uVision5、Setup_JLinkARM_V415e 3,开发平台:XCOM V2.0 4,开发知识:温湿度传感DHT11、STM32F4xx中文参考手册 5,文…...

20241114给荣品PRO-RK3566开发板刷Rockchip原厂的Android13下适配RJ45以太网卡
20241114给荣品PRO-RK3566开发板刷Rockchip原厂的Android13下适配RJ45以太网卡 2024/11/14 15:44 缘起:使用EVB2的方案,RJ45加进去怎么也不通。 实在没有办法,只能将荣品的SDK:rk-android13-20240713.tgz 解压缩,编译之…...
JVM这个工具的使用方法
JVM(Java虚拟机)是Java程序运行的基础环境,它提供了内存管理、线程管理和性能监控等功能。吃透JVM诊断方法,可以帮助开发者更有效地解决Java应用在运行时遇到的问题。以下是一些常见的JVM诊断方法: 使用JConsole: JCon…...
创建型设计模式与面向接口编程
创建型设计模式(Creational Patterns)的主要目的之一就是帮助实现面向接口编程,避免直接创建实现类的实例。通过这些模式,可以将对象的创建过程封装起来,使得客户端代码不需要知道具体的实现类,从而提高代码…...

算法每日双题精讲——滑动窗口(长度最小的子数组,无重复字符的最长子串)
🌟快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。 🌟 别再犹豫了!快来订阅我们的算法每日双题精讲专栏,一起踏上算法学习的精彩之旅吧!💪…...

1.7 JS性能优化
从输入url到页面加载完成都做了些什么 输入 URL - 资源定位符 http://www.zhaowa.com - http 协议 域名解析 https://www.zhaowa.com > ip 1. 切HOST? > 浏览器缓存映射、系统、路由、运营商、根服务器 2. 实际的静态文件存放? 大流量 > 多个…...

STL - vector的使用和模拟实现
目录 一:vector的构造函数 二:vector的迭代器 三vector的空间增长问题 四:vector 增删查改接口 五:vector的模拟实现 (一)一些简单接口的实现: (二)一些复杂接口的…...

《鸿蒙生态:开发者的机遇与挑战》
一、引言 在当今科技飞速发展的时代,操作系统作为连接硬件与软件的核心枢纽,其重要性不言而喻。鸿蒙系统的出现,为开发者带来了新的机遇与挑战。本文将从开发者的角度出发,阐述对鸿蒙生态的认知和了解,分析鸿蒙生态的…...

【C++融会贯通】二叉树进阶
目录 一、内容说明 二、二叉搜索树 2.1 二叉搜索树概念 2.2 二叉搜索树操作 2.2.1 二叉搜索树的查找 2.2.2 二叉搜索树的插入 2.2.3 二叉搜索树的删除 2.3 二叉搜索树的实现 2.3.1 二叉搜索树的节点设置 2.3.2 二叉搜索树的查找函数 2.3.2.1 非递归实现 2.3.2.2 递…...
使用python-Spark使用的场景案例具体代码分析
使用场景 1. 数据批处理 • 日志分析:互联网公司每天会产生海量的服务器日志,如访问日志、应用程序日志等。Spark可以高效地读取这些日志文件,对数据进行清洗(例如去除无效记录、解析日志格式)、转换(例如…...
如何查看本地的个人SSH密钥
1.确保你的电脑上安装了 Git。 你可以通过终端或命令提示符输入以下命令来检查: git --version 如果没有安装,请前往 Git 官网 下载并安装适合你操作系统的版本。 2.查找SSH密钥 默认情况下,SSH密钥存储在你的用户目录下的.ssh文件夹中。…...
本人认为 写程序的三大基本原则
1. 合法性 定义:合法性指的是程序必须遵守法律法规和道德规范,不得用于非法活动。 建议: 了解法律法规:在编写程序之前,了解并遵守所在国家或地区的法律法规,特别是与数据隐私、版权、网络安…...

A030-基于Spring boot的公司资产网站设计与实现
🙊作者简介:在校研究生,拥有计算机专业的研究生开发团队,分享技术代码帮助学生学习,独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取,记得注明来意哦~🌹 赠送计算机毕业设计600…...
React Hooks 深度解析与实战
💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 React Hooks 深度解析与实战 React Hooks 深度解析与实战 React Hooks 深度解析与实战 引言 什么是 Hooks? 定义 为什么需要 Ho…...

#渗透测试#SRC漏洞挖掘#蓝队基础之网络七层杀伤链04 终章
网络杀伤链模型(Kill Chain Model)是一种用于描述和分析网络攻击各个阶段的框架。这个模型最初由洛克希德马丁公司提出,用于帮助企业和组织识别和防御网络攻击。网络杀伤链模型将网络攻击过程分解为多个阶段,每个阶段都有特定的活…...

计算机毕业设计Python+大模型农产品推荐系统 农产品爬虫 农产品商城 农产品大数据 农产品数据分析可视化 PySpark Hadoop
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...

爬虫补环境案例---问财网(rpc,jsdom,代理,selenium)
目录 一.环境检测 1. 什么是环境检测 2.案例讲解 二 .吐环境脚本 1. 简介 2. 基础使用方法 3.数据返回 4. 完整代理使用 5. 代理封装 6. 封装所有使用方法 jsdom补环境 1. 环境安装 2. 基本使用 3. 添加参数形式 Selenium补环境 1. 简介 2.实战案例 1. 逆向目…...
SpringBoot有几种获取Request对象的方法
HttpServletRequest 简称 Request,它是一个 Servlet API 提供的对象,用于获取客户端发起的 HTTP 请求信息。例如:获取请求参数、获取请求头、获取 Session 会话信息、获取请求的 IP 地址等信息。 那么问题来了,在 Spring Boot 中…...
在 Windows 11 中使用 MuMu 模拟器 12 国际版配置代理
**以下是优化后的教学内容,使用 Markdown 格式,便于粘贴到 CSDN 或其他支持 Markdown 格式的编辑器中: 在 Windows 11 中使用 MuMu 模拟器 12 国际版配置代理 MuMu 模拟器内有网络设置功能,可以直接在模拟器中配置代理。但如果你不确定如何操作,可以通过在 Windows 端设…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...

以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...

JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散
前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说,在叠衣服的过程中,我会带着团队对比各种模型、方法、策略,毕竟针对各个场景始终寻找更优的解决方案,是我个人和我司「七月在线」的职责之一 且个人认为,…...