从底层到应用:开散列哈希表与_map/_set 的完整实现(附逐行注释) - 实践

news/2025/10/13 17:14:50/文章来源:https://www.cnblogs.com/slgkaifa/p/19138922

从底层到应用:开散列哈希表与_map/_set 的完整实现(附逐行注释) - 实践

从 0 到 1 实现哈希关联容器:开散列哈希表与_map/_set(附完整可运行代码)

一、引言:为什么要搞懂哈希关联容器?

在 C++ 开发中,unordered_mapunordered_set是高频使用的容器 —— 前者用于键值映射(如配置表、缓存),后者用于存储唯一元素(如 ID 集合)。它们的高效性依赖开散列(链地址法)哈希表,但很多开发者只知其然,不知其所以然。

本文将从底层原理出发,手把手实现:

  1. 基础组件(哈希函数、质数工具)
  2. 开散列哈希表(节点、迭代器、核心操作)
  3. 上层封装(_map 与_set)
  4. 实战测试(验证功能完整性)

所有代码均附带详细注释,复制即可运行,帮你彻底吃透哈希关联容器的实现逻辑。

二、基础组件:哈希表的 “地基”

哈希表的高效运行依赖两个基础:均匀的哈希函数(将键映射为索引)和合理的表大小(质数减少冲突)。

2.1 哈希函数:键→索引的转换器

哈希函数的核心目标是 “均匀分布”,避免不同键映射到同一索引(哈希冲突)。我们实现通用模板(适配基本类型)和string特化版本(适配字符串)。

#include 
#include 
#include 
#include 
using namespace std;
// 1. 通用哈希函数模板(适配int、char、long等基本类型)
template
struct HashFunc {// 重载(),使结构体成为“函数对象”(可像函数一样调用)size_t operator()(const K& key) const {// 基本类型直接转换为size_t(哈希表索引的标准类型)return static_cast(key);}
};
// 2. 哈希函数特化:适配string类型(经典BKDR算法,减少字符串冲突)
template<>
struct HashFunc {size_t operator()(const string& str) const {size_t hashVal = 0;for (char ch : str) {hashVal += static_cast(ch);  // 累加字符ASCII值hashVal *= 131;                      // 乘以质数131(经验值,增强分布均匀性)// 为什么选131?131=128+2+1,二进制运算高效,且能避免“abc”与“cba”类冲突}return hashVal;}
};

2.2 质数工具:哈希表大小的 “调节器”

哈希表的大小必须为质数—— 质数的约数少,能显著降低不同键映射到同一索引的概率(若为合数,某些哈希值会集中映射到特定桶)。

我们实现 “寻找大于等于 n 的最小质数” 的工具函数,基于预定义质数表(覆盖常用大小):

// 查找大于等于n的最小质数(用于哈希表初始化和扩容)
inline unsigned long __stl_next_prime(unsigned long n) {// 预定义质数表(从53到4294967291,共28个,覆盖主流场景)static const int kPrimeCount = 28;static const unsigned long kPrimeList[kPrimeCount] = {53,        97,        193,       389,       769,1543,      3079,      6151,      12289,     24593,49157,     98317,     196613,    393241,    786433,1572869,   3145739,   6291469,   12582917,  25165843,50331653,  100663319, 201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};// 二分查找第一个≥n的质数(效率O(log2(28))≈5,极快)const unsigned long* pStart = kPrimeList;const unsigned long* pEnd = kPrimeList + kPrimeCount;const unsigned long* pTarget = lower_bound(pStart, pEnd, n);// 若n超过最大质数,返回最大质数;否则返回目标质数return (pTarget == pEnd) ? *(pEnd - 1) : *pTarget;
}

三、底层核心:开散列哈希表完整实现

开散列哈希表的结构是 “桶数组 + 链表”:

