【C++】unordered_map与set的模拟实现
unordered系列map和set,与普通区别
用法几乎相同,键值唯一,区别unordered系列迭代器是单向的并且遍历出来不是有序的。unordered系列在数据规模大且无序的情况下性能更优
底层实现:
map 和 set :基于平衡二叉树(通常是红黑树)实现,元素在树中按有序的方式存储。
unordered_map 和 unordered_set :基于哈希表实现,元素存储在哈希桶中,存储顺序与插入顺序无关。是通过哈希函数将键(或元素)映射到哈希表的桶索引
有序性:
map和set:有序,可按自定义排序规则
unordered:无序,存储顺序取决于哈希函数和哈希桶的分配。
适用场景
map 和 set :适用于需要元素有序的场景,如排序、范围查询等unordered_map 和 unordered_set :适用于需要快速插入和查找的场景,对元素的顺序没有要求。
1.哈希表改造
按照以下思路进行模拟实现
1、改装哈希表(哈希桶)
2、封装map和set
3、实现普通迭代器
4、实现const迭代器
5、解决insert返回值问题 实现operator[]
6、解决key不能修改的问题
节点定义
用一个模板参数T来表示数据,unordered_set为key,map为pair<K,V>,实现了泛型。
1.2增加迭代器
基本模板
总共有6个模板参数,K代表键值,T代表value值类型能实现map和set的泛型,Ptr是指针类型,Ref是引用类型,KeyOfT是用于从值中获取键值的函数,HashFunc是哈希函数将键值映射到地址上去
1.重定义一个self通用迭代器,模板参数为Ptr和Ref可以灵活地定义指针和引用类型,通常用于类的内部实现,引用或返回值。
2.重定义一个具体的迭代器iterator,其中指针类型和引用类型都被写死与T相同,是用于读写访问的迭代器,可以对容器中的元素进行修改。
3.需定义一个哈希表指针,因为自增操作时可以通过该指针方便遍历找到下一个哈希桶的位置。有如下作用:a:提供对哈希表资源的访问。如哈希桶数组 _table、哈希函数 hf 和键提取函数 kot
b: 封装哈希表的实现细节。使哈希表的内部实现可以在不改变迭代器代码的情况下进行修改。
构造函数
1.将哈希表指针定义成const,因为在const迭代器begin和end函数中返回的this指针是const的,普通指针传进来也没问题,因为权限可以缩小不能放大,所以直接定义成const省略掉普通哈希表指针的迭代器构造
2.与set和map模拟实现中类似,一举两得,解决了返回值中非const的迭代器转化成const迭代器的过程。因为iterator的指针和引用参数写死了,就是常量。
自增++操作
self& operator++()
{if (_node->_next){//当前桶还没完,继续往下遍历_node = _node->_next;}else{KeyOfT kot;HashFunc hf;size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();//从下一个位置寻找hashi++;while (hashi < _pht->_table.size()){if (_pht->_table[hashi]){_node = _pht->_table[hashi];return *this;}else{hashi++;}}//找完了没找到哈希桶_node = nullptr;}return *this;
}
1.返回对当前迭代器对象的引用(self&),以便支持连续的自增操作
2.分当前桶中继续往下遍历和查找下一个哈希桶中两种情况
3.查找下一个哈希桶时先用获取键kot函数拿到键值然后用哈希函数hf计算该键的映射哈希值。从下一个哈希桶的位置开始找不为空的哈希桶,循环条件为哈希值索引小于当前已存储桶的大小,若找到不为空哈希桶返回this指针,没找到对当前_node置空再返回this指针
前置声明
由于上图中存在相互包含问题,需要前置声明。
注意:编译器需要完整的类型定义来确定内存布局、调用成员函数和生成正确的机器码。前向声明只能用于指针和引用类型,因为这些类型不需要完整的类型信息。在需要直接实例化对象或调用成员函数时,必须包含完整的类型定义。
所以选择前向声明哈希表指针
- 完整代码
//前置声明
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable;template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
struct HTIterator
{typedef HashNode<T> Node;typedef HTIterator<K, T,Ptr,Ref, KeyOfT, HashFunc> self;typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> iterator;Node* _node;const HashTable<K, T, KeyOfT, HashFunc>* _pht;HTIterator(Node* node,const HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node), _pht(pht){ }// 普通迭代器时,他是拷贝构造// const迭代器时,他是构造HTIterator(const iterator& it):_node(it._node), _pht(it._pht){ }Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}self& operator++(){if (_node->_next){//当前桶还没完,继续往下遍历_node = _node->_next;}else{KeyOfT kot;HashFunc hf;size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();//从下一个位置寻找hashi++;while (hashi < _pht->_table.size()){if (_pht->_table[hashi]){_node = _pht->_table[hashi];return *this;}else{hashi++;}}//找完了没找到哈希桶_node = nullptr;}return *this;}bool operator!=(const self& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}
};
1.3哈希表基本模板
模板参数与模板重定义和迭代器的相同,注意要加上迭代器的友元声明,在迭代器中会访问哈希表中的私有变量
获取迭代器
iterator begin()
{//找第一个桶for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){return iterator(cur, this);}}return iterator(nullptr, this);
}iterator end()
{return iterator(nullptr, this);
}const_iterator begin()const
{//找第一个桶for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){return const_iterator(cur, this);}}return const_iterator(nullptr, this);
}const_iterator end()const
{return const_iterator(nullptr, this);
}
注意迭代器是由当前节点和哈希表指针构成,末尾迭代器返回空指针和this指针。
构造与析构函数
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;}
}
resize开辟和初始化空间为空,由于vector数组中每个位置都存储着链表头指针,自定义类型需要手动释放空间
插入
pair<iterator,bool> Insert(const T&data)
{KeyOfT kot;iterator it=Find(kot(data));if(it!=end()){return make_pair(it,false);}HashFunc hf;//检查扩容,载荷因子到1就扩容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){size_t hashi = hf(kot(data)) % newsize;Node* next = cur->_next;//头插cur->_next = newtable[hashi];newtable[hashi] = cur;//更新节点cur = next;}_table[i] = nullptr;}//交换新旧表_table.swap(newtable);}//插入新节点size_t hashi = hf(kot(data)) % _table.size();Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return make_pair(iterator(newnode,this),true);
}
1.创建一个提取键对象,可以像函数一样调用
2.检查待插入元素是否已存在
3.定义一个哈希函数对象,重载了()也可以像函数一样被调用
4.检查扩容,与原哈希表逻辑相同
5.重新计算哈希值再创建新节点并插入,更新有效值元素,返回一个由迭代器和bool值构成的键值对由于unordered_map的[]重载需要通过insert实现,需要提供对指定键的值的访问,如果键不存在,则需要插入一个默认构造的值。所以insert的返回值要变成键值对
查找
iterator Find(const K& key)
{KeyOfT kot;HashFunc hf;size_t hashi = hf(key) % _table.size();//在该哈希桶中遍历寻找Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur,this);}cur = cur->_next;}return end();
}
与原哈希表区别在于返回值变成迭代器
删除
bool Erase(const K& key)
{HashFunc hf;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];Node* prev = nullptr;while (cur){if (kot(cur->_data) == 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;
}
与原哈希表相同
哈希函数
template<class K>
struct DefaultHashFunc
{size_t operator()(const K& key){//强转成返回值类型return (size_t)key;}
};
//模板特化
template<>
struct DefaultHashFunc<string>
{size_t operator()(const string& str){// BKDR,string的哈希算法size_t hash = 0;for (auto ch : str){hash *= 131;hash += ch;}return hash;}
};
与原哈希表相同
- 代码
template<class K>
struct DefaultHashFunc
{size_t operator()(const K& key){//强转成返回值类型return (size_t)key;}
};
//模板特化
template<>
struct DefaultHashFunc<string>
{size_t operator()(const string& str){// BKDR,string的哈希算法size_t hash = 0;for (auto ch : str){hash *= 131;hash += ch;}return hash;}
};namespace hash_bucket
{template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T&data):_data(data), _next(nullptr){}};//前置声明template<class K, class T, class KeyOfT, class HashFunc>class HashTable;template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>struct HTIterator{typedef HashNode<T> Node;typedef HTIterator<K, T,Ptr,Ref, KeyOfT, HashFunc> self;typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> iterator;Node* _node;const HashTable<K, T, KeyOfT, HashFunc>* _pht;HTIterator(Node* node,const HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node), _pht(pht){ }// 普通迭代器时,他是拷贝构造// const迭代器时,他是构造HTIterator(const iterator& it):_node(it._node), _pht(it._pht){ }Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}self& operator++(){if (_node->_next){//当前桶还没完,继续往下遍历_node = _node->_next;}else{KeyOfT kot;HashFunc hf;size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();//从下一个位置寻找hashi++;while (hashi < _pht->_table.size()){if (_pht->_table[hashi]){_node = _pht->_table[hashi];return *this;}else{hashi++;}}//找完了没找到哈希桶_node = nullptr;}return *this;}bool operator!=(const self& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}};// set -> hash_bucket::HashTable<K, K> _ht;// map -> hash_bucket::HashTable<K, pair<K, V>> _ht;template<class K, class T,class KeyOfT, class HashFunc = DefaultHashFunc<K>>class HashTable{typedef HashNode<T> Node;//友元声明template<class K, class T,class Ptr,class Ref, class KeyOfT, class HashFunc>friend struct HTIterator;public:typedef HTIterator<K, T,T*,T&, KeyOfT, HashFunc> iterator;typedef HTIterator<K, T,const T*,const T&, KeyOfT, HashFunc> const_iterator;iterator begin(){//找第一个桶for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){return iterator(cur, this);}}return iterator(nullptr, this);}iterator end(){return iterator(nullptr, this);}const_iterator begin()const{//找第一个桶for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){return const_iterator(cur, this);}}return const_iterator(nullptr, this);}const_iterator end()const{return const_iterator(nullptr, this);}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;}}pair<iterator,bool> Insert(const T&data){KeyOfT kot;iterator it=Find(kot(data));if(it!=end()){return make_pair(it,false);}HashFunc hf;//检查扩容,载荷因子到1就扩容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){size_t hashi = hf(kot(data)) % newsize;Node* next = cur->_next;//头插cur->_next = newtable[hashi];newtable[hashi] = cur;//更新节点cur = next;}_table[i] = nullptr;}//交换新旧表_table.swap(newtable);}//插入新节点size_t hashi = hf(kot(data)) % _table.size();Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return make_pair(iterator(newnode,this),true);}iterator Find(const K& key){KeyOfT kot;HashFunc hf;size_t hashi = hf(key) % _table.size();//在该哈希桶中遍历寻找Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur,this);}cur = cur->_next;}return end();}bool Erase(const K& key){HashFunc hf;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];Node* prev = nullptr;while (cur){if (kot(cur->_data) == 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;}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;}private:vector<Node*> _table;//指针数组size_t _n = 0;};
}
- 测试代码
#include<iostream>
using namespace std;
#include"HashTable.h"#include"UnOrderedSet.h"
#include"UnOrderedMap.h"int main(){ee::unordered_set<int> us;us.insert(3);us.insert(1);us.insert(3);us.insert(4);us.insert(5);us.insert(0);ee::unordered_set<int>::iterator it = us.begin();while (it != us.end()){//*it += 10;不能修改keycout << *it << " ";++it;}cout << endl;ee::unordered_map<string, string> dict;dict.insert(make_pair("sort", "排序"));dict.insert(make_pair("left", "左边"));dict.insert(make_pair("insert", "插入"));dict.insert(make_pair("sort", "xxx"));ee::unordered_map<string, string>::iterator dit = dict.begin();while (dit != dict.end()){//不能修改key//dit->first += 'e';dit->second += 'e';cout << dit->first << ":" << dit->second << endl;++dit;}dict["sort"] = "排序";dict["insert"] = "插入";dict["string"] = "字符串";dict["left"];for (auto& kv : dict){cout << kv.first << ":" << kv.second << endl;}return 0;}
2.封装unordered_set
#pragma once
namespace ee
{template<class K>struct unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;//迭代器底层都是const,直接定义成const即可const_iterator begin()const{return _ht.begin();}const_iterator end()const{return _ht.end();}pair<const_iterator,bool> insert(const K& key){//return _ht.Insert(key);编译器严格检查下可能报错pair<typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);return pair<const_iterator, bool>(ret.first, ret.second);}private:hash_bucket::HashTable<K, K, SetKeyOfT> _ht;};
}
迭代器底层都是const_iterator(确保键值不能修改),会造成返回值类型不匹配,与map和set的模拟实现一样,这里解决办法相同,先用哈希表中迭代器接收插入后的返回值普通迭代器,利用在哈希表中写的支持非const转化为const迭代器的函数来构造出const迭代器并返回。
3.封装unordered_map
#pragma once
namespace ee
{template<class K,class V>struct unordered_map{struct MapKeyOfT{const K& operator()(const pair<K,V>& kv){return kv.first;}};public:typedef typename hash_bucket::HashTable<K, pair<const K,V>, MapKeyOfT>::iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;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<iterator,bool> insert(const pair<K,V>& kv){return _ht.Insert(kv);}V& operator[](const K&key){pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}private:hash_bucket::HashTable<K, pair<const K,V>, MapKeyOfT> _ht;};
}
这里插入就不会有set的迭代器返回值类型不匹配的问题,常量迭代器和普通迭代器底层就是其本身。
[]重载:
返回值 :返回类型为 V&,即键对应的值的引用。如果键不存在,则插入一个默认构造的值并返回其引用。
Insert 方法 :尝试将键值对插入哈希表。如果键已存在,Insert 返回一个 pair,其中迭代器指向已存在的键值对,bool 值为 false;如果键不存在,Insert 插入键值对并返回一个 pair,其中迭代器指向新插入的键值对,bool 值为 true。
返回值引用 :ret.first->second 返回键值对中值的引用。无论键是否已存在,都能通过这个引用访问或修改值。
相关文章:
【C++】unordered_map与set的模拟实现
unordered系列map和set,与普通区别 用法几乎相同,键值唯一,区别unordered系列迭代器是单向的并且遍历出来不是有序的。unordered系列在数据规模大且无序的情况下性能更优 底层实现: map 和 set :基于平衡二叉树&…...
老旧设备升级利器:Modbus TCP转 Profinet让能效监控更智能
在工业自动化领域,ModbusTCP和Profinet是两种常见的通讯协议。Profinet是西门子公司推出的基于以太网的实时工业以太网标准,而Modbus则是由施耐德电气提出的全球首个真正开放的、应用于电子控制器上的现场总线协议。这两种协议各有各的优点,但…...
编译原理--期末复习
本文是我学习以下博主视频所作的笔记,写的不够清晰,建议大家直接去看这些博主的视频,他/她们讲得非常好: 基础知识概念: 1.【【编译原理】期末复习 零基础自学】,资料 2.【编译原理—混子速成期末保过】&…...
软件工程各种图总结
目录 1.数据流图 2.N-S盒图 3.程序流程图 4.UML图 UML用例图 UML状态图 UML时序图 5.E-R图 首先要先了解整个软件生命周期: 通常包含以下五个阶段:需求分析-》设计-》编码 -》测试-》运行和维护。 软件工程中应用到的图全部有:系统…...
Go 与 Gin 搭建简易 Postman:实现基础 HTTP 拨测的详细指南
Go 与 Gin 搭建简易 Postman:实现基础 HTTP 拨测的详细指南 文章目录 Go 与 Gin 搭建简易 Postman:实现基础 HTTP 拨测的详细指南项目简介代码结构各部分代码功能说明: 代码实现:main.go代码解释 handlers/probe.go代码解释 probe…...
层次原理图
层次原理图简介 层次原理图(Hierarchical Schematic)是一种常用于电子工程与系统设计的可视化工具,通过分层结构将复杂系统分解为多个可管理的子模块。它如同“设计蓝图”,以树状结构呈现整体与局部的关系:顶层展现系…...
嵌入式硬件篇---拓展板
文章目录 前言 前言 本文简单介绍了拓展板的原理以及使用。...
Redis的主从架构
主从模式 全量同步 首先主从同步过程第一步 会先比较replication id 判断是否是第一次同步假设为第一次同步 那么就会 启动bgsave异步生成RDB 同时fork子进程记录生成期间的新数据发送RDB给从节点 清空本地数据写入RDB 增量同步 对比ReplicationID不同因此选择增量同步在Rep…...
IIS入门指南:原理、部署与实战
引言:Web服务的基石 在Windows Server机房中,超过35%的企业级网站运行在IIS(Internet Information Services)之上。作为微软生态的核心Web服务器,IIS不仅支撑着ASP.NET应用的运行,更是Windows Server系统管…...
【上位机——WPF】布局控件
布局控件 常用布局控件Panel基类Grid(网格)UniformGrid(均匀分布)StackPanel(堆积面板)WrapPanel(换行面板)DockerPanel(停靠面板)Canvas(画布布局)Border(边框)GridSplitter(分割窗口)常用布局控件 Grid:网格,根据自定义行和列来设置控件的布局StackPanel:栈式面板,包含的…...
使用 C# 入门深度学习:线性代数详细讲解
在深度学习的领域中,线性代数是基础数学工具之一。无论是神经网络的训练过程,还是数据的预处理和特征提取,线性代数的知识都无处不在。掌握线性代数的核心概念,对于理解和实现深度学习算法至关重要。在本篇文章中,我们…...
操作系统之EXT文件系统
1.理解硬件 1.1磁盘、服务器、机柜、机房 机械磁盘是计算机中唯一的一个机械设备 磁盘--- 外设慢容量大,价格便宜 1.1.1光盘 1.1.2服务器 1.1.3机房 1.2磁盘的物理结构 1.3磁盘的存储结构 一个盘片又两个面 每个面都有一个磁头 磁头沿着盘面的半径移动 1.3.1…...
继MCP、A2A之上的“AG-UI”协议横空出世,人机交互迈入新纪元
第一章:AI交互的进化与挑战 1.1 从命令行到智能交互 人工智能的发展历程中,人机交互的方式经历了多次变革。早期的AI系统依赖命令行输入,用户需通过特定指令与机器沟通。随着自然语言处理技术的进步,语音助手和聊天机器人逐渐普…...
Java大厂面试:从Web框架到微服务技术的场景化提问与解析
Java大厂面试:从Web框架到微服务技术的场景化提问与解析 场景: 某知名互联网大厂的面试现场。面试官一脸严肃,对面坐着搞笑的程序员谢飞机。以下是他们的对话: 第一轮:Web框架基础与数据库操作 面试官:谢…...
最新缺陷检测模型:EPSC-YOLO(YOLOV9改进)
目录 引言:工业缺陷检测的挑战与突破 一、EPSC-YOLO整体架构解析 二、核心模块技术解析 1. EMA多尺度注意力模块:让模型"看得更全面" 2. PyConv金字塔卷积:多尺度特征提取利器 3. CISBA模块:通道-空间注意力再进化 4. Soft-NMS:更智能的重叠框处理 三、实…...
leetcode hot100刷题日记——2.字母异位词分组
涉及知识点:vector、哈希表 解答我的解答的时间复杂度分析我的解答的空间复杂度分析复习:排序算法的时间复杂度 和第一题需要的知识点相同,所以知识点复习可见 link1《leetcode hot100刷题日记——1.两数之和》 解题思路:是字母异位词的字符…...
elementUI 单选框存在多个互斥的选项中选择的场景
使用 el-radio-group 来使用单选框组,代码如下: <el-radio-group input"valueChangeHandler" v-model"featureForm.type"><el-radio name"feature" label"feature">业务对象</el-radio><…...
基于区块链技术的智能汽车诊断与性能分析
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界…...
基于区块链技术的供应链溯源系统:重塑信任与透明度
在当今全球化的商业环境中,供应链的复杂性不断增加,产品从原材料采购到最终交付消费者手中的过程涉及多个环节和众多参与者。然而,传统供应链管理面临着诸多挑战,如信息不透明、数据易篡改、追溯困难等,这些挑战不仅影…...
基于OpenCV的实时文档扫描与矫正技术
文章目录 引言一、系统概述二、核心代码解析1. 导入必要库2. 辅助函数定义3. 坐标点排序函数4. 透视变换函数5. 主程序流程 三、完整代码四、结语 引言 在日常工作和学习中,我们经常需要将纸质文档数字化。手动拍摄文档照片常常会出现角度倾斜、透视变形等问题&…...
基于STM32F103与Marvell88W8686的WIFI无线监控视频传输系统研发(论文)
基于STM32F103与Marvell88W8686的WIFI无线监控视频传输系统研发 中文摘要 在当今社会信息化进程不断加速的时代背景下,众多领域对于监控系统的需求日益增长,像车内安全监控、电梯运行监控等场景都离不开监控系统的支持。过去,不少领域普遍采用…...
华为云Astro中各种变量与参数的区别与用法
目录 🧠 华为云 Astro 各类变量与参数详解 🧩 一、变量与参数的核心作用是什么? 🖼️ 二、整体分类与结构图 📘 三、逐一详细解析 + 类比说明 + 使用建议 🔹 1. 输入参数(Input Parameter) 🔹 2. 输出参数(Output Parameter) 🔹 3. 变量(本地变量)…...
数字人技术的核心:AI与动作捕捉的双引擎驱动(210)
**摘要:**数字人技术从静态建模迈向动态交互,AI与动作捕捉技术的深度融合推动其智能化发展。尽管面临表情僵硬、动作脱节、交互机械等技术瓶颈,但通过多模态融合技术、轻量化动捕方案等创新,数字人正逐步实现自然交互与情感表达。…...
华为云Astro轻应用创建业务对象(BO)的概念梳理
目录 一、业务对象(BO)是什么?——【详细概念解释】 二、形象理解业务对象(BO) 🍱 类比方式: 📦 举个具体例子:以做一个“智能烟雾报警系统”应用 三、为什么使用BO很重要? 四、小结: 一、业务对象(BO)是什么?——【详细概念解释】 在华为云Astro轻应用…...
MySQL开发规范
目录 一、建表规约 二、索引规约 三、SQL语句 四、 ORM映射 一、建表规约 强制: 1、表达是与否概念的字段,必须使用is_xxx的方式命名(PoJo中不加is前缀),数据类型是unsigned tinyint(1表示是…...
K8s入门教程(一)
Kubernetes(K8s)入门教程:从零开始掌握容器编排 目录 Kubernetes(K8s)入门教程:从零开始掌握容器编排 1. Kubernetes 简介 1.1 什么是 Kubernetes? 1.2 核心功能 2. 环境搭建与 Minikube 安装 2.1 安装 Minikube 安装步骤(以 macOS 为例): 安装 kubectl(Kub…...
k8s备份namespace
在 Kubernetes 中备份 Namespace 有多种方法,以下是几种常见的备份方式: 1.使用 kubectl 命令备份 通过 kubectl 命令可以导出指定 Namespace 中的资源,生成 YAML 文件进行备份。 备份所有资源: kubectl -n <namespace> ge…...
前端动画库 Anime.js 的V4 版本,兼容 Vue、React
前端动画库 Anime.js 更新了 V4 版本,并对其官网进行了全面更新,增加了许多令人惊艳的效果,尤其是时间轴动画效果,让开发者可以更精确地控制动画节奏。 这一版本的发布不仅带来了全新的模块化 API 和显著的性能提升,还…...
OpenHarmony外设驱动使用 (四),Face_auth
OpenHarmony外设驱动使用 (四) Face_auth 概述 功能简介 人脸识别功能是端侧设备不可或缺的一部分,为设备提供一种用户认证能力,可应用于设备解锁、支付、应用登录等身份认证场景。它是基于人的脸部特征信息进行身份识别的一种…...
【Java ee初阶】jvm(1)
一、JVM Java虚拟机 面试中相关的问题有三块: 1.JVM内存区域划分 2.JVM的类加载机制 3.JVM的垃圾回收机制 JDK、JRE 和 JVM 的关系 JDK(Java Development Kit)是 Java 开发工具包,包含了编写、编译和调试 Java 程序所需的所…...
【Java ee初阶】jvm(2)
类加载机制: JVM从最开始的读取.class文件,到最终构造完成 类 对象的整个过程,也就是把 类 从硬盘 加载到内存中的机制。 Java的类加载机制主要分为五个步骤:加载、验证、准备、解析和初始化。 步骤一 加载(Loading…...
Django 项目创建全攻略
目录 一、环境准备 1. 安装 Python 2. 安装虚拟环境(可选但推荐) 3. 安装 Django 二、创建 Django 项目 1. 使用命令创建项目 2. 运行开发服务器 三、创建 Django 应用 1. 创建应用 2. 注册应用 四、配置项目 1. 数据…...
windows11 安装好后右键没有 git bash 命令
win键 R 键,输出 regedit,打开注册表 找到 \HKEY_CLASSES_ROOT\Directory\Background\shell 新建项 git-bash 然后在 git-bash 下在新建项 Command,默认值设为 "C:\Program Files\Git\git-bash.exe" --cd"%v." 在 …...
Java八股文——Java基础篇
目录 1、你是怎样理解OOP面向对象2、重载和重写的区别3、接口与抽象类的区别4、深拷贝与浅拷贝的理解5、sleep和wait区别主要区别 6、什么是自动拆装箱,int和Integer有什么区别自动拆装箱int和Integer的区别Integer缓存机制 7、和equals区别String特殊情况 8、Strin…...
蓝桥杯19682 完全背包
问题描述 有 N 件物品和一个体积为 M 的背包。第 i 个物品的体积为 vi,价值为 wi。每件物品可以使用无限次。 请问可以通过什么样的方式选择物品,使得物品总体积不超过 M 的情况下总价值最大,输出这个最大价值即可。 输入格式 第一行…...
2025年- H27-Lc135- 239.滑动窗口最大值(自定义双端队列)---java版
1.题目描述 2.思路 (1)双端队列可以移除最左边的元素,也可以移除最右边的元素(两端移除) (2)在最右边插入元素(右边加入) (3)队列单调性…...
EKS 工作节点的集群网络架构
AWS EKS(弹性 Kubernetes 服务)是亚马逊提供的托管 Kubernetes 服务,一旦配置完成,即可像变魔术一样运行。但这通常是 EKS 的默认设置。如果您打算根据组织的设计、合规性标准和隐私要求进行自定义,该怎么办࿱…...
【技海登峰】Kafka漫谈系列(十一)SpringBoot整合Kafka之消费者Consumer
【技海登峰】Kafka漫谈系列(十一)SpringBoot整合Kafka之消费者Consumer spring-kafka官方文档: https://docs.spring.io/spring-kafka/docs/2.8.10/reference/pdf/spring-kafka-reference.pdf KafkaTemplate API: https://docs.spring.io/spring-kafka/api/org/springframe…...
Python字符串格式化(一):三种经典格式化方法
文章目录 一、% operator:C语言风格的初代格式化方案(Python 2.0引入)1. 语法核心:占位符与类型码2. 进阶用法:格式修饰符3. 致命缺陷:类型严格匹配的陷阱4. 适用场景:旧代码维护的兼容性选择 二…...
浅谈无服务器WebSocket的优势
实际上,一个实用的解决方案是将构建业务关键型实时平台的复杂性卸载到专门的云服务中。 完全托管的无服务器 WebSocket 解决方案为事件驱动的消息传递提供了基础结构;它使底层基础设施成为一种商品。客户端使用提供程序服务发送/接收低延迟消息,并专注于…...
10.7 LangChain v0.3架构大升级:模块化设计+多阶段混合检索,开发效率飙升3倍!
LangChain v0.3 技术生态与未来发展 关键词:LangChain Chains, Agents 架构, Retrieval Strategy, LangGraph, 模块化设计 3. LangChain 项目:Chains, Agents, Retrieval Strategy LangChain v0.3 通过 Chains-Agents-Retrieval 三位一体的技术栈,构建起完整的大模型应用开…...
GLPK(GNU线性规划工具包)中建模语言MathProg的使用
GNU MathProg是一种用于描述线性数学规划模型的建模语言。用GNU MathProg语言编写的模型描述由一组语句和数据块组成。 在MathProg中,模型以集合、参数、变量、约束和目标(sets, parameters, variables, constraints, objectives称为模型对象)的形式进行描述。 在Ma…...
系统思考:IT企业项目困境分析
最近遇到一家快速发展的IT技术公司,遭遇了项目进度滞后、团队沟通不畅和资源分配不合理等一系列挑战。虽然他们拥有一支技术强大的团队,但在项目管理和团队协作上却显得力不从心。结果,多个项目超预算、交期延迟,客户满意度直线下…...
计算机网络 - 2.基础协议
1.TCP协议 1.TCP(Transmission Control Protocol):传输控制协议2.TCP协议是一种面向连接的、可靠的、 基于字节流的传输层通信协议 1.面向连接:两个使用TCP协议的应用(通常一个客户和一个服务器)在彼此交换数据包之前必须先建立一个TCP连接2.可靠的 1.数据传输之前都要建立…...
go语法大赏
前些日子单机房稳定性下降,找了好一会才找到真正的原因。这里面涉及到不少go语法细节,正好大家一起看一下。 一、仿真代码 这是仿真之后的代码 package mainimport ("fmt""go.uber.org/atomic""time" )type StopSignal…...
运行vscode编辑器源码
距离上次二次开发vscode已经是三年前的事了,当时是1.60.0版本,目前vscode已升级到了1.99.2版本,里面改动很大,最近下载下来了新版源码跑起来看看 准备node、python 源码里面node版本做了限制 2025-01-27 09:53:00.450 [info] Fo…...
ShenNiusModularity项目源码学习(26:ShenNius.Admin.Mvc项目分析-11)
本文学习并分析ShenNiusModularity项目中商城系统模块的小程序用户页面、用户收货地址页面。 1、小程序用户页面 小程序用户页面用于检索、浏览使用商城系统的用户数据(保存在shop_appuser表内,系统用户保存在sys_user表内),该页…...
C#中的成员常量:编译时的静态魔法
在C#编程中,常量(const)是一个强大而特殊的语言特性,特别是当它们作为类的成员时。本文将深入探讨成员常量的特性、使用场景以及与静态量的区别。 成员常量的基本特性 成员常量是声明在类内部的常量,具有以下核心特点: 声明位置…...
C# 深入理解类(成员常量)
成员常量 成员常量类似前一章所述的局部常量,只是它们被声明在类声明中而不是方法内,如下面的 示例: 与局部常量类似,用于初始化成员肯量的值在编译时必须是可计算的,而且通常是一个预定 义简单类型或由它们组成的表达…...
服务端HttpServletRequest、HttpServletResponse、HttpSession
一、概述 在JavaWeb 开发中,获取客户端传递的参数至关重要。http请求是客户端向服务端发起数据传输协议,主要包含包含请求行、请求头、空行和请求体四个部分,在这四部分中分别携带客户端传递到服务端的数据。常见的http请求方式有get、post、…...