穿越数据迷宫: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_mapunordered_set)是一类基于哈希表实现的关联式容器。与传统的 mapset 容器不同,unordered 容器通过哈希函数实现了无序存储,能够在均摊 O ( 1 ) O(1) O(1) 时间复杂度内完成插入、查找和删除操作。这使得 unordered 系列容器在处理需要快速查找的场景中非常高效。

📕1.1 unordered 容器概述

unordered 容器包含以下几种类型,主要用于不同类型的键值对或集合操作:

  1. unordered_set:一个不包含重复元素的集合,存储的是唯一的键。
  2. unordered_multiset:与 unordered_set 相似,但允许包含重复元素。
  3. unordered_map:一个键值对的容器,每个键只能出现一次,键是唯一的。
  4. unordered_multimap:一个键值对的容器,允许键重复。

与传统的有序容器(如 mapset)不同,unordered 容器中的元素没有特定的顺序。因此,unordered_mapunordered_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. 插入元素

使用 insertoperator[] 插入键值对。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 状态 _stateSTATE 枚举类定义了三个状态:

  • 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 使用拉链法来处理哈希冲突。每个桶存储一个链表的头节点,当多个键映射到相同的哈希位置时,通过链表存储多个节点。

📄成员变量
  • _tablestd::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. 扩容:若负载因子达到 1,则将哈希表容量扩大一倍。
    • 新建一个更大的表,并将旧表中的元素重新哈希并插入新表。
  2. 计算哈希值:根据哈希函数 HashFunc 计算哈希值。
  3. 插入到链表:将新节点插入到对应桶的链表头部(头插法),方便且高效。
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

删除操作与查找类似,通过哈希值定位到对应的链表,找到目标节点后将其从链表中移除。

  1. 如果要删除的节点是链表头节点,直接更新桶头指针。
  2. 否则,将该节点从链表中删除。
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前进的动力!

在这里插入图片描述

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

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

相关文章

飞牛云fnOS本地部署WordPress个人网站并一键发布公网远程访问

文章目录 前言1. Docker下载源设置2. Docker下载WordPress3. Docker部署Mysql数据库4. WordPress 参数设置5. 飞牛云安装Cpolar工具6. 固定Cpolar公网地址7. 修改WordPress配置文件8. 公网域名访问WordPress 前言 本文旨在详细介绍如何在飞牛云NAS上利用Docker部署WordPress&a…

2023年MathorCup数学建模B题城市轨道交通列车时刻表优化问题解题全过程文档加程序

2023年第十三届MathorCup高校数学建模挑战赛 B题 城市轨道交通列车时刻表优化问题 原题再现&#xff1a; 列车时刻表优化问题是轨道交通领域行车组织方式的经典问题之一。列车时刻表规定了列车在每个车站的到达和出发&#xff08;或通过&#xff09;时刻&#xff0c;其在实际…

安全见闻1-5

涵盖了编程语言、软件程序类型、操作系统、网络通讯、硬件设备、web前后端、脚本语言、病毒种类、服务器程序、人工智能等基本知识&#xff0c;有助于全面了解计算机科学和网络技术的各个方面。 安全见闻1 1.编程语言简要概述 C语言&#xff1a;面向过程&#xff0c;适用于系统…

闯关leetcode——3178. Find the Child Who Has the Ball After K Seconds

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/find-the-child-who-has-the-ball-after-k-seconds/description/ 内容 You are given two positive integers n and k. There are n children numbered from 0 to n - 1 standing in a queue in o…

Java结合ElasticSearch根据查询关键字,高亮显示全文数据。

由于es高亮显示机制的问题。当全文内容过多&#xff0c;且搜索中标又少时&#xff0c;就会出现高亮结果无法覆盖全文。因此需要根据需求手动替换。 1.根据es的ik分词器获取搜索词的分词结果。 es部分&#xff1a; //中文分词解析 post /_analyze {"analyzer":"…

Python——NumPy库的简单用法,超级详细教程使用

一、什么是NumPy库 NumPy&#xff1a;它是python的一个科学计算库函数&#xff0c;它是由c语言编写的 它应用于数据处理、机器学习、图像处理、文件操作等等 二、array函数 这里导入库numpy&#xff0c;命名为np&#xff0c;后面的np都是代表着是numpy函数 array函数表示创建…

【Java语言】String类

在C语言中字符串用字符可以表示&#xff0c;可在Java中有单独的类来表示字符串&#xff08;就是String&#xff09;&#xff0c;现在我来介绍介绍String类。 字符串构造 一般字符串都是直接赋值构造的&#xff0c;像这样&#xff1a; 还可以这样构造&#xff1a; 图更能直观的…

自由学习记录(21)

