C++进阶:哈希表实现

目录

一:哈希表的概念

1.1直接定址法

1.2哈希冲突

1.3负载因子

1.4实现哈希函数的方法

1.4.1除法散列法/除留余数法

1.4.2乘法散列法

1.4.3全域散列法

1.5处理哈希冲突

1.5.1开放地址法

线性探测

 二次探测

​编辑 双重散列

 1.5.2链地址法

二.代码实现

2.1开放地址法(线性探测)

2.2链地址法


一:哈希表的概念

哈希(hash)又称散列,是⼀种组织数据的方式。从译名来看,有散乱排列的意思。本质就是通过哈希 函数把关键字Key跟存储位置建立⼀个映射关系,查找时通过这个哈希函数计算出Key存储的位置,进行快速查找。

1.1直接定址法

当关键字的范围比较集中时,直接定址法就是非常简单高效的方法,比如一组关键字都在[0,99]之间, 那么我们开⼀个100个数的数组,每个关键字的值直接就是存储位置的下标。再比如一组关键字值都在 [a,z]的小写字母,那么我们开一个26个数的数组,每个关键字acsii码-ascii码就是存储位置的下标。

比如统计字符出现的第一个唯一字符:

class Solution {
public:int firstUniqChar(string s) {// 每个字⺟的ascii码-'a'的ascii码作为下标映射到count数组,数组中存储出现的次数 int count[26] = { 0 };// 统计次数 for (auto ch : s){count[ch - 'a']++;}for (size_t i = 0; i < s.size(); ++i){if (count[s[i] - 'a'] == 1)return i;}return -1;}
};

1.2哈希冲突

直接定址法的缺点也非常明显,当关键字的范围比较分散时,就很浪费内存甚至内存不够用。假设我们只有数据范围是[0,9999]的N个值,我们要映射到一个M个空间的数组中(一般情况下M>=N),那么 就要借助哈希函数(hash  function)hf,关键字key被放到数组的h(key)位置,这里要注意的是h(key)计算出的值必须在[0,M)之间。这里存在的一个问题就是,两个不同的key可能会映射到同⼀个位置去,这种问题我们叫做哈希冲突, 或者哈希碰撞。理想情况是找出一个好的哈希函数避免冲突,但是实际场景中,冲突是不可避免的, 所以我们尽可能设计出优秀的哈希函数,减少冲突的次数,同时也要去设计出解决冲突的方案。

1.3负载因子

假设哈希表中已经映射存储了N个值,哈希表的大小为M,那么负载因子=N/M ,负载因子有些地方也翻译为载荷因子/装载因子等,他的英文为load  factor。负载因子越大,哈希冲突的概率越高,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低。

1.4实现哈希函数的方法

1.4.1除法散列法/除留余数法

• 除法散列法也叫做除留余数法,顾名思义,假设哈希表的大小为M,那么通过key除以M的余数作为映射位置的下标,也就是哈希函数为:h(key)=key%M。

