【C++】哈希表的实现

      • 哈希是什么
      • 理解哈希
      • 哈希所用的容器
      • 计算key值方法
      • 哈希的插入和查找
      • 解决哈希冲突
        • 闭散列也叫开放寻址法
        • 开散列
      • 哈希闭散列实现
          • 闭散列结构
          • 闭散列结构插入
          • 闭散列查找
          • 闭散列删除
      • 哈希开散列实现(链表式)
          • 开散列结构
          • 开散列结构插入
          • 开散列结构查找
          • 开散列结构删除

哈希是什么

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

哈希表的做法其实很简单,就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。

而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位

搜索的效率取决于搜索过程中元素的比较次数,因此顺序结构中查找的时间复杂度为O ( N ) O(N)O(N),平衡树中查找的时间复杂度为树的高度O ( l o g N ) O(logN)O(logN)。

而最理想的搜索方法是,可以不经过任何比较,一次直接从表中得到要搜索的元素,即查找的时间复杂度为O ( 1 ) O(1)O(1)。

如果构造一种存储结构,该结构能够通过某种函数使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时就能通过该函数很快找到该元素。

理解哈希

哈希函数是一将输入(通常是一个大的数据集)映射到固定大小的输出的函数。哈希函数的一个重要特性是,无论输入数据的大小如何,其输出长度都是固定的(如果哈希函数输出长度不固定,将会导致数据分布不均匀或桶的大小不明确,影响哈希表的性能),这使得哈希函数非常有用,因为它可以将大量的数据快速映射到一个较小的范围内。

哈希所用的容器

unordered_map和unordered_set都是C++标准库中的容器,用于存储一组元素。它们的底层实现都是基于哈希表。

unordered_map是一种关联容器,它存储一组键值对(key-value pairs)。每个键都唯一且与一个值相关联。unordered_map使用哈希函数将键映射到特定的存储桶(bucket),并且在桶中存储值。由于使用哈希表实现,unordered_map的插入、查找和删除操作都具有常数平均时间复杂度。

unordered_set是一种集合容器,它存储一组唯一的元素。unordered_set使用哈希函数将元素映射到特定的存储桶,并在桶中存储元素。与unordered_map类似,unordered_set的插入、查找和删除操作也具有常数平均时间复杂度。

unordered_map和unordered_set的区别在于unordered_map存储的是键值对,而unordered_set只存储值。因此,unordered_map可以用来解决需要根据键快速查找对应值的问题,而unordered_set则可以用来快速判断一个值是否存在于集合中。

优点:

  • 查询速度快:由于使用哈希表实现,unordered_map和unordered_set的查询操作具有常数平均时间复杂度。
  • 高效的插入和删除:插入和删除元素的操作也具有常数平均时间复杂度。
  • 灵活性:可以存储不同类型的键和值。

缺点:

  • 内存消耗较大:由于需要维护哈希表,unordered_map和unordered_set通常会消耗比较多的内存空间。
  • 无序性:元素在容器中的存储位置是无序的,无法保证元素的顺序。

计算key值方法

