C++笔记---哈希表

1. 哈希的概念

哈希(hash)又称散列,是一种组织数据的方式。从译名来看,有散乱排列的意思。

本质就是通过哈希函数把关键字Key跟存储位置建立一个映射关系,查找时通过这个哈希函数计算出Key存储的位置,进行快速查找

STL中的unordered_set和unordered_map的底层就是对哈希表的封装。

图书馆中的书就类似于使用哈希的方式进行存储的,当你在图书馆找书时,你通过想找的书的类型(key)判断出其所在的分区(哈希函数计算出地址),就可以很快地将搜索范围锁定到一块区域。

在图书馆找书本来是一件大海捞针似的事,但是由于书是按分区存放的(书的类型与存放地址产生了联系),我们就可以很迅速地找到想要的书。

在数据结构中,存储位置通常是指存储数据的数组的下标。

所以,支持存储到哈希表中的key一定是整形或可以发生隐式类型转换为整形。

如果需要存储的类型不支持隐式类型转换为整形,或者在转换时会丢失大量数据,我们就需要自己实现哈希函数。

下面介绍一点学习哈希的必备概念:

1.1 直接定址法

当关键字范围比较集中时,直接定址法是最简单高效的一种哈希方式,常常会在算法题中使用到这种思想。

比如一组关键字都在[0,99]之间,那么我们开一个100个数的数组,每个关键字的值直接就是存储位置的下标。再比如一组关键字值都在[a,z]的小写字母,那么我们开一个26个数的数组,每个关键字acsii码-a ascii码就是存储位置的下标。

也就是说直接定址法本质就是用关键字计算出一个绝对位置或者相对位置。

在计数排序中,这一方法就得到了应用,在下面这道oj题中也有妙用:. - 力扣(LeetCode)

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;}
}

直接定址法的缺点也非常明显,当关键字的范围比较分散时,就很浪费内存甚至内存不够用。

假设我们只有数据范围是[0, 9999]的N个值,我们要映射到一个M个空间的数组中(一般情况下M >= N),那么就要借助哈希函数(hash function)hf,关键字key被放到数组的hf(key)位置。

这里要注意的是hf(key)计算出的值必须在[0, M)之间。

1.2 哈希冲突

这里存在的一个问题就是,两个不同的key可能会映射到同一个位置去,这种问题我们叫做哈希冲突,或者哈希碰撞。

理想情况是找出一个好的哈希函数避免冲突,但是实际场景中,冲突是不可避免的。

所以我们尽可能设计出优秀的哈希函数,减少冲突的次数,同时也要去设计出解决冲突的方案。

1.3 负载因子

假设哈希表中已经映射存储了N个值,哈希表的大小为M,那么"负载因子 = N/M",负载因子在有些地方也翻译为载荷因子/装载因子等,他的英文为load factor。

负载因子越大,哈希冲突的概率越高,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低。

2. 哈希函数

一个好的哈希函数应该让样本空间中的关键字被等概率的均匀的散列分布到哈希表的M个空间中,以减少哈希冲突,实际中却很难做到,但我们要尽量往这个方向去考量设计。

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

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

当使用除法散列法时,要尽量避免M为某些值,如2的幂,10的幂等,而尽量取素数。

假如M为2^{n},在32位环境下对key进行取模运算之后,相当于key的二进制的前32-n位被舍弃了,只保留了零头。这使得原本不相同的数只需要零头相同就能计算出相同的哈希值,大大增加了冲突概率。

使用素数作为哈希表的数组空间长度是为了利用素数的数学特性来优化哈希函数的性能,减少碰撞,并提高整体的数据分布均匀性。

尽管在某些情况下,非素数的哈希表也可以表现良好,尤其是当使用高质量的哈希函数时,但素数仍然是一个经典和广泛推荐的做法。

说到使用高质量哈希函数的非素数哈希表,这里就不得不提到Java中的HashMap。

Java的HashMap采用除法散列法时就是2的整数次幂做哈希表的大小M,这样玩的话,就不用取模,而可以直接位运算,相对而言位运算比模更高效一些。但是他不是单纯的去取模,比如M是2^16次方,本质是取后16位,那么用key' = key>>16,然后把key和key' 异或的结果作为哈希值。也就是说我们映射出的值还是在[0,M)范围内,但是尽量让key所有的位都参与计算,任何一位的不同都有可能导致哈希值的不同,这样映射出的哈希值就会更加多样,均匀一些。

2.2 乘法散列法