 • 当使用除法散列法时,要尽量避免M为某些值,如2的冥,10的冥等。如果是2^x ,那么key%2^x本质相当于保留key的后X位,那么后x位相同的值,计算出的哈希值都是⼀样的,就冲突了。如: {63,31}看起来没有关联的值,如果M是16,也就是2^4 ,那么计算出的哈希值都是15,因为63的二进制后8位是00111111,31的二进制后8位是00011111。如果是10^x,就更明显了,保留的都是10进值的后x位,如:{112,12312}.

• 当使用除法散列法时,建议M取不太接近2的整数次冥的⼀个质数(素数)。

• 需要说明的是,实践中也是八仙过海,各显神通,Java的HashMap采用除法散列法时就是2的整数次冥做哈希表的⼤小M,这样的话,就不用取模,而可以直接位运算,相对而言位运算比模更高效一些。但是他不是单纯的去取模,比如M是2^16次方,本质是取后16位,那么用key’= key>>16,然后把key和key' 异或的结果作为哈希值。也就是说我们映射出的值还是在[0,M)范围内,但是尽量让key所有的位都参与计算,这样映射出的哈希值更均匀一些即可。

1.4.2乘法散列法

• 乘法散列法对哈希表大小M没有要求,他的大思路第⼀步:用关键字K乘上常数A(0<A<1),并抽 取出k*A的小数部分。第二步:后再用M乘以k*A的小数部分,再向下取整。

• h(key) = floor(M × ((A × key)%1.0)),其中floor表示对表达式进行下取整,A∈(0,1),这里最重要的是A的值应该如何设定,Knuth认为A = 0.6180339887.... (黄金分割点) 比较好。

• 乘法散列法对哈希表大小M是没有要求的,假设M为1024,key为1234,A=0.6180339887,A*key =762.6539420558,取小数部分为0.6539420558, M×((A×key)%1.0)=0.6539420558*1024= 669.6366651392,那么h(1234)=669。

1.4.3全域散列法

• 如果存在一个恶意的对手,他针对我们提供的散列函数,特意构造出一个发生严重冲突的数据集, 比如,让所有关键字全部落入同一个位置中。这种情况是可以存在的,只要散列函数是公开且确定的,就可以实现此攻击。解决方法自然是见招拆招,给散列函数增加随机性,攻击者就无法找出确 定可以导致最坏情况的数据。这种方法叫做全域散列。

h_{ab}(key)=((a × key + b)%P )%M,P需要选一个足够大的质数,a可以随机选[1,P-1]之间的任意整数,b可以随机选[0,P-1]之间的任意整数,这些函数构成了一个P*(P-1)组全域散列函数组。 假设P=17,M=6,a=3,b=4,则h(8)=5。

1.5处理哈希冲突

1.5.1开放地址法

在开放定址法中所有的元素都放到哈希表里,当一个关键字key用哈希函数计算出的位置冲突了,则按照某种规则找到一个没有存储数据的位置进行存储,开放定址法中负载因子一定是小于的。这里的规则有三种:线性探测、二次探测、双重探测。

线性探测

• 从发生冲突的位置开始,依次线性向后探测,直到寻找到下⼀个没有存储数据的位置为止,如果走到哈希表尾,则回绕到哈希表头的位置。

• h(k)=hash0=key%M, hash0位置冲突了,则线性探测公式为:hc(key,i) = hashi = (hash0 + i) % M, i  =  {1, 2, 3, ..., M − 1} ,因为负载因子小于1, 则最多探测M-1次,一定能找到一个存储key的位置。

• 线性探测的比较简单且容易实现,线性探测的问题假设,hash0位置连续冲突,hash0,hash1, hash2位置已经存储数据了,后续映射到hash0,hash1,hash2,hash3的值都会争夺hash3位 置,这种现象叫做群集/堆积。下面的二次探测可以一定程度改善这个问题。

• 下面演示 {19,30,5,36,13,20,21,12} 等这⼀组值映射到M=11的表中。

 二次探测

• 从发生冲突的位置开始,依次左右按二次方跳跃式探测,直到寻找到下⼀个没有存储数据的位置为止,如果往右走到哈希表尾,则回绕到哈希表头的位置;如果往左走到哈希表头,则回绕到哈希表 尾的位置;

•  h(key) = hash0 =  key % M ,hash0位置冲突了,则二次探测公式为:hc(key,i) = hashi = (hash0 ± i^2 ) % M, i  =  {1, 2, 3, ...,  }

• 二次探测当hashi = (hash0 − i^2 )%M 时,当hashi<0时,需要hashi+=M

 • 下面演示 {19,30,52,63,11,22} 等这⼀组值映射到M=11的表中。

h(19) = 8, h(30) = 8, h(52) = 8, h(63) = 8, h(11) = 0, h(22) = 0 