  • 桶数组:存储链表头指针,每个桶对应一个索引;
  • 链表:同一桶内的冲突元素通过链表串联。

下面分三部分实现,每部分均附核心逻辑解析。

3.1 哈希节点:链表的 “最小单元”

每个节点存储 “数据” 和 “下一个节点指针”,用于串联同一桶内的冲突元素:

// 哈希表节点结构(链地址法的基本存储单元)
template
struct HashNode {T _data;               // 存储的数据(_map存pair,_set存const K)HashNode* _next;    // 指向下一个节点的指针(串联冲突元素)// 构造函数:初始化数据和指针(避免野指针)HashNode(const T& data): _data(data), _next(nullptr)   // 初始无后续节点,指针置空{}
};

3.2 哈希迭代器:跨桶遍历的 “关键”

哈希表的迭代器和数组 / 链表迭代器不同 —— 需要支持 “跨桶遍历”(当一个桶的链表遍历完后,自动跳转到下一个非空桶)。

因此迭代器必须持有两个核心成员:

  • _pNode:当前指向的节点;
  • _pHashT:指向所属哈希表(用于访问桶数组,寻找下一个非空桶)
// 前置声明哈希表类(迭代器需访问哈希表的私有成员,如桶数组_tables)
template
class HashTable;
// 哈希表迭代器类模板(支持普通迭代器和const迭代器)
// 参数说明:
// K:键类型;T:存储数据类型;Ref:数据引用类型(T&/const T&);Ptr:数据指针类型(T*/const T*)
// KeyOfT:从T中提取K的仿函数;Hash:哈希函数
template
struct HTIterator {typedef HashNode Node;                  // 节点类型别名typedef HashTable Ht;  // 哈希表类型别名typedef HTIterator Self;  // 迭代器自身类型Node* _pNode;  // 当前指向的节点Ht* _pHashT;   // 指向所属哈希表(用于跨桶查找)// 构造函数:初始化节点和哈希表指针HTIterator(Node* pNode, Ht* pHashT): _pNode(pNode), _pHashT(pHashT){}// 1. 解引用运算符:返回数据引用(支持访问数据)Ref operator*() const {return _pNode->_data;}// 2. 箭头运算符:返回数据指针(支持->访问成员,如it->first)Ptr operator->() const {return &(_pNode->_data);}// 3. 前置++:迭代到下一个有效节点(核心逻辑)Self& operator++() {// 情况1:当前链表有下一个节点,直接跳转到下一个节点if (_pNode->_next != nullptr) {_pNode = _pNode->_next;}// 情况2:当前链表遍历完,寻找下一个非空桶else {KeyOfT keyExtractor;  // 键提取仿函数(从T中取K)Hash hashFunc;        // 哈希函数(计算桶索引)// 步骤1:计算当前节点所在的桶索引size_t curBucketIdx = hashFunc(keyExtractor(_pNode->_data))% _pHashT->_tables.size();curBucketIdx++;  // 从下一个桶开始查找// 步骤2:遍历后续桶,找到第一个非空桶while (curBucketIdx < _pHashT->_tables.size()) {if (_pHashT->_tables[curBucketIdx] != nullptr) {_pNode = _pHashT->_tables[curBucketIdx];  // 指向非空桶的头节点return *this;}curBucketIdx++;}// 步骤3:所有桶遍历完,迭代器指向nullptr(表示end())_pNode = nullptr;}return *this;}// 4. 迭代器比较:判断是否指向同一节点bool operator!=(const Self& other) const {return _pNode != other._pNode;}bool operator==(const Self& other) const {return _pNode == other._pNode;}
};

3.3 哈希表主体:核心操作封装

哈希表主体类封装 “桶数组、元素个数” 和三大核心操作(插入、查找、删除),模板参数设计支持_map_set复用。

核心设计思路:
  • T表示存储的数据类型(_map 存pair<const K, V>,_set 存const K);
  • KeyOfT仿函数从T中提取键(解耦数据类型和键的提取逻辑);
  • 扩容条件:负载因子≥1(开散列特性,负载因子 = 元素个数 / 桶数)。
// 开散列哈希表类模板(通用设计,支持_map/_set复用)
template>
class HashTable {// 声明迭代器为友元(允许迭代器访问私有成员,如桶数组_tables)templatefriend struct HTIterator;typedef HashNode Node;  // 节点类型别名
public:// 迭代器类型定义(普通迭代器和const迭代器)typedef HTIterator Iterator;typedef HTIterator ConstIterator;// -------------------------- 1. 迭代器接口 --------------------------// 普通迭代器begin:指向第一个非空桶的头节点Iterator begin() {for (size_t i = 0; i < _tables.size(); ++i) {if (_tables[i] != nullptr) {return Iterator(_tables[i], this);}}return end();  // 空表返回end()}// 普通迭代器end:指向nullptr(表示遍历结束)Iterator end() {return Iterator(nullptr, this);}// const迭代器begin/end:与普通迭代器逻辑一致,仅返回const版本ConstIterator begin() const {for (size_t i = 0; i < _tables.size(); ++i) {if (_tables[i] != nullptr) {return ConstIterator(_tables[i], const_cast(this));}}return end();}ConstIterator end() const {return ConstIterator(nullptr, const_cast(this));}// -------------------------- 2. 构造与析构 --------------------------// 构造函数:初始化桶数组(默认大小为53)HashTable(): _tables(__stl_next_prime(1), nullptr)  // 1的下一个质数是53, _size(0)                               // 初始元素个数为0{}// 析构函数:释放所有节点内存(避免内存泄漏)~HashTable() {for (size_t i = 0; i < _tables.size(); ++i) {Node* pCur = _tables[i];  // 指向当前桶的头节点while (pCur != nullptr) {Node* pNext = pCur->_next;  // 保存下一个节点(避免释放后丢失)delete pCur;                // 释放当前节点pCur = pNext;               // 跳转到下一个节点}_tables[i] = nullptr;  // 桶指针置空(避免野指针)}}// -------------------------- 3. 核心操作:插入 --------------------------// 返回值:pair// - Iterator:指向插入的节点(或已存在的节点)// - bool:true=插入成功(新元素),false=插入失败(元素已存在)pair insert(const T& data) {KeyOfT keyExtractor;  // 键提取仿函数(从T中取K)Hash hashFunc;        // 哈希函数(计算桶索引)// 步骤1:检查元素是否已存在(哈希表键唯一,不允许重复)K key = keyExtractor(data);  // 从插入数据中提取键Iterator it = find(key);     // 查找键是否已存在if (it != end()) {return { it, false };  // 已存在,返回现有节点和false}// 步骤2:负载因子≥1时扩容(避免链表过长,导致查询效率下降)if (_tables.size() <= _size) {size_t newBucketCount = __stl_next_prime(_tables.size() + 1);  // 新桶数为下一个质数vector newTables(newBucketCount, nullptr);              // 创建新桶数组// 步骤2.1:旧节点重新哈希到新桶(扩容后桶数变,索引需重新计算)for (size_t i = 0; i < _tables.size(); ++i) {Node* pCur = _tables[i];while (pCur != nullptr) {Node* pNext = pCur->_next;  // 保存下一个节点// 计算当前节点在新桶中的索引K nodeKey = keyExtractor(pCur->_data);size_t newIdx = hashFunc(nodeKey) % newBucketCount;// 头插法插入新桶(效率O(1),无需遍历链表)pCur->_next = newTables[newIdx];newTables[newIdx] = pCur;pCur = pNext;  // 处理下一个旧节点}_tables[i] = nullptr;  // 旧桶置空(避免野指针)}// 步骤2.2:交换新旧桶数组(旧桶数组会在函数结束后被销毁)_tables.swap(newTables);}// 步骤3:插入新节点(头插法)size_t targetIdx = hashFunc(key) % _tables.size();  // 计算目标桶索引Node* pNewNode = new Node(data);                    // 创建新节点pNewNode->_next = _tables[targetIdx];               // 新节点的next指向桶的头节点_tables[targetIdx] = pNewNode;                      // 桶的头节点更新为新节点_size++;                                            // 元素个数+1return { Iterator(pNewNode, this), true };  // 插入成功,返回新节点迭代器}// -------------------------- 4. 核心操作:查找 --------------------------// 根据键查找,返回迭代器(未找到返回end())Iterator find(const K& key) {Hash hashFunc;        // 哈希函数KeyOfT keyExtractor;  // 键提取仿函数// 步骤1:计算键对应的桶索引size_t targetIdx = hashFunc(key) % _tables.size();Node* pCur = _tables[targetIdx];  // 指向目标桶的头节点// 步骤2:遍历桶内链表,查找匹配的键while (pCur != nullptr) {if (keyExtractor(pCur->_data) == key) {return Iterator(pCur, this);  // 找到,返回节点迭代器}pCur = pCur->_next;  // 未找到,继续遍历下一个节点}return end();  // 遍历完链表仍未找到,返回end()}// -------------------------- 5. 核心操作:删除 --------------------------// 根据键删除,返回是否删除成功(true=成功,false=未找到)bool erase(const K& key) {Hash hashFunc;        // 哈希函数KeyOfT keyExtractor;  // 键提取仿函数// 步骤1:计算键对应的桶索引size_t targetIdx = hashFunc(key) % _tables.size();Node* pCur = _tables[targetIdx];  // 指向目标桶的头节点Node* pPrev = nullptr;            // 前驱节点(用于删除节点)// 步骤2:遍历桶内链表,查找待删除节点while (pCur != nullptr) {if (keyExtractor(pCur->_data) == key) {// 情况1:待删除节点是桶的头节点(前驱为nullptr)if (pPrev == nullptr) {_tables[targetIdx] = pCur->_next;  // 桶的头节点更新为下一个节点}// 情况2:待删除节点是中间节点(前驱非nullptr)else {pPrev->_next = pCur->_next;  // 前驱的next跳过当前节点}delete pCur;  // 释放节点内存_size--;      // 元素个数-1return true;  // 删除成功}pPrev = pCur;     // 前驱节点后移pCur = pCur->_next;  // 当前节点后移}return false;  // 遍历完链表仍未找到,删除失败}
private:size_t _size = 0;               // 有效元素个数vector _tables;          // 桶数组(存储链表头指针)
};

四、上层封装:_map 与_set 的实现

基于底层哈希表,我们只需通过 “键提取仿函数” 和 “存储类型适配”,即可快速封装_map_set—— 这就是通用设计的魅力!

4.1 _set:唯一键集合

_set的核心特性:

