c++进阶--哈希表的实现
大家好,今天我们来学习ubordered_set和unordered_map的底层哈希表。
目录
哈希表实现
1. 哈希概念
1.1 直接定址法
1.2 哈希冲突
1.3 负载因⼦
1.4 将关键字转为整数
1.5 哈希函数
下面我们介绍几种哈希函数:1.5.1 除法散列法/除留余数法
1.5.2 乘法散列法
1.5.3 全域散列法
1.6 处理哈希冲突
1.7 开放地址法
1.7.1 线性探测
1.7.2 二次探测
1.7.3 双重探测(了解)
1.7.4 开放定址法代码实现
1.8 链地址法
解决冲突的思路
扩容
极端场景
1.8.1 链地址法代码实现
哈希表实现
1. 哈希概念
哈希(hash)⼜称散列,是⼀种组织数据的⽅式。从译名来看,有散乱排列的意思。本质就是通过哈希函数把关键字Key跟存储位置建⽴⼀个映射关系,查找时通过这个哈希函数计算出Key存储的位置,进⾏快速查找。
1.1 直接定址法
求映射的方法有很多,最直接的就是直接定址法:
当关键字的范围⽐较集中时,直接定址法就是⾮常简单⾼效的⽅法,⽐如⼀组关键字都在[0,99]之间,那么我们开⼀个100个数的数组,每个关键字的值直接就是存储位置的下标。再⽐如⼀组关键字值都在[a,z]的⼩写字⺟,那么我们开⼀个26个数的数组,每个关键字acsii码-a ascii码就是存储位置的下标。
也就是说直接定址法本质就是⽤关键字计算出⼀个绝对位置或者相对位置。这个⽅法我们在计数排序部分已经⽤过了,其次在string章节的下⾯OJ也⽤过了。
387. 字符串中的第一个唯一字符 - 力扣(LeetCode)