 双重散列

• 第一个哈希函数计算出的值发生冲突,使用第二个哈希函数计算出一个跟key相关的偏移量值,不断往后探测,直到寻找到下一个没有存储数据的位置为止。

• h(key) = hash0 =  key % M ,hash0位置冲突了,则双重探测公式为:hc(key,i) = hashi = (hash0 +  i ∗ h2 (key)) % M, i  =  {1, 2, 3, ..., M}

• 要求h2 (key) < M且和M互为质数,有两种简单的取值方法:1、当M为2整数冥时, h2 (key) 从[0,M-1]任选一个奇数;2、当M为质数时,h2 (key)  =  key % (M − 1)  +  1

• 保证h2 (key) 与M互质是因为根据固定的偏移量所寻址的所有位置将形成一个群,若最大公约数 p = gcd(M, h1 (key)) > 1,那么所能寻址的位置的个数为M/P < M ,使得对于一个关键字来说无法充分利用整个散列表。举例来说,若初始探查位置为1,偏移量为3,整个散列表大小为12, 那么所能寻址的位置为{1,4,7,10},寻址个数为12/gcd(12, 3) = 4。(也就是说,不是质数,只会在1,4,7,10这几个数里循环,其他数不会取找了。如果是质数还有可能)

• 下面演示 {19,30,52} 等这一组值映射到M=11的表中,设h2 (key)  =  key%10 + 1

 1.5.2链地址法

解决冲突的思路

开放定址法中所有的元素都放到哈希表里,链地址法中所有的数据不再直接存储在哈希表中,哈希表 中存储一个指针,没有数据映射这个位置时,这个指针为空,有多个数据映射到这个位置时,我们把 这些冲突的数据链接成⼀个链表,挂在哈希表这个位置下面,链地址法也叫做拉链法或者哈希桶。

• 下面演示 {19,30,5,36,13,20,21,12,24,96} 等这一组值映射到M=11的表中。

扩容

开放定址法负载因子必须小于1,链地址法的负载因子就没有限制了,可以大于1。负载因子越大,哈希冲突的概率越高,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低;stl中 unordered_xxx的最大负载因子基本控制在1,大于1就扩容,我们下面实现也使用这个方式。

极端场景

如果极端场景下,某个桶特别长怎么办?其实我们可以考虑使用全域散列法,这样就不容易被针对 了。但是假设不是被针对了,用了全域散列法,但是偶然情况下,某个桶很长,查找效率很低怎么 办?这里在Java8的HashMap中当桶的长度超过⼀定阀值(8)时就把链表转换成红黑树。一般情况下, 不断扩容,单个桶很长的场景还是比较少的。 

二.代码实现

2.1开放地址法(线性探测)

enum State
{EXIST,EMPTY,DELETE
};
template<class K, class V>
struct HashDate
{pair<K, V> _kv;State _state = EMPTY;
};
template<class K, class V>
class HashTable
{
private:vector<HashData<K, V>> _tables;size_t _n = 0; // 表中存储数据个数 
};

要注意的是这⾥需要给每个存储值的位置加⼀个状态标识,否则删除⼀些值以后,会影响后面冲突的值的查找。如下图,我们删除30,会导致查找20失败,当我们给每个位置加一个状态标识 {EXIST,EMPTY,DELETE} ,删除30就可以不用删除值,而是把状态改为 DELETE ,那么查找20 时是遇到 EMPTY 才能,就可以找到20。

 h(19) = 8,h(30) = 8,h(5) = 5,h(36) = 3,h(13) = 2,h(20) = 9,h(21) = 10,h(12) = 1

