【C++】精妙的哈希算法
目录
- 一、哈希结构
- 1、哈希概念
- 2、哈希函数
- 3、哈希冲突
- 3.1 闭散列
- 3.2 开散列
- 4、完整代码
一、哈希结构
1、哈希概念
AVL树、红黑树等平衡树搜索效率取决于搜索过程中的比较次数,一般时间复杂度为O(logN),虽然平衡树的搜索效率已经很快,但如果可以不经过任何比较或者常数次的比较后就能搜索到我们要找的元素,会极大的提高效率。
哈希结构,是一种通过特定函数(哈希函数)将关键码映射到表中的一个位置,那么在查找时通过该函数就可以很快的找到该元素。

但是上述的映射方法存在一个问题,就是不同的元素可能会映射到同一个位置,这时就发生了哈希冲突(也叫哈希碰撞),解决哈希冲突,是实现哈希结构的关键。
2、哈希函数
引起哈希冲突的一个原因可能是:哈希函数设计不合理。
哈希函数的设计要保证高效性和可靠性:
- 一致性:确保相同的输入总是产生相同的输出哈希值
- 均匀分布:哈希值应在哈希表的地址空间中尽可能均匀分布,以减少哈希冲突
- 计算效率:哈希函数应简单且计算快速,以便在实际应用中能够快速执行
- 冲突最小化:设计哈希函数时应尽量减少哈希冲突的发生,以提高哈希表的性能
| 常见哈希函数:
哈希函数是哈希表的核心,它决定了如何将关键字映射到哈希地址。
- 直接定制法:取关键字的某个线性函数为散列地址,Hash(Key)=A*Key+B。这种方法简单、均匀,但需要事先知道关键字的分布情况
- 除留余数法:取一个不大于哈希表地址数m的质数p,按照哈希函数Hash(key)=key%p将关键码转换成哈希地址。这种方法实现简单,且当p选择合理时,哈希冲突的概率较低
- 平方取中法:对关键字进行平方运算,然后抽取中间的几位作为哈希地址。这种方法适用于不知道关键字分布情况,且位数不是很大的场景
- 折叠法:将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按哈希表表长取后几位作为哈希地址。这种方法适用于关键字位数较多的情况
此外,还有随机数法、数学分析法等哈希函数设计方法,可以根据具体应用场景选择合适的哈希函数。
哈希函数设计的越好,产生哈希冲突的可能性就越低,但是哈希冲突还是无可避免。
3、哈希冲突
解决哈希冲突的两种常见方法是:闭散列(开放定址法)和开散列(链地址法)。
3.1 闭散列
当发生哈希冲突时,如果哈希表中还有空位置,就把key存放到冲突位置的“下一个”空位置去。找下一个空位置,常见的探测方法有线性探测、二次探测和双重散列等。
| 线性探测: 从发生冲突的位置开始,依次向后探测,直到找到下一个空位置为止。