哈希计算存储关键码的方法有以下几种:

  1. 直接定址法(常用):
    取关键字的某个线性函数为哈希地址:H a s h ( K e y ) = A ∗ K e y + B
  • 思路:将关键码直接作为地址来存储,即 H(key) = key。适用于关键码分布比较均匀的情况。
  • 区别:直接定址法不需要计算哈希值,直接使用关键码本身作为地址,因此查找效率很高。
  • 优点:查找操作的平均时间复杂度为O(1),即常数时间复杂度。
  • 缺点:当关键码的分布不均匀时,会导致冲突(多个关键码映射到同一个地址),需要解决冲突的方法。
  1. 数字分析法:
  • 思路:通过对关键码进行分析,选取其中具有代表性的数字作为哈希地址。例如,对身份证号码进行哈希存储时,可以选取其中的年份作为地址。
  • 区别:数字分析法需要对关键码进行分析,并根据分析结果选择适合的数字作为地址,适用于某些特定的数据集。
  • 优点:对于符合特定规律的数据集,数字分析法可以获得较好的哈希效果。
  • 缺点:对于没有明显规律的数据集,数字分析法可能无法获得较好的哈希效果。
  1. 平方取中法:
  • 思路:将关键码平方后,取中间的几位作为哈希地址。例如,对关键码 key 进行平方后,取中间的 m 位作为地址 H(key)。
  • 区别:平方取中法通过平方运算对关键码进行转换,然后取中间的几位作为哈希地址。
  • 优点:相对于直接定址法和数字分析法,平方取中法能够更加均匀地分布关键码,减少冲突的概率。
  • 缺点:平方取中法需要进行额外的平方运算和位数操作,相比直接定址法和数字分析法,会增加一定的计算成本。
  1. 除留余数法(常用):
  • 思路:将关键码除以某个不大于哈希表大小的数,再取余数作为哈希地址。即 H(key) = key%p,其中 p 是一个不大于哈希表大小的素数。
  • 区别:除留余数法通过除法和取余操作来计算哈希地址。
  • 优点:除留余数法相对简单,计算速度快。
  • 缺点:如果选取的素数 p 与关键码的特征相关,则可能导致冲突较多。

哈希的插入和查找

数据集合{1,7,6,4,5,9};

哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小

在这里插入图片描述

当我们在再次插入一个数值位11的时候,这时发现,如果我们已同样的方法进行插入的时候,就会与 1 下标位置的元素起到冲突。这时我们就要解决冲突问题,冲突问题在下方解决

  • 插入元素
    根据待插入元的关键码(在下边将介绍),以此函数计算出该元素的存储位置并按此位置进行存放
//插入两种方法//bool Insert(const T& data)pair<iterator,bool> Insert(const T& data){KeyOfT kot;//将不同对象进行提取iterator it = Find(kot(data));if (it != end()){return make_pair(it,false);}Hash hash;//进行扩容if (_n == _tables.size()){//不调用自定义析构的方法size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;vector<Node*> newtables(newsize, nullptr);//for (Node*& cur : _tables)for(auto& cur : _tables){while (cur){Node* next = cur->_next;size_t hashi = hash(kot(cur->_data)) % newtables.size();//头插到新列表cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}}_tables.swap(newtables);}size_t hashi = hash(kot(data)) % _tables.size();//头插Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;_n++;return make_pair(iterator(newnode,this),true);}
  • 查找元素
    对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置去元素比较,若关键码相等,则搜索成功
//查找iterator Find(const K& key){if (_tables.size() == 0){return end();}Hash hash;//根据类型不同来计算他的整体key值KeyOfT kot;//将不同对象进行提取查找对应值size_t hashi = hash(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur,this);}else{cur = cur->_next;}}return end();}

解决哈希冲突

数据集合{1,7,6,4,5,9};

哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小

在这里插入图片描述

当我们在再次插入一个数值位11的时候,这时发现,如果我们已同样的方法进行插入的时候,就会与 1 下标位置的元素起到冲突。这时我们就要解决冲突问题

解决哈希冲突两种常见的方法是:闭散列开散列

闭散列也叫开放寻址法

开放寻址法(Open Addressing):在每个哈希桶中直接存储键值对,当发生冲突时,通过一定的探索规则找到下一个可用的桶。常见的探索规则有线性探测、二次探测

当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有
空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。

开放寻址法不需要额外的内存来存储指针,且具有较好的缓存友好性,但当负载因子较高时,可能会导致连续冲突的概率增加,进而影响到性能。
线性探测

在这里插入图片描述
删除元素问题
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。

二次探测
线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为:

Hi=(H0+i ^2)%m ( i = 1 , 2 , 3 , . . . )
H0:通过哈希函数对元素的关键码进行计算得到的位置。
H i:冲突元素通过二次探测后得到的存放位置。
m:表的大小。
在这里插入图片描述

采用二次探测为产生哈希冲突的数据寻找下一个位置,相比线性探测而言,采用二次探测的哈希表中元素的分布会相对稀疏一些,不容易导致数据堆积。