 扩容:

这里我们哈希表负载因子控制在0.7,当负载因子到0.7以后我们就需要扩容了,我们还是按照2倍扩 容,但是同时我们要保持哈希表⼤小是一个质数,第一个是质数,2倍后就不是质数了。那么如何解决 了,一种方案就是上面1.4.1除法散列中我们讲的Java  HashMap的使⽤2的整数冥,但是计算时不能直接取模的改进方法。另外一种方案是sgi版本的哈希表使用的方法,给了一个近似2倍的质数表,每次去 质数表获取扩容后的大小。

inline unsigned long __stl_next_prime(unsigned long n)
{// Note: assumes long is at least 32 bits.static const int __stl_num_primes = 28;static const unsigned long __stl_prime_list[__stl_num_primes] ={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};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;//查找范围内第一个不小于(大于或等于)指定值的元素。const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;
}

Key不能取模的问题: 

当key是string/Date等类型时,key不能取模,那么我们需要给HashTable增加一个仿函数,这个仿函数支持把key转换成一个可以取模的整形,如果key可以转换为整形并且不容易冲突,那么这个仿函数就用默认参数即可,如果这个Key不能转换为整形,我们就需要自己实现⼀个仿函数传给这个参数,实现这个仿函数的要求就是尽量key的每值都参与到计算中,让不同的key转换出的整形值不同。string 做哈希表的key非常常见,所以我们可以考虑把string特化⼀下。

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};
template<>
struct HashFunc<string>
{// 字符串转换成整形,可以把字符ascii码相加即可 // 但是直接相加的话,类似"abcd"和"bcad"这样的字符串计算出是相同的 // 这里我们使用BKDR哈希的思路,用上次的计算结果去乘以一个质数,这个质数一般取31, 131等效果会比较好size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash *= 131;hash += e;}return hash;}
};template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
private:vector<HashData<K, V>> _tables;size_t _n = 0; // 表中存储数据个数 
};

完整代码实现:

namespace lwz
{enum State{EXIST,EMPTY,DELETE};template<class K, class V>struct HashDate{pair<K, V> _kv;State _state = EMPTY;};template<class K>struct HashFunc{size_t operator()(const K& key){return (size_t)key;}};template<>struct HashFunc<string>{// 字符串转换成整形,可以把字符ascii码相加即可 // 但是直接相加的话,类似"abcd"和"bcad"这样的字符串计算出是相同的 // 这里我们使用BKDR哈希的思路,用上次的计算结果去乘以一个质数,这个质数一般取31, 131等效果会比较好size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash *= 131;hash += e;}return hash;}};template<class K, class V,class Hash=HashFunc<K>>class HashTable{public:inline unsigned long __stl_next_prime(unsigned long n){// Note: assumes long is at least 32 bits.static const int __stl_num_primes = 28;static const unsigned long __stl_prime_list[__stl_num_primes] ={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};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;//查找范围内第一个不小于(大于或等于)指定值的元素。const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;}HashTable():_tables(__stl_next_prime(0)), _n(0){}bool Insert(const pair<K, V>& kv){//如果已经存在这个值了,就返回错误if (Find(kv.first))return false;//负载因子大于0.7就扩容if (_n * 10 / _tables.size() >= 7){//新初始化一个对象HashTable<K, V> newHT;newHT._tables.resize(__stl_next_prime(_tables.size()+1));//把原来的值放到新创建的对象里for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST){//这里复用我们正在写的InsertnewHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);}//hash获得值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;}HashDate<K, V>* Find(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state == EXIST&& _tables[hashi]._kv.first == key){return &_tables[hashi];}hashi++;hashi %= _tables.size();}return nullptr;}bool Erase(const K& key){HashDate<K, V>* ret = Find(key);if (ret == nullptr){return false;}else{//删除就直接把状态改了就行ret->_state = DELETE;return true;}}private:vector<HashDate<K,V>> _tables;size_t _n = 0; // 表中存储数据个数 };
}

2.2链地址法

namespace lwz1
{template<class K,class V>struct HashNode{pair<K, V> _kv;HashNode<K,V>* _next;HashNode(const pair<K, V>& kv):_kv(kv), _next(nullptr){}};template<class K>struct HashFunc{size_t operator()(const K& key){return (size_t)key;}};template<>struct HashFunc<string>{size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash *= 131;hash += e;}return hash;}};template<class K,class V,class Hash=HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;inline unsigned long __stl_next_prime(unsigned long n){// Note: assumes long is at least 32 bits.static const int __stl_num_primes = 28;static const unsigned long __stl_prime_list[__stl_num_primes] ={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};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;//查找范围内第一个不小于(大于或等于)指定值的元素。const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;}public:HashTable(){_tables.resize(__stl_next_prime(_tables.size()), nullptr);}~HashTable(){for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = 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()){//创建新的指针数组vector<Node*> newtables(__stl_next_prime(_tables.size()), nullptr);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;// 旧表中节点,挪动新表重新映射的位置 size_t hashi = hs(cur->_kv.first) % newtables.size();// 头插到新表 cur->_next = newtables[hashi];newtables[hashi] = cur;cur = 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* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;}bool Erase(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();//删除前需要记录Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){//删除的是数组里链接的第一个节点if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _tables; // 指针数组 size_t _n = 0; // 表中存储数据个数 };
}

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

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