s是由小写字母构成的字符串,所以我们使用一个长度为26的数组,遍历字符串,e - ' a ' 就是字符串中每个字符在数组中的映射位置,字符每出现一次,在该映射位置就增加一次,最后再进行查找。
1.2 哈希冲突
直接定址法的缺点也⾮常明显,当关键字的范围⽐较分散时,就很浪费内存甚⾄内存不够⽤。假设我们只有数据范围是[0, 9999]的N个值,我们要映射到⼀个M个空间的数组中(⼀般情况下M >= N),那么就要借助哈希函数(hash function)hf,关键字key被放到数组的h(key)位置,这⾥要注意的是h(key)计算出的值必须在[0, M)之间。
所以在数据范围大时,就要换一种映射方式,
这⾥存在的⼀个问题就是,两个不同的key可能会映射到同⼀个位置去,这种问题我们叫做哈希冲突,或者哈希碰撞。理想情况是找出⼀个好的哈希函数避免冲突,但是实际场景中,冲突是不可避免的,所以我们尽可能设计出优秀的哈希函数,减少冲突的次数,同时也要去设计出解决冲突的⽅案。
1.3 负载因⼦
假设哈希表中已经映射存储了N个值,哈希表的⼤⼩为M,那么 ,负载因⼦有些地⽅也翻译为载荷因⼦/装载因⼦等,他的英⽂为load factor。负载因⼦越⼤,哈希冲突的概率越⾼,空间利⽤率越⾼;负载因⼦越⼩,哈希冲突的概率越低,空间利⽤率越低;
1.4 将关键字转为整数
我们将关键字映射到数组中位置,⼀般是整数好做映射计算,如果不是整数,我们要想办法转换成整数,这个细节我们后⾯代码实现中再进⾏细节展⽰。下⾯哈希函数部分我们讨论时,如果关键字不是整数,那么我们讨论的Key是关键字转换成的整数。
1.5 哈希函数
哈希函数就是求映射的方法,
⼀个好的哈希函数应该让N个关键字被等概率的均匀的散列分布到哈希表的M个空间中,但是实际中却很难做到,但是我们要尽量往这个⽅向去考量设计。
下面我们介绍几种哈希函数:
1.5.1 除法散列法/除留余数法
1. 除法散列法也叫做除留余数法,顾名思义,假设哈希表的⼤⼩为M,那么通过key除以M的余数作为映射位置的下标,也就是哈希函数为:h(key) = key % M。2. 当使⽤除法散列法时,要尽量避免M为某些值,如2的幂,10的幂等。如果是 ,那么key %本质相当于保留key的后X位,那么后x位相同的值,计算出的哈希值都是⼀样的,就冲突了。如:{63 , 31}看起来没有关联的值,如果M是16,也就是 ,那么计算出的哈希值都是15,因为63的⼆进制后8位是 00111111,31的⼆进制后8位是 00011111。如果是 ,就更明显了,保留的都是10进值的后x位,如:{112, 12312},如果M是100,也就是 ,那么计算出的哈希值都是12。 所以 当使⽤除法散列法时,建议M取不太接近2的整数次幂的⼀个质数(素数)。3. 需要说明的是,Java的HashMap采⽤除法散列法时就是2的整数次幂做哈希表的⼤⼩M,这样的话就不⽤取模,⽽可以直接位运算,相对⽽⾔位运算⽐模更⾼效⼀些。但是他不是单纯的去取模,⽐如M是2^16次⽅,本质是取后16位,那么⽤key’ =key>>16,然后把key和key' 异或的结果作为哈希值。也就是说我们映射出的值还是在[0,M)范围内,但是尽量让key所有的位都参与计算,这样映射出的哈希值更均匀⼀些即可。所以我们上⾯建议M取不太接近2的整数次幂的⼀个质数的理论是⼤多数数据结构书籍中写的理论,但是实践中,灵活运⽤,抓住本质。
1.5.2 乘法散列法
对于除法散列法,对于M的选择是有要求的,下面介绍的乘法散列法则对M没有要求:
1. 乘法散列法对哈希表⼤⼩M没有要求,他的⼤思路第⼀步:⽤关键字 K 乘上常数 A (0<A<1),并抽取出 k*A 的⼩数部分。第⼆步:后再⽤M乘以k*A 的⼩数部分,再向下取整。2. h ( key ) = floor ( M × (( A × key )%1.0)),其中floor表⽰对表达式进⾏下取整,A∈(0,1),这⾥最重要的是A的值应该如何设定,Knuth认为A = ( 5^1/2 − 1)/2 = 0.6180339887.... (⻩⾦分割点])⽐较好。3. 乘法散列法对哈希表⼤⼩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.5.3 全域散列法
对于上面两种方法,由于求映射的函数公式是相同的,所以在面对特定的数据时,会产生严重的数据冲突,下面这种全域散列法可以解决这种问题:
1. 如果存在⼀个恶意的对⼿,他针对我们提供的散列函数,特意构造出⼀个发⽣严重冲突的数据集,⽐如,让所有关键字全部落⼊同⼀个位置中。这种情况是可以存在的,只要散列函数是公开且确定的,就可以实现此攻击。解决⽅法⾃然是⻅招拆招,给散列函数增加随机性,攻击者就⽆法找出确定可以导致最坏情况的数据。这种⽅法叫做全域散列。2. 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 34 (8) = ((3 × 8 + 4)%17)%6 = 5。3. 需要注意的是每次初始化哈希表时,随机选取全域散列函数组中的⼀个散列函数使⽤,后续增删查改都固定使⽤这个散列函数,否则每次哈希都是随机选⼀个散列函数,那么插⼊是⼀个散列函数,查找⼜是另⼀个散列函数,就会导致找不到插⼊的key了。
1.6 处理哈希冲突
介绍了几种方法,可见无论哪种方法都绕不开哈希冲突这个难题,实践中哈希表⼀般还是选择除法散列法作为哈希函数,那么插⼊数据时,如何解决冲突呢?主要有两种两种⽅法,开放定址法和链地址法。
下面我们来一一学习这两种方法:
1.7 开放地址法
在开放定址法中所有的元素都放到哈希表⾥,当⼀个关键字key⽤哈希函数计算出的位置冲突了,则按照某种规则找到⼀个没有存储数据的位置进⾏存储,开放定址法中负载因⼦⼀定是⼩于1的。这⾥的规则有三种:线性探测、⼆次探测、双重探测。
1.7.1 线性探测
1. 从发⽣冲突的位置开始,依次线性向后探测,直到寻找到下⼀个没有存储数据的位置为⽌,如果⾛到哈希表尾,则回绕到哈希表头的位置。2. h ( key ) = hash 0 = key % M, hash0位置冲突了,则线性探测公式为:hc ( key , i ) = hashi = ( hash 0 + i ) % M , i = {1, 2, 3, ..., M − 1},因为负载因⼦⼩于1, 则最多探测M-1次,⼀定能找到⼀个存储key的位置。3. 线性探测的⽐较简单且容易实现,线性探测的问题假设,hash0位置连续冲突,hash0,hash1,hash2位置已经存储数据了,后续映射到hash0,hash1,hash2,hash3的值都会争夺hash3位置,这种现象叫做群集/堆积。下⾯的⼆次探测可以⼀定程度改善这个问题。
首先根据hash0进行计算,计算结果如上,可见19和30的映射值是相同的,对于线性探测来说,对于映射值相同的数据,则从该映射值开始向后查找,在未插入值的地方进行插入。此时19被插入到了映射值为8的地方,30被插入到映射值为9的地方,那么当20进行插入的时候,由于映射值为9的位置已经被插入了,所以认为20也冲突,也需要向后查找一个空的位置进行插入,以次类推。
但是在相同映射值的数据很多的情况下,会导致数据的堆积。
1.7.2 二次探测
1. 从发⽣冲突的位置开始,依次左右按⼆次⽅跳跃式探测,直到寻找到下⼀个没有存储数据的位置为⽌,如果往右⾛到哈希表尾,则回绕到哈希表头的位置;如果往左⾛到哈希表头,则回绕到哈希表尾的位置;2. h ( key ) = hash 0 = key % M , hash0位置冲突了,则⼆次探测公式为: hc ( key , i ) = hashi = ( hash 0 ± i ^ 2 ) % M , i = {1, 2, 3, ...,M/2 }3. ⼆次探测当 hashi = ( hash 0 − i ^ 2 )% M 时,当hashi<0时,需要hashi += M
h(19) = 8, h(30) = 8, h(52) = 8, h(63) = 8, h(11) = 0, h(22) = 0
二次探测和线性探测的不同点就是,线性探测是从一个方向开始查找位置,到末尾了再从开头查找,而二次探测是从左右两个方向交替进行查找,每次跳过i^2个位置,这样,在相同映射值的数据很多的时候,不会形成像线性探测那样大规模的数据堆积。
1.7.3 双重探测(了解)
1. 第⼀个哈希函数计算出的值发⽣冲突,使⽤第⼆个哈希函数计算出⼀个跟key相关的偏移量值,不断往后探测,直到寻找到下⼀个没有存储数据的位置为⽌。2. h 1 ( key ) = hash 0 = key % M , hash0位置冲突了,则双重探测公式为:hc ( key , i ) = hashi = ( hash 0 + i ∗ h 2 ( key )) % M , i = {1, 2, 3, ..., M }3. 要求h 2 ( key ) < M且h 2 ( key )和M互为质数,有两种简单的取值⽅法:1、当M为2整数幂时,h2 (key)从[0,M-1]任选⼀个奇数;2、当M为质数时, h 2 ( key ) = key % ( M − 1) + 14. 保证h 2 ( key)与M互质是因为根据固定的偏移量所寻址的所有位置将形成⼀个群,若最⼤公约数p = gcd ( M , h 1 ( key)) > 1,那么所能寻址的位置的个数为M / P < M,使得对于⼀个关键字来说⽆法充分利⽤整个散列表。举例来说,若初始探查位置为1,偏移量为3,整个散列表⼤⼩为12,那么所能寻址的位置为{1, 4, 7, 10},寻址个数为12/gcd (12, 3) = 4
1.7.4 开放定址法代码实现
enum State
{EXIST,EMPTY,DELETE
};template<class K, class V>
struct HashData
{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>
{size_t operator()(const string& s){// BKDRsize_t hash = 0;for (auto ch : s){hash += ch;hash *= 131;}return hash;}
};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;
}template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public: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, Hash> newht;//newht._tables.resize(_tables.size() * 2);newht._tables.resize(__stl_next_prime(_tables.size() + 1));for (auto& data : _tables){// 旧表的数据映射到新表if (data._state == EXIST){newht.Insert(data._kv);}}_tables.swap(newht._tables);}Hash hash;size_t hash0 = hash(kv.first) % _tables.size();size_t hashi = hash0;size_t i = 1;int flag = 1;while (_tables[hashi]._state == EXIST){// 线性探测hashi = (hash0 + i) % _tables.size();++i;}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;}HashData<K, V>* Find(const K& key){Hash hash;size_t hash0 = hash(key) % _tables.size();size_t hashi = hash0;size_t i = 1;while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state == EXIST&& _tables[hashi]._kv.first == key){return &_tables[hashi];}// 线性探测hashi = (hash0 + i) % _tables.size();++i;}return nullptr;}bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (ret){ret->_state = DELETE;return true;}else{return false;}}private:vector<HashData<K, V>> _tables;size_t _n; // 记录数据个数
};
1.8 链地址法
解决冲突的思路
开放定址法中所有的元素都放到哈希表⾥,链地址法中所有的数据不再直接存储在哈希表中,哈希表中存储⼀个指针,没有数据映射这个位置时,这个指针为空,有多个数据映射到这个位置时,我们把这些冲突的数据链接成⼀个链表,挂在哈希表这个位置下⾯,链地址法也叫做拉链法或者哈希桶。
扩容
开放定址法负载因⼦必须⼩于1,链地址法的负载因⼦就没有限制了,可以⼤于1。负载因⼦越⼤,哈希冲突的概率越⾼,空间利⽤率越⾼;负载因⼦越⼩,哈希冲突的概率越低,空间利⽤率越低;stl中unordered_xxx的最⼤负载因⼦基本控制在1,⼤于1就扩容,我们下⾯实现也使⽤这个⽅式。
极端场景
如果极端场景下,某个桶特别⻓怎么办?其实我们可以考虑使⽤全域散列法,这样就不容易被针对了。但是假设不是被针对了,⽤了全域散列法,但是偶然情况下,某个桶很⻓,查找效率很低怎么办?这⾥在Java8的HashMap中当桶的⻓度超过⼀定阀值(8)时就把链表转换成红⿊树。⼀般情况下,不断扩容,单个桶很⻓的场景还是⽐较少的,下⾯我们实现就不搞这么复杂了,这个解决极端场景的思路,⼤家了解⼀下。
1.8.1 链地址法代码实现
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 += e;hash *= 131;}return hash;}
};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,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;}typedef HashNode<K, V> Node;HashTable():_tables(__stl_next_prime(0)),_n(0){}~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;}}HashTable(const HashTable& table):_tables(__stl_next_prime(table.size())),_n(0){Hash hs;for (size_t i = 0; i < table.size(); i++){Node* cur = table[i];while (cur){ Node* newnode = new Node(cur->_kv);size_t hashi = hs(cur->_kv.first) % _tables.size();newnode->_next = _tables[hashi];_tables[hashi] = newnode;cur = cur->_next;}}}HashTable& operator=(const HashTable& table){this->swap(table);return *this;}void swap(HashTable& table){swap(_tables, table._tables);swap(_n, table._n);}bool Insert(const pair<K,V>& kv){if (Find(kv.first))return false;Hash hs;if (_n == _tables.size()){vector<Node*> newtables(__stl_next_prime(_tables.size() + 1));for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){size_t hashi = hs(cur->_kv.first) % newtables.size();cur->_next = newtables[hashi];newtables[hashi] = cur;cur = cur->_next;}_tables[i] = nullptr;}_tables.swap(newtables);}size_t hashi = hs(kv.first) % _tables.size();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;}else {cur = cur->_next;}}return nullptr;}bool Erase(const K& key){if (Find(key)){Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;while (cur){if (hs(cur->_kv.first) == key){if (prev == nullptr){_tables[hashi] = cur->_next;}else {prev->_next = cur->_next;}delete cur;--_n;return true;}else {prev = cur;cur = cur->_next;}}}else {return false;}}
private:vector<Node*> _tables;size_t _n;
};
今天就讲到这里,对于哈希表是不是有了初步的认识了呢,下一期我们来学习哈希表的封装:ubordered_set和unordered_map的实现,我们下次再见。
相关文章:
c++进阶--哈希表的实现
大家好,今天我们来学习ubordered_set和unordered_map的底层哈希表。 目录 哈希表实现 1. 哈希概念 1.1 直接定址法 1.2 哈希冲突 1.3 负载因⼦ 1.4 将关键字转为整数 1.5 哈希函数 下面我们介绍几种哈希函数:1.5.1 除法散列法/除留余数法 1.…...
颠覆传统:SaaS 品牌如何通过 SEO 策略引爆市场!
SaaS 商业模式提供了令人难以置信的可扩展性和盈利能力——但前提是与正确的营销增长策略相结合。 SaaS 品牌知道,托管基于云的应用程序的成本会随着用户量的增加而降低,因此必须专注于订阅者的快速增长,以保持竞争力并降低成本。 许多 CMO…...
【数据库发展史】
数据库的发展历史可以追溯到20世纪50年代,随着计算机技术的进步和数据管理需求的演变,数据库系统经历了多个阶段的变革。以下是数据库技术的主要发展阶段: 1. 前数据库时代(1950年代前) 手工管理:数据通过…...
HTTP 核心知识点整理
1. HTTP 基础 定义:HTTP(HyperText Transfer Protocol)是应用层协议,基于 请求-响应模型,用于客户端(浏览器)与服务器之间的通信。特点: 无状态:每次请求独立&a…...
从AEC-Q100看车规芯片的可靠性设计要点
引言 随着汽车电子化、智能化的飞速发展,汽车电子控制系统对芯片的可靠性提出了极为严苛的要求。AEC-Q100是汽车电子委员会(Automotive Electronics Council)制定的车规级芯片可靠性标准,旨在确保芯片能够在复杂多变的汽车环境中…...
陕西安全员A证考试的报名流程是什么?
陕西安全员 A 证考试报名流程如下: 进入报名系统:登录陕西省建筑工程施工企业安全管理人员及特种作业人员考试报名系统。首次使用需点击 “特种作业人员注册”,进入个人注册界面。注册账号:输入身份证号、登录密码,并…...
特殊行车记录仪DAT视频丢失的恢复方法
行车记录仪是一种常见的车载记录仪,和常见的“小巧玲珑”的行车记录仪不同,一些特种车辆使用的记录仪的外观可以用“笨重”来形容。下边我们来看看特种车载行车记录仪删除文件后的恢复方法。 故障存储: 120GB存储设备/文件系统:exFAT /簇大小:128KB 故…...
PAT乙级1007
常规解法 #include <iostream> using namespace std;// 判断一个数是否为素数的函数 bool isprime(int a) {// 遍历 2 到 sqrt(a) 之间的数,判断 a 是否能被它们整除for (int i 2; i * i < a; i) {if (a % i 0) // 如果能整除,说明 a 不是素…...
数据库中不存在该字段
mybatisplus 定义的类中某些字段是数据库里面没有的,我们可用tablefield(existfalse)来注解,演示如下:...
吾爱出品,文件分类助手,高效管理您的 PC 资源库
在日常使用电脑的过程中,文件杂乱无章常常让人感到困扰。无论是桌面堆积如山的快捷方式,还是硬盘中混乱的音频、视频、文档等资源,都急需一种高效的整理方法。文件分类助手应运而生,它是一款文件管理工具,能够快速、智…...
关于瑞芯微开发工具(RKDevTool)刷机下载Boot失败原因的研究
昨天发了文章《网心云OEC/OEC-turbo刷机问题——刷机教程、救砖方法、技术要点及下载boot失败异常解决尝试》,其中有关于刷机各种问题的一些解决方法。 网心云OEC/OEC-turbo刷机问题——刷机教程、救砖方法、技术要点及下载boot失败异常解决尝试-CSDN博客文章浏览阅…...
web爬虫笔记:js逆向案例十一 某数cookie(补环境流程)
web爬虫笔记:js逆向案例十一 某数cookie(补环境流程) 一、获取网页数据请求流程 二、目标网址、cookie生成(逐步分析) 1、目标网址:aHR0cHM6Ly9zdWdoLnN6dS5lZHUuY24vSHRtbC9OZXdzL0NvbHVtbnMvNy9JbmRleC5odG1s 2、快速定位入口方法 1、通过脚本监听、hook_cookie等操作可…...
浅谈 Vue3 中的设计模式
设计模式是软件开发中的一种最佳实践,它提供了解决特定问题的通用解决方案。通过合理运用设计模式,可以提高代码的可维护性、可扩展性和可读性。在 Vue3 的源码中,设计模式被广泛应用于各个模块中,充分体现了其在现代前端框架中的…...
Unix Domain Socket、IPC、RPC与gRPC的深度解析与实战
Unix Domain Socket、IPC、RPC与gRPC的深度解析与实战 引言 在分布式系统和本地服务通信中,进程间通信(IPC)与远程过程调用(RPC)是核心能力。本文将深入剖析 Unix Domain Socket(UDS)、IPC、RP…...
07_JavaScript函数作用域_递归
目录 一、作用域(重点) 二、变量的使用规则 (重点) 2.1 访问规则 2.2 赋值规则 三、递归函数 (难点) 了解 四、对象 4.1 对象的创建 一、作用域(重点) 什么是作用域 ? 作用…...
.gitignore使用指南
.gitignore使用指南 目录 什么是.gitignore为什么需要.gitignore如何创建.gitignore文件.gitignore文件的语法规则 忽略单个文件忽略目录忽略特定类型的文件不忽略特定文件或目录递归匹配 示例.gitignore文件注意事项更多特殊场景匹配规则 忽略多个特定后缀的文件忽略特定目录…...
Excel多级联动下拉菜单的自动化设置(使用Python中的openpyxl模块)
1 主要目的 在Excel中,经常会遇到需要制作多级联动下拉菜单的情况,要求单元格内填写的内容只能从指定的多个选项中进行选择,并且需要设置多级目录,其中下级目录的选项内容要根据上级目录的填写内容确定,如下图所示&am…...
深入解析 Spring Framework 5.1.8.RELEASE 的源码目录结构
深入解析 Spring Framework 5.1.8.RELEASE 的源码目录结构 1. 引言 Spring Framework 是 Java 领域最流行的企业级开发框架之一,广泛用于 Web 开发、微服务架构、数据访问等场景。本文将深入解析 Spring Framework 5.1.8.RELEASE 的源码目录结构,帮助开…...
excalidraw画图工具——背景画布有无格子设置
服啦找了大半天,愣是没找到 toggle grid : 切换格子… Excalidraw的背景格子 只要右键,将这个勾取消就好了?...
计算机组成原理———I\O系统精讲<1>
本篇文章主要介绍输入输出系统的发展概况 一.输入输出系统的发展概况 1.早期阶段 该阶段的特点是I/O设备与主存交换信息都必须通过CPU 当时的I/O设备有如下几个特点: (1)每个I\O设备都必须配有一套独立的逻辑电路与CPU相连,用来…...
[数据结构] 动态顺序表应用
可扩容顺序表顺序表 SeqList.hSeqList.cTest.c 动态顺序表能够根据数据存储的需要动态地管理内存空间。 SeqList.h #include<stdio.h> #include<stdlib.h>//静态顺序表 //小了不够用,多了浪费 //#define N 10 //typedef int SLDatatype; //struct SeqL…...
MinIO-对象存储方案
MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。 MinIO是一个非常轻量的服务…...
装饰器模式 (Decorator Pattern)
装饰器模式 (Decorator Pattern) 是一种结构型设计模式,它动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。 一、基础 1 意图 动态地给一个对象添加一些额外的职责。 就增加功能来说,装饰器模式相比生成子类更为灵活。 2 适用场景 当…...
手动配置树莓派wifi联网连接热点手机热点
手动配置树莓派wifi联网连接热点 修改wifi配置文件: 运行命令: sudo nano /etc/wpa_supplicant/wpa_supplicant.conf 在文件中添加无线网配置信息: ctrl_interfaceDIR/var/run/wpa_supplicant GROUPnetdev update_config1 countryCN network{ ssid”你的无线网名字” psk”…...
【学习笔记】麦肯锡《超级智能体:赋能人们释放人工智能的全部潜力》
麦肯锡《超级智能体:赋能人们释放人工智能的全部潜力》报告的学习笔记: 报告背景与意义 • 科技发展趋势:随着人工智能技术的飞速发展,其在各行业的应用逐渐深入,麦肯锡的这份报告正是基于这一背景,旨在深入…...
ENSP学习day9
ACL访问控制列表实验 ACL(Access Control List,访问控制列表)是一种用于控制用户或系统对资源(如文件、文件夹、网络等)访问权限的机制。通过ACL,系统管理员可以定义哪些用户或系统可以访问特定资源&#x…...
文章记单词 | 第2篇(六级)
一,单词释义 story:名词(n.)故事;小说;(真实情况的)叙述,描述;楼层(美语写法,英式英语为 storey)stress:名词…...
【C++动态规划 数学】1039. 多边形三角剖分的最低得分|2130
本文涉及知识点 C动态规划 数学 LeetCode1039. 多边形三角剖分的最低得分 你有一个凸的 n 边形,其每个顶点都有一个整数值。给定一个整数数组 values ,其中 values[i] 是第 i 个顶点的值(即 顺时针顺序 )。 假设将多边形 剖分 …...
5.go切片和map
切片的概念 数组和切片相比较切片的长度是不固定的,可以追加元素,在追加时可能会使切片的容量增大,所以可以将切片理解成 "动态数组",但是,它不是数组,而是构建在数组基础上的更高级的数据结构。…...
【Linux网络-多路转接select】
代码:https://gitee.com/nanyi-c/linux/tree/master/day50 一、I/O多路转接之select 1.初始select 系统提供select函数来实现多路复用输入/输出模型 select系统调用是用来让我们的程序监视多个文件描述符的状态变化的程序会停在select这里等待,直到被…...