- 插入
上图中在插入15前,通过哈希函数得到映射位置为5,但是5位置被占了,就依次向后找,在7位置找到了一个空位置将15插入。 - 删除
闭散列解决哈希冲突时,不好随便物理删除某个元素,可以考虑标记的方法来伪删除一个元素。
//每个位置都给标记
enum State
{EXIST,//存在DELETE,//删除EMPTY//空
}
| 线性探测实现:
enum State
{EXIST,EMPTY,DELETE
};template<class K, class V>
struct HashData
{pair<K, V> _kv;State _state = EMPTY;
};template<class K, class V>
class HashTable
{
public:HashTable(){_tables.resize(10);//提前开10个位置} private:vector<HashData<K, V>> _tables;size_t _n = 0;//存储元素个数
};
- 插入
关键码对表的size()取模,不能对capacity()取模,因为哈希表支持[]访问,只能访问下标小于size()的元素。
散列表的载荷因子 = 表中的元素个数 / 表的大小
当载荷因子达到某个临界值,就需要扩容。载荷因子越大,产生冲突的可能性就越大,相反产生冲突的可能性就越小。通常载荷因子应限制在0.7-0.8一下。
不能直接对原表进行扩容,无论是原地扩还是异地扩,都会把原数据拷贝过来。但是扩完容后元素的相对位置可能会发生改变,原本冲突的元素扩完容后就不冲突了,所以直接对原表进行扩容是不行的。

扩容有两种方法:
- 方法一:新建原表两倍大小的
vector,遍历原表的元素重新映射到vector中,再将新建的vector和原表的vector交换。 - 方法二:因为方法一还需要重写一遍映射过程,所以可以直接新建一个哈希表,遍历原表的元素插入到新建的哈希表中,最后交换两个哈希表的
vector,这个方法的好处是新建的哈希表复用了原哈希表的Insert。
方法一我们就不实现了,直接用更好一点的方法二:
bool Insert(const pair<K, V>& kv)
{if (_n * 10 / _tables.size() >= 7)//载荷因子达到一定的值进行扩容{HashTable<K, V> newHT;newHT._tables.resize(2 * _tables.size());for (int i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST){newHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);}size_t hashi = kv.first % _tables.size();//确定映射位置while (_tables[hashi]._state == EXIST){++hashi;hashi %= _tables.size();//防止越界}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;
}
但是现在还有个问题,在上面的代码中要求我们的key可以取模,也就是key只能是无符号整数,如果是浮点数、字符串等上面的代码就行不通,所以还需要想办法将可能出现的浮点数、字符串等类型的key转换为无符号的整型再做映射。
像浮点数等可以直接强转为无符号整型,可以考虑用仿函数解决。字符串一般不能直接强转为无符号整型,我们可以对字符串特殊处理,也就是模版特化,将字符串中字符的ASCII码值加起来作为映射值。
但是这里还有个问题,将字符串中字符的ASCII码值加起来也可能冲突,比如相同的字符按不同的顺序组合起来的字符串。不过好在有专门的字符串哈希函数(字符串哈希函数有好多种,这里使用其中一种:BKDR Hash函数),这里就不做过多介绍了,有兴趣的同学请百度了解。他给出的解决办法是字符每次相加之前+31(31、131、1313、13131…都行)来尽可能减少冲突。
template<class K>
struct HashFunc //key强转为整型
{size_t operator()(const K& key){return (size_t)key;}
};//对string类型特殊处理
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash = hash * 31 + e;}return hash;}
};
- 删除
删除指定的元素,只需要找到该元素的位置,将该位置的状态标记为DELETE即可。
bool Erase(const K& key)
{HashData<K, V>* ret = Find(key);if (ret == nullptr){return false;}else{ret->_state = DELETE;return true;}
}
- 查找
查找过程需要注意的是,当在表中找到被查找的元素时还要判断此位置是否被标记为已删除,因为删除操作我们并没有实际物理上的删除某个元素。
HashData<K, V>* Find(const K& key)
{Hash hs;size_t hashi = hs(key) % _tables.size();while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state != DELETE && _tables[hashi]._kv.first == key){return &_tables[hashi];}++hashi;hashi %= _tables.size();}return nullptr;
}
线性探测的优点是简单好理解,缺点是数据容易堆积,查找时可能需要多次比较。
闭散列 / 开放定址法我们就先实现到这里,它是一种零和博弈,和下面将要介绍的开散列 / 链地址法对比还是稍逊一筹。
3.2 开散列
通过哈希函数计算散列地址,具有相同映射地址的元素归于同一子集合,每一个子集合称为一个哈希桶,各个桶中的元素通过一个单链表链接起来,哈希表中存各链表的头节点。开散列每个桶中存放的都是产生哈希冲突的元素。

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash = hash * 31 + e;}return hash;}
};template<class K, class V>
struct HashNode
{HashNode(const pair<K, V>& kv):_kv(kv),_next(nullptr){}pair<K, V> _kv;HashNode<K, V>* _next;
};template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{typedef HashNode<K, V> Node;
public:HashTable(){_tables.resize(10, nullptr);}~HashTable(){for (int i = 0; i < _tables.size(); i++){Node* pcur = _tables[i];while (pcur){Node* next = pcur->_next;delete pcur;pcur = next;}_tables[i] = nullptr;}}private:vector<Node*> _tables;size_t _n = 0;
};
- 插入
bool Insert(const pair<K, V>& kv)
{size_t hashi = kv.first % _tables.size();//负载因子==1就扩容if (_n == _tables.size()){HashTable<K, V> newHT;newHT._tables.resize(2 * _tables.size(), nullptr);for (int i = 0; i < _tables.size(); i++){Node* pcur = _tables[i];while (pcur){newHT.Insert(pcur->_kv);pcur = pcur->_next;}}_tables.swap(newHT._tables);}Node* newnode = new Node(kv);//头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;
}
上面的扩容过程虽然可行,但是不够好。假如原表中有很多个节点,新建新表扩容后复用Insert就要new很多个节点再插入,这实际上是很有消耗的。因为原节点和新new的节点并无差别,所以可以直接将原表中的节点拿下来头插到新表中,这样就不用再new新节点。
| 优化:
bool Insert(const pair<K, V>& kv)
{size_t hashi = hs(kv.first) % _tables.size();//负载因子==1就扩容if (_n == _tables.size()){vector<Node*> newtables(2 * _tables.size(), nullptr);for (int i = 0; i < _tables.size(); i++){Node* pcur = _tables[i];while (pcur){Node* next = pcur->_next;size_t hashi = pcur->_kv.first % newtables.size();pcur->_next = newtables[hashi];newtables[hashi] = pcur;pcur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}Node* newnode = new Node(kv);//头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;
}
- 删除
学习过链表我们知道,单链表的头删和其他位置的删除需要分开处理,因为其他位置删除节点后要将前后节点链接起来,而单链表的头节点没有前一个节点。
bool Erase(const K& key)
{Hash hs;size_t hashi = hs(key) % _tables.size();Node* pcur = _tables[hashi];Node* prev = nullptr;while (pcur){if (pcur->_kv.first == key){if (prev == nullptr){_tables[hashi] = pcur->_next;}else{prev->_next = pcur->_next;}delete pcur;--_n;return true;}prev = pcur;pcur = pcur->_next;}return false;
}
4、完整代码
namespace open_address
{enum State{EXIST,EMPTY,DELETE};template<class K, class V>struct HashData{pair<K, V> _kv;State _state = EMPTY;};template<class K>struct HashFunc //key强转为整型{size_t operator()(const K& key){return (size_t)key;}};//对string类型特殊处理template<>struct HashFunc<string>{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash = hash * 31 + e;}return hash;}};template<class K, class V, class Hash = HashFunc<K>>class HashTable{public:HashTable(){_tables.resize(10);//提前开10个位置}bool Insert(const pair<K, V>& kv){//去冗余if (Find(kv.first)){return false;}if (_n * 10 / _tables.size() >= 7)//载荷因子达到一定的值进行扩容{HashTable<K, V, Hash> newHT;newHT._tables.resize(2 * _tables.size());for (int i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST){newHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);}Hash hs;size_t hashi = hs(kv.first) % _tables.size();//确定映射位置while (_tables[hashi]._state == EXIST){++hashi;hashi %= _tables.size();//防止越界}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;}HashData<K, V>* Find(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state != DELETE && _tables[hashi]._kv.first == key){return &_tables[hashi];}++hashi;hashi %= _tables.size();}return nullptr;}bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (ret == nullptr){return false;}else{ret->_state = DELETE;return true;}}private:vector<HashData<K, V>> _tables;size_t _n = 0;//存储元素个数};
}namespace close_address
{template<class K>struct HashFunc{size_t operator()(const K& key){return (size_t)key;}};template<>struct HashFunc<string>{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash = hash * 31 + e;}return hash;}};template<class K, class V>struct HashNode{HashNode(const pair<K, V>& kv):_kv(kv), _next(nullptr){}pair<K, V> _kv;HashNode<K, V>* _next;};template<class K, class V, class Hash = HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:HashTable(){_tables.resize(10, nullptr);}~HashTable(){for (int i = 0; i < _tables.size(); i++){Node* pcur = _tables[i];while (pcur){Node* next = pcur->_next;delete pcur;pcur = next;}_tables[i] = nullptr;}}bool Insert(const pair<K, V>& kv){Hash hs;size_t hashi = hs(kv.first) % _tables.size();负载因子==1就扩容//if (_n == _tables.size())//{// HashTable<K, V> newHT;// newHT._tables.resize(2 * _tables.size(), nullptr);// for (int i = 0; i < _tables.size(); i++)// {// Node* pcur = _tables[i];// while (pcur)// {// newHT.Insert(pcur->_kv);// pcur = pcur->_next;// }// }// _tables.swap(newHT._tables);//}//负载因子==1就扩容if (_n == _tables.size()){vector<Node*> newtables(2 * _tables.size(), nullptr);for (int i = 0; i < _tables.size(); i++){Node* pcur = _tables[i];while (pcur){Node* next = pcur->_next;//记录下一个节点size_t hashi = hs(pcur->_kv.first) % newtables.size();//映射新表的相对位置pcur->_next = newtables[hashi];//头插newtables[hashi] = pcur;pcur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}Node* newnode = new Node(kv);//头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}Node* Find(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();Node* pcur = _tables[hashi];while (pcur){if (key == pcur->_kv.first){return pcur;}pcur = pcur->_next;}return nullptr;}bool Erase(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();Node* pcur = _tables[hashi];Node* prev = nullptr;while (pcur){if (pcur->_kv.first == key){if (prev == nullptr){_tables[hashi] = pcur->_next;}else{prev->_next = pcur->_next;}delete pcur;--_n;return true;}prev = pcur;pcur = pcur->_next;}return false;}private:vector<Node*> _tables;size_t _n = 0;};
}
本篇文章的分享就到这里了,如果您觉得在本文有所收获,还请留下您的三连支持哦~
相关文章:
【C++】精妙的哈希算法
🚀个人主页:小羊 🚀所属专栏:C 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 一、哈希结构1、哈希概念2、哈希函数3、哈希冲突3.1 闭散列3.2 开散列 4、完整代码 一、哈希结构 1、哈希概念 A…...
智慧链动青春:国家区块链中心接待北京市十一学校青少年访学探索
以生动科学的方法点燃青少年科学探索欲望是构建未来科技人才梯队的基石。近期国家区块链技术创新中心接待北京市十一学校新生访学,以科普讲座、实操互动的方式让学生在深度思考中感受科学魅力、接触前沿科技,激发学生对区块链、隐私计算和芯片设计制造的…...
利用C++封装鼠标轨迹算法为DLL:游戏行为检测的利器
在现代软件开发中,鼠标轨迹模拟技术因其在自动化测试、游戏脚本编写等领域的广泛应用而备受青睐。本文将介绍如何使用C语言将鼠标轨迹算法封装为DLL(动态链接库),以便在多种编程环境中实现高效调用,同时探讨其在游戏行…...
Qt- QSS风格选择器常用属性选择器样式表盒子
1. 风格设置 Qt 提供了 3 种整体风格,使用 QStyleFactory::keys() 来获取 (windowsvista 、Windows 、Fusion) 可以在 main.cpp 中调用 setStyle 方法对应用程序进行全局风格的设置 int main(int argc, char *argv[]) {QApplication a(arg…...
粤智助自助一体机大厂浮出水面 OBOO鸥柏已成服务终端中坚力量
自助服务查询一体机作为操作自主化便民的重要窗口,OBOO鸥柏自助服务终端机以其显著的技术优化,通过触摸屏或其他交互界面,使用户能够自助服务完成各种操作,如支付、查询信息终端、办理业务,自助查档答应一体化等。为交…...
SpringBoot-application.properties配置
默认配置最终都是映射/关联到某个类 #SPRING CONFIG(ConfigFileApplicationListener) spring.config.name #配置文件名(默认 为 application ) spring.config.location #配置文件的位置 …...
STM32-ADC模数转换
一、概述 ADC(Analog-Digital Converter)模拟-数字转换器 ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁12位逐次逼近型ADC,1us转换时间输入电压范围:0~3.3Vÿ…...
lspci | grep VGA
执行lspci | grep VGA后如下,解释含义 00:0f.0 VGA compatible controller: VMware SVGA II Adapter 0b:00.0 VGA compatible controller: NVIDIA Corporation GA104 [GeForce RTX 3070] (rev a1) 执行 lspci | grep VGA 命令后,您得到了两条输出&#…...
智慧厂区车辆导航解决方案;智慧工厂电子地图应用解决方案;大型工厂内部导航解决方案;智慧工厂可视化地图应用方案
智慧厂区车辆导航解决方案;智慧工厂电子地图应用解决方案 在智慧工业的蓬勃发展背景下,上海懒图科技凭借其室内电子地图技术的深厚积淀,正为智慧工厂物流管理领域注入革新力量。其创新的车辆导航与可视化管理系统,凭借高精度定位…...
决策树C4.5算法详解及实现
C4.5决策树是一种广泛使用的机器学习算法,它用于分类任务。它是在ID3算法的基础上改进的,主要通过生成决策树来构建分类模型。C4.5通过以下步骤工作: 1. 数据集分裂 C4.5通过选择具有最高信息增益率的特征来分裂数据集。信息增益率…...
prompt learning
prompt learning 对于CLIP(如上图所示)而言,对其prompt构造的更改就是在zero shot应用到下游任务的时候对其输入的label text进行一定的更改,比如将“A photo of a{obj}”改为“[V1][V2]…[Vn][Class]”这样可学习的V1-Vn的token…...
适用于 Windows 11 的 5 大数据恢复软件 [免费和付费]
为什么我们需要Windows 11数据恢复软件? 计算机用户经常遇到的一件事就是数据丢失,这种情况随时可能发生。错误地删除重要文件和文件夹可能会非常令人担忧,但幸运的是,有一种方法可以恢复 PC 上丢失的数据。本文将向您展示可用于…...
vue实现获取当前时间并实时显示
以下代码可以实现获取当前时间并实时显示,朋友们直接copy使用即可,希望可以帮助到有需要的朋友们! <template><div class"time">{{ datetimeStr }}</div> </template> <script>export default {data…...
【论文阅读】SRCNN
学习资料 论文题目:Learning a Deep Convolutional Network for Image Super-Resolution(学习深度卷积网络用于图像超分辨率)论文地址:link.springer.com/content/pdf/10.1007/978-3-319-10593-2_13.pdf代码:作者提出的…...
数据结构与算法——Java实现 32.堆
目录 堆 大顶堆 威廉姆斯建堆算法 Floyd建堆算法 Floyd建堆算法复杂度 大顶堆代码实现 人的想法和感受是会随着时间的认知改变而改变, 原来你笃定不会变的事,也会在最后一刻变得释然 —— 24.10.10 堆 堆是基于二叉树实现的数据结构 大顶堆任意一个父节…...
深度学习 .dot()
在 MXNet 中,.dot() 是用于计算两个数组的点积(矩阵乘法)的方法。这个方法适用于一维和二维数组,并返回它们的点积结果。 语法 ndarray1.dot(ndarray2) 参数 ndarray1: 第一个输入数组。ndarray2: 第二个输入数组,…...
idea2024 git merge 时丢失 Merge remote-tracking branch问题
idea2024 git merge 时丢失 Merge remote-tracking branch问题 处理建议 直接修改本地git的配置 git config --global merge.ff false 分析 在 IntelliJ IDEA 中进行 Git merge 操作时,有时你可能会遇到提交历史中丢失 Merge remote-tracking branch 的信息&#…...
pdf怎么删除多余不想要的页面?删除pdf多余页面的多个方法
pdf怎么删除多余不想要的页面?在日常办公或学习中,我们经常会遇到需要处理PDF文件的情况。PDF文件因其格式稳定、不易被篡改的特点而广受青睐,但在编辑方面却相对不如Word等文档灵活。有时,在接收或创建的PDF文件中,可…...
树莓派应用--AI项目实战篇来啦-3.OpenCV 读取写入和显示图像
1. 介绍 在计算机视觉和图像处理领域,读取和显示图像是最基础且常见的操作之一,OpenCV作为一个强大的计算机视觉库,提供了丰富的功能来处理图像数据。 读取、显示和写入图像是图像处理和计算机视觉的基础,即使裁剪、调整大…...
一句话就把HTTPS工作原理讲明白了
号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部 上午好,我的网工朋友。 在当今互联网高度发达的时代,信息安全已成为不容忽视的重要议题。 随着越来越多的个人信息和敏感…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...