相关文章

鸿蒙NEXT开发案例:血型遗传计算

【引言】 血型遗传计算器是一个帮助用户根据父母的血型预测子女可能的血型的应用。通过选择父母的血型&#xff0c;应用程序能够快速计算出孩子可能拥有的血型以及不可能拥有的血型。这个过程不仅涉及到了简单的数据处理逻辑&#xff0c;还涉及到UI设计与交互体验的设计。 【…

(十八)JavaWeb后端开发案例——会话/yml/过滤器/拦截器

目录 1.业务逻辑实现 1.1 登录校验技术——会话 1.1.1Cookie 1.1.2session 1.1.3JWT令牌技术 2.参数配置化 3.yml格式配置文件 4.过滤器Filter 5.拦截器Interceptor 1.业务逻辑实现 Day10-02. 案例-部门管理-查询_哔哩哔哩_bilibili //Controller层/*** 新增部门*/Pos…

2024.5 AAAiGLaM:通过邻域分区和生成子图编码对领域知识图谱对齐的大型语言模型进行微调

GLaM: Fine-Tuning Large Language Models for Domain Knowledge Graph Alignment via Neighborhood Partitioning and Generative Subgraph Encoding 问题 如何将特定领域知识图谱直接整合进大语言模型&#xff08;LLM&#xff09;的表示中&#xff0c;以提高其在图数据上自…

amd显卡和nVidia显卡哪个好 amd和英伟达的区别介绍

AMD和英伟达是目前市场上最主要的两大显卡品牌&#xff0c;它们各有自己的特点和优势&#xff0c;也有不同的适用场景和用户群体。那么&#xff0c;AMD显卡和英伟达显卡到底哪个好&#xff1f;它们之间有什么区别&#xff1f;我们又该如何选择呢&#xff1f;本文将从以下几个方…

接口加密了怎么测?

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1、定义加密需求 确定哪些数据需要进行加密。这可以是用户敏感信息、密码、身份验证令牌等。确定使用的加密算法&#xff0c;如对称加密&#xff08;如AES&am…

接口上传视频和oss直传视频到阿里云组件

接口视频上传 <template><div class"component-upload-video"><el-uploadclass"avatar-uploader":action"uploadImgUrl":on-progress"uploadVideoProcess":on-success"handleUploadSuccess":limit"lim…

springboot基于数据挖掘的广州招聘可视化分析系统

摘 要 基于数据挖掘的广州招聘可视化分析系统是一个创新的在线平台&#xff0c;旨在通过深入分析大数据来优化和改善广州地区的招聘流程。系统利用Java语言、MySQL数据库&#xff0c;结合目前流行的 B/S架构&#xff0c;将广州招聘可视化分析管理的各个方面都集中到数据库中&a…

VIM的下载使用与基本指令【入门级别操作】

VIM——超级文本编辑器 在当今时代&#xff0c;功能极其复杂的代码编辑器和集成开发环境&#xff08;IDE&#xff09;有很多。 但如果只想要一个超轻量级的代码编辑器&#xff0c;用于 Unix、C 或其他语言/系统&#xff0c;而不需要那些华而不实的功能&#xff0c;该怎么办呢&…

心情追忆-首页“毒“鸡汤AI自动化

之前&#xff0c;我独自一人开发了一个名为“心情追忆”的小程序&#xff0c;旨在帮助用户记录日常的心情变化及重要时刻。我从项目的构思、设计、前端&#xff08;小程序&#xff09;开发、后端搭建到最终部署。经过一个月的努力&#xff0c;通过群聊分享等方式&#xff0c;用…

开源代码统计工具cloc的简单使用

一.背景 公司之前开发了个小系统&#xff0c;要去申请著作权&#xff0c;需要填写代码数量。应该怎么统计呢&#xff1f;搜索了一下&#xff0c;还是用开源工具cloc吧&#xff01;我的操作系统是windows&#xff0c;代码主要是java项目和vue项目。 二.到哪里找 可以去官方下载…