感觉反而 还复杂一点&#xff0c;关系并不纯粹&#xff0c;游戏里用的少...的确 是知道为什么游戏不用了 理解思想就可以了&#xff0c;实际操作也是动态的分析&#xff0c;硬套某种模式也不是怎么很合适 MVC的了解应该是差不多了&#xff0c;重点还是实际中的使用了 所以删了…

Bugku CTF_Web——点login咋没反应

Bugku CTF_Web——点login咋没反应 进入靶场 随便输个试试 看来确实点login没反应 抓包看看 也没有什么信息 看了下源码 给了点提示 一个admin.css try ?12713传参试试 拿到一个php代码 <?php error_reporting(0); $KEYctf.bugku.com; include_once("flag.php&q…

软件测试面试大全(含答案+文档)

1、你的测试职业发展是什么&#xff1f; 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师奔去。而且我也有初步的职业规划&#xff0c;前3年积累测试经验&#xff0c;按如何做好测试工程师的要点去要求自己&…

从华为到创业公司

我有一个朋友&#xff0c;在华为工作了很长一段时间&#xff0c;一年多前&#xff0c;他从华为出来到了一家创业公司。 周末趁着有时间&#xff0c;我跟他聊了下关于从华为到创业公司的一些问题&#xff0c;总结给大伙看看。 ▎1 在华为工作和在创业公司工作最大的差别是什么呢…

Linux网络——网络初识

目录 1. 认识协议 2. 协议的分层 3. OSI 七层模型 && TCP/IP 五层(四层)模型 4. 网络传输的基本流程 5. 以太网的通信原理 6. 数据的跨网络传播 7. 认识 IP 地址 ① IP 是什么 ② IP 与 MAC 的关系 ③ 为什么需要 IP 在谈及网络之前&#xff0c;我们要先对学…

React Hooks在现代前端开发中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 React Hooks在现代前端开发中的应用 React Hooks在现代前端开发中的应用 React Hooks在现代前端开发中的应用 引言 React Hooks …

执行flink sql连接clickhouse库

手把手教学&#xff0c;flink connector打通clickhouse大数据库&#xff0c;通过下发flink sql&#xff0c;来使用ck。 组件版本jdk1.8flink1.17.2clickhouse23.12.2.59 1.背景 flink官方不支持clickhouse连接器&#xff0c;工作中难免会用到。 2.方案 利用GitHub大佬提供…

【机器学习】如何配置anaconda环境(无脑版)

马上就要上机器学习的实验&#xff0c;这里想写一下我配置机器学习的anaconda环境的二三事 一、首先&#xff0c;下载安装包&#xff1a; Download Now | Anaconda 二、打开安装包&#xff0c;一直点NEXT进行安装 这里要记住你要下载安装的路径在哪&#xff0c;后续配置环境…

如何保护 Microsoft 网络免受中间人攻击

一名办公室工作人员收到了一封看似来自供应商的电子邮件&#xff0c;但该邮件被隔离了&#xff0c;用户请求将其释放。这封邮件看起来没什么问题&#xff0c;因此管理员释放了这封邮件。用户点击邮件查看内容&#xff0c;其中包括一张附加发票。 问题就从这里开始&#xff1a;…

Excel筛选的操作教程

用Excel整理数据时&#xff0c;常常要用到筛选功能&#xff0c;很多复杂的数据经过一定条件的筛选后就变得很清晰。筛选也是Excel的一个基本功能之一&#xff0c;你会使用这个功能吗&#xff1f;不会也没关系&#xff0c;接着往下看&#xff0c;接下来就来演示一些Excel表格筛选…

爬虫补环境案例---问财网(rpc,jsdom,代理,selenium)

目录 一.环境检测 1. 什么是环境检测 2.案例讲解 二 .吐环境脚本 1. 简介 2. 基础使用方法 3.数据返回 4. 完整代理使用 5. 代理封装 6. 封装所有使用方法 jsdom补环境 1. 环境安装 2. 基本使用 3. 添加参数形式 Selenium补环境 1. 简介 2.实战案例 1. 逆向目…

JAVA:探索 EasyExcel 的技术指南

1、简述 在 Java 开发中&#xff0c;Excel 文件的读写操作是一项常见的需求。阿里巴巴开源的 EasyExcel 提供了一种高效、简洁的解决方案&#xff0c;特别是在处理大规模数据时表现尤为突出。本文将详细介绍 EasyExcel 的优缺点、应用场景&#xff0c;并通过实例展示其基本用法…

力扣662:二叉树的最大宽度

给你一棵二叉树的根节点 root &#xff0c;返回树的 最大宽度 。 树的 最大宽度 是所有层中最大的 宽度 。 每一层的 宽度 被定义为该层最左和最右的非空节点&#xff08;即&#xff0c;两个端点&#xff09;之间的长度。将这个二叉树视作与满二叉树结构相同&#xff0c;两端…