乘法散列法的思路就是:用一个和key相关的[0,1)之间的小数与M相乘,即可的到在[0,M)之间的与key相关的哈希值。

其中,与key相关的[0,1)之间的小数通过key与一个小数常数相乘取小数部分得到。

第⼀步:用关键字 key 乘上常数 A (0<A<1),并抽取出 k*A 的小数部分。

第⼆步:后再用M乘以k*A 的小数部分,再向下取整。

即,哈希函数为"hf(key) = floor(M × ((A × key)%1.0))"。

其中floor表示对表达式进行向下取整,A∈(0,1),这里最重要的是A的值应该如何设定,Knuth(算法界大佬)认为A = (\sqrt{5} - 1) / 2 = 0.6180339887... (黄金分割点)比较好。

乘法散列法对哈希表大小M是没有要求的。

2.3 全域散列法(简单了解)

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

• hf(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, 则h34(8) = ((3 × 8 + 4)%17)%6 = 5 。

• 需要注意的是每次初始化哈希表时,随机选取全域散列函数组中的一个散列函数使用,后续增删查改都固定使用这个散列函数,否则每次哈希都是随机选一个散列函数,那么插入是一个散列函数,查找又是另一个散列函数,就会导致找不到插入的key了。

3. 哈希冲突地解决方式

哈希冲突无法被避免,我们只能尝试去解决它,常见的解决方式有两种:开放定址法和链地址法。

3.1 开放定址法

开放定址法(Open Addressing)是一种处理哈希冲突的方法,当两个或更多的键通过哈希函数映射到同一个槽(哈希地址)时,开放定址法通过在哈希表中寻找下一个空槽来解决冲突。这种方法不使用额外的存储结构,而是在哈希表内部解决冲突。

开放定址法的基本思想是,如果一个键的哈希地址所在的槽已被占用,就按照预定的策略在哈希表中寻找下一个可用的槽。

这种策略通常包括线性探测、二次探测、双重散列等多种形式。在这些策略中,线性探测是最简单的一种,它按照固定的增量顺序检查后续的槽;二次探测则使用递增的平方数作为增量;双重散列使用两个或以上的散列函数来确定探测的步长。

开放定址法的关键在于处理删除操作时的特殊情况。由于开放定址法中的槽可能会被多次探测,简单地删除一个元素可能会导致后续插入或查找操作中断开,因此通常需要设置一个特殊的标志来指示一个槽虽然被标记为删除,但仍然参与探测过程。

就好比我们通过书的类型计算出的地址是某个分区的首位,但此处已经放上了书,那么我们就可以在其后的位置(该区的下一个空位)放下这本书。在找书时依然按照相同的策略,也可以很快找到书,且冲突被化解了。

但是在依次查找书时,不能因为遇到了空槽就认为要查找的书没有被存放,而不继续向下寻找,因为空槽的位置可能在存书时存在,而在取书之前先被别人取走了。 

3.1.1 线性探测

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

hf(key) = 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(19) = 8,h(30) = 8,h(5) = 5,h(36) = 3,h(13) = 2,h(20) = 9,h(21) =10,h(12) = 1

3.1.2 二次探测 

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

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

二次探测当 hashi = (hash0 - i2)%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

 3.1.3 双重散列

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

h1(key) = hash0 = key % M, hash0位置冲突了,则双重探测公式为:

hc(key, i) = hashi = (hash0 + i ∗ h2(key)) % M, i = {1, 2, 3, ..., M}

要求h2(key) < M 且h2(key)和M互为质数,有两种简单的取值方法:

1、当M为2整数幂时,h2(key) 从[0,M-1]任选一个奇数;

2、当M为质数时, h2(key) = key % (M - 1) + 1

保证h2(key)与M互质是因为,假如偏移量刚好是散列表大小的因数,那么在每次寻址到末尾回到开头时都会是同一个位置,导致形成一个循环,只有固定的几个地址能被寻到。

举例来说,若初始探查位置为1,偏移量为3,整个散列表大小为12,那么所能寻址的位置为{1, 4, 7, 10},寻址个数为12/gcd(12, 3) = 4

但如果h2(key)与M互质的话,每次回到开头时都不会与上一轮重复。

例如将偏移量改为5,那么所能寻址的位置为{1, 6, 11, 4, 9, 2, 7, 0, 5, 10, 3, 8},每一个位置都有可能寻到。

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

3.1.4 线性探测哈希表代码实现示例
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
};inline unsigned long __stl_next_prime(unsigned long n)
{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;
}// 哈希函数采用除留余数法
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>
struct Equal
{bool operator()(const K& key1, const K& key2){return key1 == key2;}
};// 以下采用开放定址法,即线性探测解决冲突
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, class V, class Hash = HashFunc<K>, class Compare = Equal<K>>class HashTable{public:HashTable():_tables(__stl_next_prime(0)), _n(0){}HashTable(const HashTable<K, V, Hash, Compare>& hashtable): _tables(hashtable._tables), _n(hashtable._n){}bool Insert(const pair<K, V>& kv){// 扩容if (_n == _tables.size()){HashTable<K, V, Hash> newHashTable;newHashTable._tables.resize(__stl_next_prime(_n + 1));for (size_t i = 0; i < _n; i++){if (_tables[i]._state == EXIST)newHashTable.Insert(_tables[i]._kv);}swap(_tables, newHashTable._tables);}size_t pos = _hash(kv.first) % _tables.size();while (_tables[pos]._state == EXIST){if (_com(_tables[pos]._kv.first, kv.first))return false;pos = (pos + 1) % _tables.size();}_tables[pos]._kv = kv;_tables[pos]._state = EXIST;++_n;return true;}HashData<K, V>* Find(const K& key){size_t pos = _hash(key) % _tables.size();while (_tables[pos]._state != EMPTY){if (_com(_tables[pos]._kv.first, key) && _tables[pos]._state != DELETE)return &_tables[pos];pos = (pos + 1) % _tables.size();}return nullptr;}bool Erase(const K& key){if (HashData<K, V>* p = Find(key)){(*p)._state = DELETE;--_n;}return false;}private:vector<HashData<K, V>> _tables;size_t _n = 0;  // 表中存储数据个数Hash _hash;Compare _com;};
}

在上面的代码中,M的取值采取了STL中的实现方式,即在一个素数表中取值。

注意:扩容必须要将原表的数据重新插入新表中,因为M发生了变化,哈希函数计算出的地址也会发生变化。 

 3.2 链地址法

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

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

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

相比于开放地址法,链地址法不仅更加直观而且更加高效,实际当中的应用更多。

哈希桶的叫法实际上十分形象,每一个链表就是一个桶,我只需要知道把数据存到那个桶和到那个桶中去寻找需要的数据即可。

这种解决冲突的方式就与图书馆的例子更加贴合了,一个分区就是一个桶,我们在图书馆中找书,能锁定的就只是一个分区而已,但这无疑大大提高了我们找书的效率。

3.2.1 扩容时机

开放定址法中,负载因子一定小于1,表满就扩容,这是必然的。

但是在链地址法中,链表中可以存储无数的数据,负载因子没有限制。

负载因子越大,哈希冲突的概率越高,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低;stl中unordered_xxx的最大负载因子基本控制在1,大于1就扩容,我们下面实现也使用这个方式。

3.2.2 极端场景

如果极端场景下,某个桶特别长,查找效率很低怎么办?这里在Java8的HashMap中当桶的长度超过一定阀值(8)时就把链表转换成红黑树。一般情况下,不断扩容,单个桶很长的场景还是比较少的,下面我们实现就不搞这么复杂了,这个解决极端场景的思路,大家了解一下即可。

3.2.3 链地址哈希表代码实现示例
// 以下采用链地址法
namespace linked_address
{template<class K, class V>struct HashNode{pair<K, V> _kv;HashNode* _next;HashNode(const pair<K, V>& kv):_kv(kv), _next(nullptr){}};template<class K, class V, class Hash = HashFunc<K>, class Compare = Equal<K>>class HashTable{typedef HashNode<K, V> Node;typedef HashTable<K, V, Hash, Compare> Self;public:HashTable():_tables(__stl_next_prime(0)), _n(0){}HashTable(const Self& hashtable):HashTable(){for (auto cur : hashtable._tables){while (cur){Insert(cur->_kv);cur = cur->_next;}}}Self& operator=(Self hashtable){swap(_tables, hashtable._tables);swap(_n, hashtable._n);return *this;}~HashTable(){for (auto cur : _tables){while (cur){Node* next = cur->_next;delete cur;cur = next;}}}bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;// 负载因子等于1,扩容if (_n == _tables.size()){vector<Node*> newTables(__stl_next_prime(_n + 1));// 原结点依次插入新表for (size_t i = 0; i < _n; i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;size_t pos = _hash(cur->_kv.first) % _tables.size();cur->_next = newTables[pos];newTables[pos] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t pos = _hash(kv.first) % _tables.size();Node* newnode = new Node(kv);newnode->_next = _tables[pos];_tables[pos] = newnode;++_n;return false;}Node* Find(const K& key){size_t pos = _hash(key) % _tables.size();Node* cur = _tables[pos];while (cur && !_com(cur->_kv.first, key)) { cur = cur->_next; }return cur;}bool Erase(const K& key){size_t pos = _hash(key) % _tables.size();if (_tables[pos] == nullptr)return false;Node* del = nullptr;if (_com(_tables[pos]->_kv.first, key)){del = _tables[pos];_tables[pos] = _tables[pos]->_next;}else{Node* parent = _tables[pos];while (parent->_next && !_com(parent->_next->_kv.first, key)){parent = parent->_next;}if (parent->_next == nullptr)return false;del = parent->_next;parent->_next = parent->_next->_next;}delete del;--_n;return true;}private:vector<Node*> _tables;size_t _n = 0;  // 表中存储数据个数Hash _hash;Compare _com;};
}

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

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

相关文章

【Python数据库操作】使用SQLite和MySQL进行数据存储和查询!

【Python数据库操作】使用SQLite和MySQL进行数据存储和查询&#xff01; 在现代应用程序中&#xff0c;数据存储与管理是至关重要的。Python为开发者提供了多种与数据库进行交互的方式&#xff0c;其中SQLite和MySQL是最常用的两种数据库。本文将深入探讨如何使用Python进行SQ…

No.20 笔记 | WEB安全 - 任意文件操作详解 part 2

一、文件后缀名验证 &#xff08;一&#xff09;验证方式分类 基于白名单验证&#xff1a;只允许上传白名单中指定后缀名的文件。基于黑名单验证&#xff1a;只允许上传黑名单中未包含后缀名的文件。 &#xff08;二&#xff09;实验准备 修改 Apache 的 httpd - conf 文件…

uni-app写的微信小程序如何体积太大如何处理

方法一&#xff1a;对主包进行分包处理&#xff0c;将使用url: /pages/components/equipment/equipment跳转页面的全部拆分为分包&#xff0c;如url: /pagesS/components/equipment/equipment 在pages.json中添加 "subPackages": [{ "root"…

2024年五一杯数学建模C题煤矿深部开采冲击地压危险预测求解全过程论文及程序

2024年五一杯数学建模 C题 煤矿深部开采冲击地压危险预测 原题再现&#xff1a; “煤炭是中国的主要能源和重要的工业原料。然而&#xff0c;随着开采深度的增加&#xff0c;地应力增大&#xff0c;井下煤岩动力灾害风险越来越大&#xff0c;严重影响着煤矿的安全高效开采。在…

transient关键字详解

今天没打算写blog&#xff0c;在看一篇关于多线程环境下SimpleDateFormat线程不安全的问题&#xff0c;一般我们都知道多线程下这个是不安全&#xff0c;但是为什么不安全不太清楚&#xff0c;我在看的这篇文章讲的比较透彻&#xff0c;我根据文章中讲结合SimpleDateFormat源码…

[Unity Demo]从零开始制作空洞骑士Hollow Knight第十五集:制作更多地图,更多敌人,更多可交互对象

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、第一个代表性场景 1.制作更多敌人2.制作更多可交互对象二、第二个代表性场景 1.制作更多敌人2.制作更多可交互对象三、第三个代表性场景 1.制作更多敌人2.制…

苹果手机照片误删还能恢复吗?3款数据恢复工具推荐

照片是人们记录生活点滴与美好回忆的重要方式之一。而苹果手机则具备了较强的拍照功能&#xff0c;深受市场欢迎&#xff0c;但其也存在误删照片并难以恢复的难题。现在市面上也又很多照片恢复软件&#xff0c;其功能参差不齐。今天&#xff0c;小编为您找到了3款高效且可靠的苹…

猫咪掉毛上岸!一招解决清理难题——好用的宠物空气净化器

养宠前就知道猫咪有换毛季&#xff0c;我了解的是一年有两次&#xff0c;养宠后才知道&#xff0c;一次是半年...秋天风大的时候更加严重&#xff0c;直接就是一只“蒲公英”&#xff0c;随时散落一地的猫毛。早晚给它梳毛&#xff0c;每次都能收获巨大一张猫饼。 家里的地板上…

高标准农田信息化推动农业产业链升级

在当前全球农业转型的关键时期&#xff0c;高标准农田信息化作为现代农业发展的核心驱动力之一&#xff0c;正深刻地影响并推动着农业产业链的全面升级。这一进程不仅关乎粮食安全与质量的提升&#xff0c;更是实现农业可持续发展、促进农村经济繁荣的重要途径。 技术应用&…

TCL中环开工率下滑,员工集体要求解约赔偿

“ 尽管中环的市占率有所提高&#xff0c;但是高开工率也带来了巨量硅片库存&#xff0c;严重拖累了公司业绩。 ” 转载&#xff1a;科技新知 原创 作者丨依蔓 编辑丨蕨影 因大幅下调开工率&#xff0c;光伏硅片龙头TCL中环疑似遭遇员工“离职潮”&#xff1f; 近日&…

Java EE规范

1、简介 Java EE的全称是Java Platform, Enterprise Edition。早期Java EE也被称为J2EE&#xff0c;即Java 2 Platform Enterprise Edition的缩写。从J2EE1.5以后&#xff0c;就改名成为Java EE。一般来说&#xff0c;企业级应用具备这些特征&#xff1a;1、数据量特别大&…

curl支持ssl报错:SSL certificate problem: unable to get local issuer certificate

1. 说明 在使用curl时返回CURLE_OUT_OF_MEMORY&#xff0c;猜想可能是ssl的问题&#xff0c;用curl命令做实验发现访问http没问题&#xff0c;访问https会有问题&#xff0c;根据打印log判断是缺少证书&#xff0c;需要下载证书并声明证书路径。 1.1 curl访问http 访问正常&…

计算机网络基础进阶

三次握手四次挥手 三次握手 1------建立连接----------------------2 ACK1&#xff0c;seq0 2------传输数据&#xff0c;建立连接---------1 1------传输数据&#xff0c;建立连接---------2 三次握手用于建立TCP连接&#xff0c;确保通信双方都准备好进行数据传输。整个…

Gin 协程mysql客户端

一、Gin框架 mysql配置 这里选择yaml文件配置 二、配置读取 viper 读取yaml文件中对应配置 三、mysql 的协程客户端 文件位置 package databaseimport ("database/sql""fmt""github.com/spf13/viper""log""net/http"&quo…

鸿蒙_基础

HarmonyOS基础 页面跳转 import { router } from kit.ArkUIButton(去首页).onClick(()>{router.pushUrl({ // 普通跳转&#xff0c;可以返回url:pages/Index}) }) Button(登录).onClick(()>{router.replaceUrl({ // 替换跳转&#xff0c;无法返回&#xff0…

Zabbix进阶实战!将告警推送到Syslog服务器详细教程

需求场景&#xff1a; 用户需要将zabbix产生的告警事件推送给rsyslog服务器&#xff0c;syslog服务器再对事件日志进行分析处理. 环境配置信息 服务器 IP地址 ZabbixServer 192.168.200.195 Rsyslog服务器 192.168.200.128 *Rsyslog服务器防火墙需要放通 UDP/514端口 推送脚本…

C#描述-计算机视觉OpenCV(7):MSER特征检测

C#描述-计算机视觉OpenCV&#xff08;7&#xff09;&#xff1a;MSER特征检测 基本概念操作实例效果优化 基本概念 前文C#描述-计算机视觉OpenCV&#xff08;6&#xff09;&#xff1a;形态学描述了如何对图像的前后景特征形态进行检测与运算&#xff0c;本篇将分析基于形态的…

CANoe与C#联合仿真方案

引言 CANoe作为一款强大的网络仿真工具,能够模拟各种通信协议,尤其是在汽车领域的CAN、LIN、Ethernet等协议。而C#作为一种广泛使用的编程语言,能够为CANoe提供灵活的用户界面和逻辑控制。本文将探讨如何将CANoe与C#结合,实现高效的联合仿真方案。 1. 系统架构 联合仿真…

秋招突击——8/6——万得数据面试总结

文章目录 引言正文面经整理一1、讲一下java的多态&#xff0c;重载&#xff0c;重写的概念&#xff0c;区别2、说一下Java的数组&#xff0c;链表的结构&#xff0c;优缺点3、创建java线程的方式有哪些&#xff0c;具体说说4、创建线程池呢、每个参数的意义5、通过那几种方式保…

软件测试工程师:如何写出好的测试用例?

软件测试用例(Test Case)是软件测试过程中的一种详细文档或描述&#xff0c;用于描述在特定条件下&#xff0c;对软件系统或组件进行测试的步骤、输入数据、预期输出和预期行为。编写高质量的测试用例是确保软件质量的关键步骤之一。以下是一些编写优秀测试用例的建议&#xff…