  • 存储唯一键(键即值);
  • 键不可修改(避免哈希表索引失效)。

实现思路:

  • 哈希表的存储类型T设为const K(键不可修改);
  • 键提取仿函数直接返回const K(键即数据本身)。
// 命名空间bobo:避免与标准库冲突
namespace bobo {
// _set类:基于开散列哈希表的唯一键集合
template>
class _set {// 键提取仿函数:从存储的const K中提取键(键即数据本身)struct KeyOfT {const K& operator()(const K& data) const {return data;}};
public:// 迭代器复用哈希表的迭代器(_set迭代器不可修改键,故直接用const迭代器逻辑)typedef typename HashTable::Iterator iterator;typedef typename HashTable::ConstIterator const_iterator;// 迭代器接口(直接复用哈希表的begin/end)iterator begin() { return _ht.begin(); }iterator end() { return _ht.end(); }const_iterator begin() const { return _ht.begin(); }const_iterator end() const { return _ht.end(); }// 插入:返回pair(键唯一,重复插入返回false)pair insert(const K& key) {return _ht.insert(key);  // 直接调用哈希表的insert}// 查找:根据键查找,返回迭代器(未找到返回end())iterator find(const K& key) {return _ht.find(key);  // 直接调用哈希表的find}// 删除:根据键删除,返回是否成功bool erase(const K& key) {return _ht.erase(key);  // 直接调用哈希表的erase}
private:// 底层哈希表:键类型K,存储类型const K,键提取仿函数KeyOfTHashTable _ht;
};
}  // namespace bobo

4.2 _map:键值对映射

_map的核心特性:

  • 存储键值对(pair<const K, V>);
  • 键不可修改(firstconst),值可修改(secondconst);
  • 支持[]运算符(访问 / 插入值)。

实现思路:

  • 哈希表的存储类型T设为pair<const K, V>
  • 键提取仿函数返回pairfirst(键);
  • []运算符通过insert实现(插入默认值,返回值的引用)。
namespace bobo {
// _map类:基于开散列哈希表的键值对映射
template>
class _map {// 键提取仿函数:从pair中提取键(返回first)struct KeyOfT {const K& operator()(const pair& data) const {return data.first;  // 键是pair的first成员}};
public:// 迭代器复用哈希表的迭代器(解引用返回pair)typedef typename HashTable, KeyOfT, Hash>::Iterator iterator;typedef typename HashTable, KeyOfT, Hash>::ConstIterator const_iterator;// 迭代器接口(直接复用哈希表的begin/end)iterator begin() { return _ht.begin(); }iterator end() { return _ht.end(); }const_iterator begin() const { return _ht.begin(); }const_iterator end() const { return _ht.end(); }// 插入:返回pair(键唯一,重复插入返回false)pair insert(const pair& kv) {return _ht.insert(kv);  // 直接调用哈希表的insert}// 核心:[]运算符(访问/插入值)V& operator[](const K& key) {// 插入{key, V()}:若键不存在,插入默认值;若存在,返回已有值pair ret = _ht.insert({ key, V() });// 返回值的引用(ret.first是迭代器,解引用是pair,second是值)return ret.first->second;}// 查找:根据键查找,返回迭代器(未找到返回end())iterator find(const K& key) {return _ht.find(key);  // 直接调用哈希表的find}// 删除:根据键删除,返回是否成功bool erase(const K& key) {return _ht.erase(key);  // 直接调用哈希表的erase}
private:// 底层哈希表:键类型K,存储类型pair,键提取仿函数KeyOfTHashTable, KeyOfT, Hash> _ht;
};
}  // namespace bobo

五、实战测试:验证功能完整性

我们编写测试代码,验证_map_set的核心功能(插入、查找、删除、遍历、[]访问),确保代码可运行。

5.1 测试代码

// 测试函数:_set功能测试
void TestSet() {cout << "------------------- TestSet -------------------" << endl;bobo::_set s;// 1. 插入(包含重复键)auto ret1 = s.insert(10);auto ret2 = s.insert(20);auto ret3 = s.insert(10);  // 重复插入cout << "插入10:" << (ret1.second ? "成功" : "失败") << endl;cout << "插入20:" << (ret2.second ? "成功" : "失败") << endl;cout << "重复插入10:" << (ret3.second ? "成功" : "失败") << endl;// 2. 遍历cout << "遍历_set:";for (auto it = s.begin(); it != s.end(); ++it) {cout << *it << " ";}cout << endl;// 3. 查找auto findIt1 = s.find(10);auto findIt2 = s.find(30);cout << "查找10:" << (findIt1 != s.end() ? "存在" : "不存在") << endl;cout << "查找30:" << (findIt2 != s.end() ? "存在" : "不存在") << endl;// 4. 删除bool eraseRet1 = s.erase(10);bool eraseRet2 = s.erase(30);  // 删除不存在的键cout << "删除10:" << (eraseRet1 ? "成功" : "失败") << endl;cout << "删除30:" << (eraseRet2 ? "成功" : "失败") << endl;// 5. 遍历验证删除结果cout << "删除后遍历_set:";for (auto it = s.begin(); it != s.end(); ++it) {cout << *it << " ";}cout << endl << endl;
}
// 测试函数:_map功能测试
void TestMap() {cout << "------------------- TestMap -------------------" << endl;bobo::_map m;// 1. 插入(包含重复键)auto ret1 = m.insert({ "张三", 20 });auto ret2 = m.insert({ "李四", 25 });auto ret3 = m.insert({ "张三", 30 });  // 重复插入cout << "插入{张三,20}:" << (ret1.second ? "成功" : "失败") << endl;cout << "插入{李四,25}:" << (ret2.second ? "成功" : "失败") << endl;cout << "重复插入{张三,30}:" << (ret3.second ? "成功" : "失败") << endl;// 2. []访问/插入m["王五"] = 35;  // 插入新键值对m["李四"] = 28;  // 修改已有值cout << "[]访问王五:" << m["王五"] << endl;cout << "[]修改后李四:" << m["李四"] << endl;// 3. 遍历cout << "遍历_map:";for (auto it = m.begin(); it != m.end(); ++it) {cout << "{" << it->first << ":" << it->second << "} ";}cout << endl;// 4. 查找auto findIt1 = m.find("张三");auto findIt2 = m.find("赵六");cout << "查找张三:" << (findIt1 != m.end() ? "存在" : "不存在") << endl;cout << "查找赵六:" << (findIt2 != m.end() ? "存在" : "不存在") << endl;// 5. 删除bool eraseRet1 = m.erase("张三");bool eraseRet2 = m.erase("赵六");  // 删除不存在的键cout << "删除张三:" << (eraseRet1 ? "成功" : "失败") << endl;cout << "删除赵六:" << (eraseRet2 ? "成功" : "失败") << endl;// 6. 遍历验证删除结果cout << "删除后遍历_map:";for (auto it = m.begin(); it != m.end(); ++it) {cout << "{" << it->first << ":" << it->second << "} ";}cout << endl;
}
// 主函数:执行测试
int main() {TestSet();TestMap();return 0;
}

5.2 测试结果

编译运行代码后,输出如下(符合预期):

plaintext

------------------- TestSet -------------------
插入10:成功
插入20:成功
重复插入10:失败
遍历_set:10 20
查找10:存在
查找30:不存在
删除10:成功
删除30:失败
删除后遍历_set:20
------------------- TestMap -------------------
插入{张三,20}:成功
插入{李四,25}:成功
重复插入{张三,30}:失败
[]访问王五:35
[]修改后李四:28
遍历_map:{张三:20} {李四:28} {王五:35}
查找张三:存在
查找赵六:不存在
删除张三:成功
删除赵六:失败
删除后遍历_map:{李四:28} {王五:35}

六、常见问题与总结

6.1 关键问题解答