和线性探测一样,采用二次探测也需要关注哈希表的负载因子,例如,采用二次探测将上述数据插入到表长为20的哈希表,产生冲突的次数也会有所减少:
在这里插入图片描述

开散列

在每个哈希桶中使用链表或其他数据结构存储冲突的键值对。当发生冲突时,新的键值对可以简单地添加到链表的末尾。这种方法简单易行,适用于频繁发生冲突的情况。链地址法的缺点是需要额外的内存来存储链表的指针,同时在处理大量冲突时,链表的遍历效率可能较低。

例如,我们用除留余数法将序列{1, 6, 15, 60, 88, 7, 40, 5, 10}插入到表长为10的哈希表中,当发生哈希冲突时我们采用开散列的形式,将哈希地址相同的元素都链接到同一个哈希桶下,插入过程如下:
在这里插入图片描述
闭散列解决哈希冲突,采用的是一种报复的方式,“我的位置被占用了我就去占用其他位置”。而开散列解决哈希冲突,采用的是一种乐观的方式,“虽然我的位置被占用了,但是没关系,我可以‘挂’在这个位置下面”。

与闭散列不同的是,这种将相同哈希地址的元素通过单链表链接起来,然后将链表的头结点存储在哈希表中的方式,不会影响与自己哈希地址不同的元素的增删查改的效率,因此开散列的负载因子相比闭散列而言,可以稍微大一点。

  • 闭散列的开放定址法,负载因子不能超过1,一般建议控制在[0.0, 0.7]之间。
  • 开散列的哈希桶,负载因子可以超过1,一般建议控制在[0.0, 1.0]之间。

在实际中,开散列的哈希桶结构比闭散列更实用,主要原因有两点

  • 哈希桶的负载因子可以更大,空间利用率高。
  • 哈希桶在极端情况下还有可用的解决方案。

哈希桶的极端情况就是,所有元素全部产生冲突,最终都放到了同一个哈希桶中,此时该哈希表增删查改的效率就退化成了O ( N )

在这里插入图片描述

哈希闭散列实现

闭散列结构

我们用枚举来表示它当前的状态

//枚举:标识每个位置的状态
enum State
{EMPTY,//为空EXIST,//存在DELETE//删除
};

设置当前状态的原因:

  • 当我们进行插入、查找、删除的时候,能够跟有效快捷的得到当前位置的状态。
    • 当进行插入的时候,我们会时刻进行判断,判断此时的位置是否有值,直到遇到空或者删除状态的位置才会停下来进行存储。
    • 当进行查找的时候,我们会时刻进行判断,当遇到状态为空的时候,我们就会直接退出,因为遇到空后就说明此时的范围已经没有我们要查值了。反过来如果我们要查找一个值
      在这里插入图片描述
      -当删除数据后,我们要将此时的位置设置为删除状态,这样在查找和插入的时候就不会出现错

所以,我们在设置它结构的时候,我们要将它的每一个位置初始化为空状态,且还要加一个表的长度,防止表的负载过大。

//哈希表每个位置存储的结构
template<class K, class V>
struct HashData
{pair<K, V> _kv;State _state = EMPTY; //状态
};

创建一个类专门进行插入各种操作功能

//哈希表
template<class K, class V>
class HashTable
{
public://...
private:vector<HashData<K, V>> _table; //哈希表size_t _n = 0; //哈希表中的有效元素个数
};
闭散列结构插入

当进行插入的时候,我们需要注意它的扩容问题,在这里我们的负载因子一般控制在 0 ~ 0.7 之间,如果超过了这个范围,就需要扩容。
当扩容时,我们不是原地扩,而是先设置一个新的容器,然后放大原先的两倍,再将所有数据挪过去,最后交换数组就可以了

bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;// 负载因子超过0.7就扩容//if ((double)_n / (double)_tables.size() >= 0.7)if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7){size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;HashTable<K, V> newht;newht._tables.resize(newsize);// 遍历旧表,重新映射到新表for (auto& data : _tables){if (data._state == EXIST){newht.Insert(data._kv);}}_tables.swap(newht._tables);}size_t hashi = kv.first % _tables.size();// 线性探测size_t i = 1;size_t index = hashi;while (_tables[index]._state == EXIST){index = hashi + i;index %= _tables.size();++i;}_tables[index]._kv = kv;_tables[index]._state = EXIST;_n++;return true;}
闭散列查找

在进行查找时,首先需要用哈希函数算出对应key值,然后剩下我们的工作只是遍历元素,查找对应的元素值。
只需要看当前状态是否为存在或者删除,当遇到空时,说明所找元素不存在

HashData<K, V>* Find(const K& key){if (_tables.size() == 0){return false;}size_t hashi = key % _tables.size();// 线性探测size_t i = 1;size_t index = hashi;while (_tables[index]._state != EMPTY){if (_tables[index]._state == EXIST&& _tables[index]._kv.first == key){return &_tables[index];}index = hashi + i;index %= _tables.size();++i;// 如果已经查找一圈,那么说明全是存在+删除if (index == hashi){break;}}return nullptr;}
闭散列删除

删除哈希表中的元素非常简单,我们只需要进行伪删除即可,也就是将待删除元素所在位置的状态设置为DELETE。

在哈希表中删除数据的步骤如下:

  • 查看哈希表中是否存在该键值的键值对,若不存在则删除失败。
  • 若存在,则将该键值对所在位置的状态改为DELETE即可。
  • 哈希表中的有效元素个数减一。

注意: 虽然删除元素时没有将该位置的数据清0,只是将该元素所在状态设为了DELETE,但是并不会造成空间的浪费,因为我们在插入数据时是可以将数据插入到状态为DELETE的位置的,此时插入的数据就会把该数据覆盖。

bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (ret){ret->_state = DELETE;--_n;return true;}else{return false;}}

哈希开散列实现(链表式)

开散列结构

在开散列的哈希表中,哈希表的每个位置存储的实际上是某个单链表的头结点,即每个哈希桶中存储的数据实际上是一个结点类型,该结点类型除了存储所给数据之外,还需要存储一个结点指针用于指向下一个结点。

//每个哈希桶中存储数据的结构
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 HashTable
{
public://...
private://typedef HashNode<K, V> Node;vector<Node*> _table; //哈希表size_t _n = 0; //哈希表中的有效元素个数
};
开散列结构插入
  • 若哈希表的负载因子已经等于1了,则先创建一个新的哈希表,该哈希表的大小为原哈希表的两倍,之后遍历原哈希表,将原哈希表中的数据插入到新哈希表,最后将原哈希表与新哈希表交换即可。
  • 重点: 在将原哈希表的数据插入到新哈希表的过程中,不要通过复用插入函数将原哈希表中的数据插入到新哈希表,因为在这个过程中我们需要创建相同数据的结点插入到新哈希表,在插入完毕后还需要将原哈希表中的结点进行释放,多此一举。

实际上,我们只需要遍历原哈希表的每个哈希桶,通过哈希函数将每个哈希桶中的结点重新找到对应位置插入到新哈希表即可,不用进行结点的创建与释放。
在这里插入图片描述