基于单片机的条形码识别结算设计

本设计基于单片机的条形码辨识与结算系统。该系统主要用于超市、商场等场所的商品结算&#xff0c;实现了在超市内对不同种类商品进行自动识别及自动分类结算的功能。该系统由STM32F103C8T6单片机、摄像头、显示、蜂鸣器报警、按键和电源等多个模块构成。该系统可实现商品自动识…

进程间通信的信号艺术:机制、技术与实战应用深度剖析

目录 1 什么是信号 2 为什么要有信号 3 对于信号的反应 3.1 默认行为 3.2 signal()函数 -- 自定义行为对信号做出反应 3.3 对信号进行忽略 4 信号的产生的类型 4.1 kill命令 4.2 键盘输入产生信号 4.3 系统调用接口 4.3.1 kill() 4.3.2 raise() 函数 4.4 软件条件 …

美畅物联丨JT/T 808 终端设备如何加入畅联云平台

在道路运输行业中&#xff0c;JT/T 808终端设备的应用正变得越来越广泛&#xff0c;把该设备接入畅联云平台&#xff0c;能够达成更高效的车辆管理与监控功能。今天&#xff0c;我们就来探讨一下JT/T 808终端设备接入畅联云平台的步骤与要点。 一、了解畅联云平台接入要求 首先…

【微服务】SpringBoot 整合ELK使用详解

目录 一、前言 二、为什么需要ELK 三、ELK介绍 3.1 什么是elk 3.2 elk工作原理 四、ELK搭建 4.1 搭建es环境 4.1.1 获取es镜像 4.1.2 启动es容器 4.1.3 配置es参数 4.1.4 重启es容器并访问 4.2 搭建kibana 4.2.1 拉取kibana镜像 4.2.2 启动kibana容器 4.2.3 修改…

jenkins的安装(War包安装)

‌Jenkins是一个开源的持续集成工具&#xff0c;基于Java开发&#xff0c;主要用于监控持续的软件版本发布和测试项目。‌ 它提供了一个开放易用的平台&#xff0c;使软件项目能够实现持续集成。Jenkins的功能包括持续的软件版本发布和测试项目&#xff0c;以及监控外部调用执行…

低速接口项目之串口Uart开发(一)——串口UART

本节目录 一、串口UART 二、串口协议 三、串口硬件 四、往期文章链接本节内容 一、串口UART 串口UART,通用异步收发传输器&#xff08;Universal Asynchronnous Receiver / Transmitter&#xff09;,一种异步收发传输器&#xff0c;全双工传输。数据发送时&#xff0c;将并行…

WEB攻防-通用漏洞SQL注入Tamper脚本Base64Jsonmd5等

知识点&#xff1a; 1、数据表现格式类型注入&#xff1b; 2、字符转义绕过-宽字节注入&#xff1b; 3、数字&字符&搜索&编码&加密等&#xff1b; 参考资料&#xff1a; https://www.cnblogs.com/bmjoker/p/9326258.html SQL注入课程体系&#xff1a; 1、…

[Unity]TileMap开发,TileMap地图缝隙问题

环境&#xff1a; windows11 unity 2021.3.14f1c1 tilemap使用的图是美术已经拼接到一起的整图&#xff0c;块与块之间没有留缝隙 问题&#xff1a; TileMap地图直接在Unity中使用时&#xff0c;格子边缘会出现缝隙&#xff0c;移动或缩放地图时较明显。 解决方案&#x…

第75期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

Vue 专属状态管理库Pinia的使用与实践

目录 前言1. 什么是 Pinia&#xff1f;2. Pinia 的安装与基本配置2.1 安装 Pinia2.2 在 Vue 应用中配置 Pinia 3. 使用 Pinia 创建和管理状态3.1 定义一个简单的 Store3.2 在组件中使用 Store 4. Pinia 的高级功能4.1 使用 Getter 简化数据处理4.2 支持异步操作4.3 在服务端渲染…