  1. 为什么_set的键和_mapfirstconst若允许修改键,会导致键的哈希值变化,原索引失效,哈希表无法找到该元素,因此必须设为const

  2. 迭代器什么时候会失效?扩容时(哈希表重建桶数组,旧节点指针迁移),原迭代器指向的节点可能已被重新哈希到新桶,此时原迭代器失效。删除时,仅被删除节点的迭代器失效,其他迭代器不受影响。

  3. 开散列为什么选负载因子 1 作为扩容阈值?开散列的冲突元素存储在链表中,负载因子 1 表示 “平均每个桶有 1 个元素”,此时链表长度较短,查询效率仍接近 O (1);若阈值过大(如 2),链表过长会导致效率下降。

6.2 总结

本文从底层到上层,完整实现了开散列哈希表与基于它的_map/_set,核心收获:

  1. 通用设计的重要性:通过KeyOfT仿函数和解耦的哈希函数,让哈希表支持不同存储类型(const Kpair<const K, V>);
  2. 迭代器的核心逻辑:哈希表迭代器需处理 “跨桶遍历”,这是区别于其他容器迭代器的关键;
  3. 性能权衡:开散列通过 “桶 + 链表” 平衡空间和时间效率,扩容和重新哈希是保证性能的核心手段。

该实现与 C++ STL 的unordered_map/unordered_set原理一致,只是 STL 还做了更多优化(如桶的负载均衡、异常安全、内存池等)。掌握本文内容,你就能轻松理解 STL 哈希容器的底层逻辑!

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

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

相关文章

学习日报 20250928|React 中实现 “实时检测”:useEffect 依赖项触发机制详解 - 实践

学习日报 20250928|React 中实现 “实时检测”:useEffect 依赖项触发机制详解 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important;…

日志|JavaScript

XML数据没多少标签一大堆事件监听事件种类

UNION类SQL注入步骤总结

UNION类SQL注入步骤总结

MQTT的使用

客户端重连机制public static void Reconnect_Using_Timer(){/** This sample shows how to reconnect when the connection was dropped.* This approach uses a custom Task/Thread which will monitor the connecti…

iOS 26 电耗监测与优化,耗电问题实战 + 多工具 辅助策略

本文聚焦 iOS 26 电耗 /耗电监测 /省电策略 /电量优化,结合媒体实测与 Apple 正式声明,分析 Liquid Glass 界面、Adaptive Power 模式、系统后台任务等对电量的影响,设计详细的电耗监测指标体系与实战流程。很多用户…

详细介绍:[wps_clear]wps清理残余 ——注册表不干净

详细介绍:[wps_clear]wps清理残余 ——注册表不干净2025-10-13 17:02 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; dis…

moectf2025

misc ez_LSB 放入stegsolve.jarbase64解密为:SSTV查询到可以用qsstv程序进行解码来恢复图片。 初次尝试发现附件不能直接放进qsstv,需要删掉下图LIST蓝色部分(生成软件FFmpeg(Lavf57.52.100)的标识)才能被qsstv正…

VRay 6.1 for Rhino 6-8 安装教程与功能详解(含图文步骤)

软件介绍 VRay 6.1 for Rhino 6-8是由Chaos Group推出的专业三维模型渲染插件,专为Rhino 6至8版本设计。该软件集成多平台兼容性,支持Windows与Mac OS系统无缝运行,可实现跨设备数据交换与协同工作。其核心渲染引擎…

网络编程实践笔记_3_阿贝云_免费云服务器_远程操作的一些小提示

git bash 操作记录如果你已经把服务启动了,应该就不用看这里了,不过先给启动起来的做个小提醒: 后台模式 用 nohup gunicorn 启动起来的服务,要额外 kill 掉才会终止,不然还是会一直运行并占用端口的哦。 执行命令…

读书笔记:深入理解 Oracle 的 DATE 类型:存储、计算与最佳实践

我们的文章会在微信公众号IT民工的龙马人生和博客网站( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢! 由于博客中有大量代码,通过页面浏览效果更佳。本文为个人学…

一文读懂:C++ STL 学生与竞赛选手都会遇到的容器默认排序的疑惑

C++ STL 学生与竞赛选手都会遇到的疑惑。 下面我们系统讲清楚:🧩 一、结论速览表:STL 常见数据结构默认排序方向容器类型 默认排序方向 底层实现 比较器默认类型 改为从大到小的写法std::set 从小到大(升序) 红黑…

零基础新手的以太坊质押(Staking)终极科普文章

零基础新手的以太坊质押(Staking)终极科普文章。我们将用最生活化的比喻,揭开它神秘的面纱。不再挖矿,变身“数字银行股东”:一文读懂以太坊质押 想象一下,以前的以太坊就像一个巨大的“数字挖矿游戏”,而今天,…

常见应用案例,AI应用开发流程

AI应用开发流程: 1.分析需求; 2.找对应的模型,分析匹配合适的模型; 3.下载模型,跑数据集测试 4.应用 1 文生图模型: 2 图生文: 图片文本识别,提取文本,识别车牌号: 3.识别表单文本 https://github.com/Paddl…

对数据要求高的On-the-fly

必须是有序序列(Sequence is Mandatory) 不支持无序数据集(如 Zip-NeRF 风格或许多 DeepBlending 场景)。 要求图像按顺序捕获,且连续帧之间必须有足够的重叠(理想情况下超过 2/3 的内容)。 严格的运动要求(Tr…

计算机视觉(opencv)——基于 dlib 的实时摄像头人脸检测 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

【开题答辩全过程】以 springboot+美食电子商城的设计与实现为例,含有答辩的问题和答案

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

程序设计实践训练(Day1) - --YZ-

程序设计实践训练(Day1)Posted on 2025-10-13 16:46 --YZ-- 阅读(0) 评论(0) 收藏 举报程序设计实践训练(Day1) 第一——判断 对任何一个正整数 n,如果它是偶数,那么把它砍掉一半;如果它是奇数,那么把 (3n+…

vmware部署win7,win2008,win2012等系统如何手动安装vmware tools

vmware部署win7,win2008,win2012等系统如何手动安装vmware toolswin10和win11都能自动安装vmware tools,但win7,win2008和win2012必须手动安装,Windows版的vmware toos镜像名称是windows.iso,路径在VMware Works…

2025 年工业 X 光机厂家最新推荐排行榜:聚焦技术领先与市场认可的优质国内企业选购指南X光检查机/食品X光机/异物检测厂家推荐

引言当前工业制造向高端化、智能化加速转型,X 光机作为核心检测设备,在集成电路、新能源电池、汽车制造等领域的作用愈发关键,是保障产品质量安全的 “工业医生”。但随着市场需求激增,X 光机品牌数量大幅增加,产…

【黑马python】基础 4.Python 循环语句 while for range

笔记汇总目录【黑马python】8天python从入门到精通 - 汇总Python 循环语句代码示例参考链接黑马-4.Python 循环语句 01-while循环的基础应用tbd