bool Insert(const pair<K, V>& kv){if (Find(kv.first)){return false;}Hash hash;// 负载因因子==1时扩容if (_n == _tables.size()){size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;vector<Node*> newtables(newsize, nullptr);//for (Node*& cur : _tables)for (auto& cur : _tables){while (cur){Node* next = cur->_next;size_t hashi = hash(cur->_kv.first) % newtables.size();// 头插到新表cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}}_tables.swap(newtables);}size_t hashi = hash(kv.first) % _tables.size();// 头插Node* newnode = new Node(kv);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}
开散列结构查找

在哈希表中查找数据的步骤如下:

  • 先判断哈希表的大小是否为0,若为0则查找失败。
  • 通过哈希函数计算出对应的哈希地址。
  • 通过哈希地址找到对应的哈希桶中的单链表,遍历单链表进行查找即可。
		Node* Find(const K& key){if (_tables.size() == 0)return nullptr;Hash hash;size_t hashi = hash(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 hash;size_t hashi = hash(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;return true;}else{prev = cur;cur = cur->_next;}}return false;}

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

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

相关文章

Map和Set及其实现类详解

目录 一, 搜索 1,传统搜索 2,Map和Set模型 二, Map的使用 1,Map接口的继承及实现图 2,Map接口的使用 3,TreeMap和HashMap的使用和对比 1,TreeMap 代码示例 map中插入的数据按照key进行排序 map中插入的数据必须具有可比较性(或者实现了比较器相关接口) ​map中插入…

Echarts 折线图的详细配置过程

文章目录 折线图 简介配置步骤简易示例 折线图 简介 Echarts是一款基于JavaScript的开源可视化库&#xff0c;由百度开发和维护。它提供了丰富多样的图表类型&#xff0c;其中折线图是其中一种常用的图表类型。 折线图通过连接数据点所形成的折线来展示数据的变化趋势。在折线…

互联网3.0 数字原生——数物虚实多维细粒度泛在融合

随着计算机、宽带网、通信技术的飞速发展&#xff0c;互联网技术和软硬件系统也不断演进&#xff0c;催生了一场前所未有的数字化革命。从Web1.0到Web3.0&#xff0c;以及虚拟现实、人工智能和数字孪生等领域的崛起&#xff0c;每一步都勾画出了一个崭新的数字未来&#xff0c;…

实战SpringMVC之CRUD

目录 一、前期准备 1.1 编写页面跳转控制类 二、实现CRUD 2.1 相关依赖 2.2 配置文件 2.3 逆向生成 2.4 后台代码完善 2.4.1 编写切面类 2.4.2 编写工具类 2.4.3 编写biz层 2.4.4 配置mapper.xml 2.4.5 编写相应接口类&#xff08;MusicMapper&#xff09; 2.4.6 处…

高效成绩查询系统助力,让学校管理事半功倍

各位老师们&#xff0c;大家好&#xff01;作为教育工作者&#xff0c;我们都了解成绩查询在学校管理中的重要性。然而&#xff0c;传统的查询方式往往繁琐耗时&#xff0c;给我们带来了不少困扰。因此&#xff0c;今天我将向大家介绍一个极其便捷的查询工具&#xff0c;能够帮…

生成式人工智能在高等教育 IT 中的作用

作者&#xff1a;Jared Pane 通过将你大学的数据与公共 LLMs 和 Elasticsearch 安全集成来找到你需要的答案。 根据 2023 年 4 月 EDUCAUSE 的一项调查&#xff0c;83% 的受访者表示&#xff0c;生成式人工智能将在未来三到五年内深刻改变高等教育。 学术界很快就询问和想象生…

盘点:人工智能发展趋势下的4大常见AI算法以及应用场景

近年来&#xff0c;人工智能的发展速度十分惊人&#xff0c;在安防监控、工业制造、农业、教育、金融、医疗等领域中的应用越来越广泛&#xff0c;并且未来几年也将继续保持高速的发展趋势。通过人工智能技术提高自动化程度、减少人工干预、提高监管效率&#xff0c;已经成为当…

虚拟机Ubuntu操作系统常用终端命令(2)(详细解释+详细演示)

本篇概要 本篇讲述了Ubuntu操作系统常用的几个功能&#xff0c;即超级用户&#xff0c;虚拟机系统损坏如何修复&#xff0c;用户和组&#xff0c;如何以root登录界面以及文件的权限方面的知识。希望能够得到大家的支持。 文章目录 本篇概要1.超级用户1.1使用超级用户1.2切换到…

【Robotframework+python】实现http接口自动化测试

前言 下周即将展开一个http接口测试的需求&#xff0c;刚刚完成的java类接口测试工作中&#xff0c;由于之前犯懒&#xff0c;没有提前搭建好自动化回归测试框架&#xff0c;以至于后期rd每修改一个bug&#xff0c;经常导致之前没有问题的case又产生了bug&#xff0c;所以需要…

Feign远程接口调用

概述 目的&#xff1a;解决微服务调用问题。如何从微服务A调用微服务B提供的接口。 特性&#xff1a; 声明式语法&#xff0c;简化接口调用代码开发。像调用本地方法一样调用其他微服务中的接口。集成了Eureka服务发现&#xff0c;可以从注册中心中发现微服务。集成了Spring…

一文详解TCP三次握手四次挥手

文章目录 TCP的三次握手和四次挥手三次握手四次挥手 TCP的三次握手和四次挥手 基本概念 SYN&#xff08;Synchronize Sequence Numbers&#xff0c;同步序列数字&#xff09;&#xff1a;用于建立连接的同步信号。 SYN 序列号的作用是用于标识每个数据包中的字节流的起始位置。…

Java基础入门·File类的使用

前言 ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ File类的创建方法 File类介绍 ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ …

docker 数据持久化

文章目录 定制镜像持久化需求实现 数据卷持久化数据卷简介数据卷的特性创建读写数据卷停止容器后的操作查看数据卷详情 创建只写数据卷查看数据卷详情 创建共享数据卷 Dockerfile持久化创建Dockerfile、构建和运行镜像查看宿主机端的目录 在容器层的 UnionFS&#xff08;联合文…

笔记1.2 计算机网络结构

网络边缘 主机、网络应用 接入网络&#xff0c;物理介质 有线或无线通信链路 网络核心&#xff08;核心网络&#xff09;&#xff1a; 互联的路由器&#xff08;或分组转发设备&#xff09; 网络之网络 一、网络边缘 主机&#xff08;端系统&#xff09;&#xff1a; 位…

vue3+ts+uniapp小程序封装获取授权hook函数

vue3tsuniapp小程序封装获取授权hook函数 小程序授权的时候&#xff0c;如果点击拒绝授权&#xff0c;然后就再也不会出现授权了&#xff0c;除非用户手动去右上角…设置打开 通过uni官方api自己封装一个全局的提示: uni.getSetting :http://uniapp.dcloud.io/api/other/settin…

Java操作Influxdb2.x

本片文章不讲怎么安装&#xff0c;只讲安装后如何用JAVA代码操作库表 1.创建数据库2.为bucket添加TELEGRAF配置3.TELEGRAF配置参数说明4.配置数据库的访问权限API TOKENS5.JAVA代码操作库表5.1 yaml5.2 pom依赖5.3 config5.4 controller5.5 查询方法、结果集提取方法 1.创建数据…

SpringBoot-插件化以及springboot扩展接口

插件化常用的实现思路 spi机制&#xff0c;Service Provider Interface &#xff0c;是JDK内置的一种服务发现机制&#xff0c;SPI是一种动态替换扩展机制约定配置和目录&#xff0c;利用反射配合实现springboot中的Factories机制Java agent&#xff08;探针&#xff09;技术S…

JavaScript中的Generator函数及其使用方式

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ Generator函数⭐ 创建Generator函数⭐ 调用Generator函数⭐ Generator函数的应用1. 异步编程2. 生成器&#xff08;Generator&#xff09; ⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧…

老板要我开发一个简单的工作流引擎-读后感与补充

概述 最近读了一篇《老板要我开发一个简单的工作流引擎》 幽默风趣&#xff0c;干货较多&#xff0c;作为流程引擎的设计者、开发者、探索者&#xff0c;写的很好&#xff0c;合计自己的理解&#xff0c;对每个功能补充说明&#xff0c;对于流程引擎的应用场景&#xff0c;做出…

vue中slot,slot-scope,v-slot的用法和区别

slot用于设置标签的属性值(slot“title”)slot-scopev-slot slot <el-menu-item v-if"!navMenu.children" :key"navMenu.id" :index"navMenu.id " click"itemClick(navMenu)" ><span slot"title">{{ navMenu.…