穿越数据迷宫: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 端设…...

ASP.NET Core Webapi 返回数据的三种方式
ASP.NET Core为Web API控制器方法返回类型提供了如下几个选择: Specific type IActionResult ActionResult<T> 1. 返回指定类型(Specific type) 最简单的API会返回原生的或者复杂的数据类型(比如,string 或者…...

SQL面试题——蚂蚁SQL面试题 连续3天减少碳排放量不低于100的用户
连续3天减少碳排放量不低于100的用户 这是一道来自蚂蚁的面试题目,要求我们找出连续3天减少碳排放量低于100的用户,之前我们分析过两道关于连续的问题了 SQL面试题——最大连续登陆问题 SQL面试题——球员连续四次得分 这两个问题都是跟连续有关的,但是球员连续得分的难…...

Python酷库之旅-第三方库Pandas(216)
目录 一、用法精讲 1011、pandas.DatetimeIndex.tz属性 1011-1、语法 1011-2、参数 1011-3、功能 1011-4、返回值 1011-5、说明 1011-6、用法 1011-6-1、数据准备 1011-6-2、代码示例 1011-6-3、结果输出 1012、pandas.DatetimeIndex.freq属性 1012-1、语法 1012…...

论文解析:计算能力资源的可信共享:利益驱动的异构网络服务提供机制
目录 论文解析:计算能力资源的可信共享:利益驱动的异构网络服务提供机制 KM-SMA算法 KM-SMA算法通过不断更新节点的可行顶点标记值(也称为顶标),利用匈牙利方法(Hungarian method)来获取匹配结果。在获取匹配结果后,该算法还会判断该结果是否满足Pareto最优性,即在没…...

Spring AOP技术
1.AOP基本介绍 AOP 的全称 (aspect oriented programming) ,面向切面编程。 1.和传统的面向对象不同。 面向切面编程是根据自我的需求,将切面类的方法切入到其他的类的方法中。(这么说抽象吧!来张图来解释。) 如图 传…...

数字IC实践项目(10)—基于System Verilog的DDR4 Model/Tb 及基础Verification IP的设计与验证(付费项目)
数字IC实践项目(10)—基于System Verilog的DDR4 Model/Tb 及基础Verification IP的设计与验证(付费项目) 前言项目框图1)DDR4 Verification IP2)DDR4 JEDEC Model & Tb 项目文件1)DDR4 Veri…...

MATLAB保存多帧图形为视频格式
基本思路 在Matlab中,要将drawnow绘制的多帧数据保存为视频格式,首先需要创建一个视频写入对象。这个对象用于将每一帧图像数据按照视频格式的要求进行组合和编码。然后,在每次drawnow更新绘图后,将当前的图形窗口内容捕获为一帧图…...

redis7.x源码分析:(3) dict字典
dict字典采用经典hash表数据结构实现,由键值对组成,类似于C中的unordered_map。两者在代码实现层面存在一些差异,比如gnustl的unordered_map分配的桶数组个数是(质数n),而dict分配的桶数组个数是࿰…...

连续九届EI稳定|江苏科技大学主办
【九届EI检索稳定|江苏科技大学主办 | IEEE出版 】 🎈【截稿倒计时】!!! ✨徐秘书:gsra_huang ✨往届均已检索,已上线IEEE官网 🎊第九届清洁能源与发电技术国际学术会议(CEPGT 2…...

HarmonyOS NEXT应用开发实战 ( 应用的签名、打包上架,各种证书详解)
前言 没经历过的童鞋,首次对HarmonyOS的应用签名打包上架可能感觉繁琐。需要各种秘钥证书生成和申请,混在一起也分不清。其实搞清楚后也就那会事,各个文件都有它存在的作用。 HarmonyOS通过数字证书与Profile文件等签名信息来保证鸿蒙应用/…...