[数据结构]-哈希

前言

作者:小蜗牛向前冲

名言:我可以接受失败,但我不能接受放弃

  如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 

 本期学习目标:了解unordered关联式容器,什么是哈希,哈希冲突怎么解决,哈希的模拟实现

一、unordered系列关联式容

1、undordered_map

常见的接口说明

unordered_map的构造:

函数声明功能介绍
unordered_map构造不同格式的unordered_map对象

unordered_map的容量:

函数声明功能介绍
bool empty() const检测unordered_map是否为空
size_t size() const获取unordered_map的有效元素个数

 unordered_map的迭代器:

函数声明功能介绍
begin返回unordered_map第一个元素的迭代器
end返回unordered_map最后一个元素下一个位置的迭代器
cbegin返回unordered_map第一个元素的const迭代器
cend返回unordered_map最后一个元素下一个位置的const迭代器

unordered_map的元素访问:

函数声明功能介绍
operator[]返回与key对应的value,没有一个默认值

注意:该函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶 中插入,如果key不在哈希桶中,插入成功返回V()插入失败,说明key已经在哈希桶中, 将key对应的value返回

unordered_map的查询:

函数声明功能介绍
iterator find(const K& key)返回key在哈希桶中的位置
size_t count(const K& key)返回哈希桶中关键码为key的键值对的个数

 . unordered_map的修改操作 :

函数声明功能介绍
insert向容器中插入键值对
erase删除容器中的键值对
void clear()清空容器中有效元素个数
void swap(unordered_map&)交换两个容器中的元素

 unordered_map的桶操作:

函数声明功能介绍
size_t bucket_count()const返回哈希桶中桶的总个数
size_t bucket_size(size_t n)const返回n号桶中有效元素的总个数
size_t bucket(const K& key)返回元素key所在的桶号

 undordered_map最重要的功能是他的查找能力非常厉害,时间复杂度为 O(1)。

查找的运用:

这里我们将数组的元素入哈希表,然后遍历哈希表,键值对对中的value为N即是重复数 

class Solution {
public:int repeatedNTimes(vector<int>& nums){sort(nums.begin(),nums.end());int n = nums.size()/2;unordered_map<int,int> counMap;for(auto& e:nums){counMap[e]++;}for(auto& kv:counMap){if(kv.second==n){return kv.first;}}return -1;}
};

2、undordered_set

undordered_set和map接口基本相同,这里不在过多介绍,下面我们将对他们的相异点进行比对

  • 元素类型:
  • unordered_map 是一种关联容器,用于存储键-值对。每个元素都是一个包含键和值的 pair。
  • unordered_set 是一种关联容器,用于存储唯一的元素。每个元素就是一个单独的值。

存储方式:

  • unordered_map 存储键-值对,每个键唯一,值可以重复
  • :unordered_set 存储唯一的元素,不包含重复值。

 使用方式:

  • unordered_map 适用于需要通过键来查找值的场景。例如,可以使用键来表示单词,值表示单词的出现次数
  • unordered_set 适用于需要存储一组唯一值的场景,而不关心这些值的顺序。例如,可以使用它来存储一组唯一的单词。

接口差异:

  • unordered_map 提供了 operator[],允许通过键来访问对应的值。
  • unordered_set 没有类似于 operator[] 的接口,因为它不是通过键来访问元素的

迭代器:

  • 对于 std::unordered_map,迭代器类型是一个指向 std::pair<const Key, T> 的迭代器。
  • 对于 std::unordered_set,迭代器类型是一个指向元素值的迭代器。

unordered_ste去重的运用

 这里运用了unordered_set去重

class Solution {
public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {// 用unordered_set对nums1中的元素去重unordered_set<int> s1;for (auto e : nums1)s1.insert(e);// 用unordered_set对nums2中的元素去重unordered_set<int> s2;for (auto e : nums2)s2.insert(e);// 遍历s1,如果s1中某个元素在s2中出现过,即为交集vector<int> vRet;for (auto e : s1){if (s2.find(e) != s2.end())vRet.push_back(e);}return vRet;}
};

3、 有序关联容器和无序关联容器

区别总结:

  • 有序关联容器std::map 和 std::set)适用于需要按顺序访问元素的场景,操作的时间复杂度较为稳定,但相对于无序关联容器,性能较差。
  • 无序关联容器std::unordered_map 和 std::unordered_set适用于需要快速查找、插入和删除的场景,但不关心元素的顺序。性能在平均情况下很好,但在最坏情况下可能较差。

 二、哈希

1、哈希概念

从前:

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素 时,必须要经过关键码的多次比较顺序查找时间复杂度为O(N),平衡树中为树的高度,即 O(log_2 N),搜索的效率取决于搜索过程中元素的比较次数。 

现在: 

             以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立 一一映射的关系,那么在查找时通过该函数可以很快找到该元素

结构模型:

插入元素 :

根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放

搜索元素 :

对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置 取元素比较,若关键码相等,则搜索成功 

简单的说,就是让元素的存储位置,形成一种映射,然后我们通过映射的关系很快找到该元素。 

 该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称 为哈希表(Hash Table)(或者称散列表)

下面我们通过哈希函数,将我们要存放的值,通过映射关系存放。

但是如果我们继续按照上面的逻辑存放,44发生什么:

计算位置:hash(44)  =44%10=4,但是4的位置,我们不是已经存放了4了,这种现象我们称为

哈希冲突:

不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称哈希冲突 或哈希碰撞。

2、哈希函数

引起哈希冲突的一个原因可能是:哈希函数设计不够合理。

哈希函数设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值 域必须在0到m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应该比较简单

常见的哈希函数:

直接定址法:

取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B

优点:简单、均匀

缺点:需要事先知道关键字的分布情况 使用场景:适合查找比较小且连续的情况

除留余数法 :

设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数, 按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址

3、哈希冲突解决 

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

3.1 闭散列

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有 空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置

 闭散列是通过线性探测的方法来解决哈希冲突的,那什么又是线性探测,这里我们还是以上面我们通过哈希函数重新插入44为例子:

插入:

线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

删除

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

 线性探测的实现模拟实现:


//这里是为了保证进入哈希表的数据能够正常取模
//通用
template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};
namespace closehash
{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 Hash = HashFunc<K>>class HashTable{typedef HashData<K, V> Data;public:HashTable():_n(0){_tables.resize(10);//默认哈希表中开10个空间}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);//插入数据到新的哈希表中for (auto& e : _tables){if (e._state == EXIST){newHT.Insert(e._kv);}}//交换新旧表指针_tables.swap(newHT._tables);}//找映射位置,存在就向后找空位置Hash hf;size_t hashi = hf(kv.first) % _tables.size();//找到空位置while (_tables[hashi]._state == EXIST){++hashi;hashi %= _tables.size();}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;}Data* Find(const K& key){Hash hf;size_t hashi = hf(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){Data* ret = Find(key);if (ret){ret->_state = DELETE;--_n;return true;}else{false;}}private:vector<Data> _tables;size_t _n = 0;//表中有效数据的个数};
}

测试: 

对于上面的模拟实现,我们要注意一下细节:

1、负载因子是什么?

负载因子 =  填入表中的元素个数 / 闲散列的长度

  • 负载因子的作用是衡量散列表的空间利用率。当负载因子较小时,表可能会有大量的空闲位置,而当负载因子较大时,可能导致哈希冲突的概率增加,影响性能。
  • 对于散列表,通常有一个合适的负载因子范围,通常在 0.5 到 0.8 之间。当负载因子超过某个阈值时,可能触发重新哈希(rehashing)操作,即重新调整表的大小,并重新将元素分布到新的表中。这有助于保持较低的负载因子,提高性能。

2、闲散列扩容

哈希表在达到一定的负载因子阈值时通常会触发扩容操作

线性探测优点:实现非常简单,

线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同 关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降 低。

为解决堆积问题的出现,可以进行二次探测 :思想是探测相隔较远的单元,而不是和原始位置相邻的单元

3.2 开散列

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链 接起来,各链表的头结点存储在哈希表中。

开散列实现:

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};
namespace buckethash
{template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};// 前置声明template<class K, class T, class Hash, class KeyOfT>class HashTable;//迭代器template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>struct __HTIterator{typedef HashNode<T> Node;typedef __HTIterator<K, T, Ref, Ptr, Hash, KeyOfT> Self;typedef HashTable<K, T, Hash, KeyOfT> HT;Node* _node;HT* _ht;//构造函数__HTIterator(Node* node, HT* ht):_node(node), _ht(ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator != (const Self& s) const{return _node != s._node;}//++Self& operator++(){if (_node->_next){_node = _node->_next;}else{//当前桶找完了KeyOfT kot;Hash hash;size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();++hashi;while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){_node = _ht->_tables[hashi];break;//++完成}else{++hashi;}}//后面没有桶数据if (hashi == _ht->_tables.size()){_node = nullptr;}}return *this;}};//哈希表//K: 表示哈希表中键(Key)的类型。//T: 表示哈希表中值(Value)的类型。//Hash: 表示用于计算哈希值的哈希函数对象的类型。//KeyOfT: 表示一个用于从值 T 中提取键 K 的函数对象的类型。template<class K, class T, class Hash, class KeyOfT>class HashTable{typedef HashNode<T> Node;template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>friend struct __HTIterator;public:typedef __HTIterator<K, T, T&, T*, Hash, KeyOfT> iterator;typedef __HTIterator<K, T, const T&, const T*, Hash, KeyOfT> const_iterator;iterator begin(){for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]){return iterator(_tables[i], this);}}return iterator(nullptr, this);}iterator end(){return iterator(nullptr, this);}//构造函数HashTable():_n(0){_tables.resize(__stl_next_prime(0));//开默认的空间}//析构函数~HashTable(){for (int i = 0; i < _tables.size(); i++){Node* cur = _tables[i];//释放桶while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}pair<iterator, bool> Insert(const T& data){KeyOfT kot;//表中有数据就不插入iterator it = Find(kot(data));if (it != end())return make_pair(it, false);// 负载因子控制在1,超过就扩容if (_tables.size() == _n){vector<Node*> newTables;newTables.resize(__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 = Hash()(kot(cur->_data)) % newTables.size();//头插入到新链表cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_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){KeyOfT kot;size_t hashi = Hash()(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);//this 指针代表当前对象(即哈希表对象)的地址}else{cur = cur->_next;}}return end();}bool Erase(const K& key){KeyOfT kot;size_t hashi = Hash()(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];//删除while (cur){if (kot(cur->_data) == key){//删除if (cur == _tables[hashi]){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}//找到删除 delete cur;--_n;return true;}else{prev = cur;cur = cur->_next;}}return false;}//确保哈希表的大小是一个质数,从而提高散列性能。inline unsigned long __stl_next_prime(unsigned long n){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};for (int i = 0; i < __stl_num_primes; ++i){if (__stl_prime_list[i] > n){return __stl_prime_list[i];}}return __stl_prime_list[__stl_num_primes - 1];}private:vector<Node*> _tables;//指针数组size_t _n;};
}

上面一连串的模拟实现,大家可能会看的有点费劲,其实的实现思路是非常简单的,就是创建一个指针数组,指针数组中放定义的哈希桶。但是实现起来细节却是非常多的,

细节问题

 1、哈希表怎样进行正常的取模?

大家心里可能会想,不直接对数据进行取模不就行了,数据如果是整形进行取模,但是如果数据是字符、字符串、自定义对象呢?

这里我们就要进行复杂的hashfun进行取模值的获取

可隐式类型转换哈希函数

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};

 字符串哈希函数

template<class K>
struct HashFunc
{size_t operator()(const K& key){size_t hashValue = 0;for (char c : key) {// 加法哈希hashValue = (hashValue * 31) + static_cast<size_t>(c);}return hashValue;}
};

2、开散列在什么情况下进行扩容

开散列最好的情况是:每个哈希桶中刚好挂一个节点, 再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容

3.3 开散列与闭散列比较

应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a <= 0.7,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间。

三、哈希的应用

1、位图操作

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用 来判断某个数据存不存在的。

我们先看一道面试题目:


问题1:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在 这40亿个数中。


思路: 

1. 遍历,时间复杂度O(N)

2. 排序(O(NlogN)),利用二分查找: logN

3. 位图解决

数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一 个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0 代表不存在。比如 

位图的实现:

template<size_t N>class bitset{public:bitset(){//初始化位图空间_bits.resize((N >> 3) + 1, 0);}void set(size_t x){size_t i = x >> 3;size_t j = x % 8;_bits[i] |= (1 << j);}void reset(size_t x){size_t i = x / 8;//x位于第几个字符size_t j = x % 8;//x位于第i个字符的第j位_bits[i] &= (~(1 << j));//1 << j 会创建一个只有第 j 位为 1 的数值}//测试bool test(size_t x){size_t i = x >> 3;size_t j = x % 8;return _bits[i] & (1 << j);}private:vector<char> _bits;};

在这段代码中,使用位操作 (N >> 3)(等价于 N / 8)而不是直接的除法操作是因为这样的做法更为高效。使用位操作在某些情况下能够提高代码的执行效率,尤其是在涉及到计算机底层的位运算时。

  1. 位移操作的效率更高: 在许多计算机体系结构中,位移操作(>><<)通常比除法操作更为高效。对于2的幂次方的除法,位移操作是特别快速的。因此,将 N 右移3位(相当于除以8)可以更有效地计算出 N 除以8的结果。

  2. 代码的可读性: 通过使用位操作,可以传达一种意图,即在这里我们只关心字节的偏移,而不是简单的数学除法。这种表达方式更能突显代码的目的,即在位图中存储位信息。

那这里我们是如何将数据和位图进行映射的呢,假设输入的数据为x怎么在位图是表示?

首先我们x/8,确定该数据在那个字符位置。

然后我们x%8,确实该数据在那字符的那一位。

最后将该位置1._bits[i] |= (1 << j);

 位图的应用

1.快速查找某个数据是否在一个集合中

2. 排序 + 去重

3. 求两个集合的交集、并集等

4. 操作系统中磁盘块标记

2、布隆过滤器

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉 那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用 户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那 些已经存在的记录。 如何快速查找呢?

对于去重,我们肯定会想到,用哈希表去存放,用户看过的信息,但是这样会造成大量的空间浪费,而位图又一般只能处理整形。

这时候有人就想到将哈希与位图结合,即布隆过滤器。

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概 率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存 在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也 可以节省大量的内存空间。

布隆过滤器插入:

 布隆过滤器的查找

布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特 位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为 零,只要有一个为零,代表该元素一定不在哈希表中否则可能在哈希表中。 注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可 能存在,因为有些哈希函数存在一定的误判。 比如:在布隆过滤器中查找"你好"时,假设3个哈希函数计算的哈希值为:3、5、7,刚好和其 他元素的比特位重叠,此时布隆过滤器告诉该元素存在,但实该元素是不存在的。

布隆过滤器删除

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。

缺陷: 1. 无法确认元素是否真正在布隆过滤器中 2. 存在计数回绕

布隆过滤器优点:

1.增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无 关

2. 哈希函数相互之间没有关系,方便硬件并行运算

3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势

4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势

5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能

6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

布隆过滤器缺陷 :

1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)

2. 不能获取元素本身

3. 一般情况下不能从布隆过滤器中删除元素

4. 如果采用计数方式删除,可能会存在计数回绕问题

简单实现:

struct BKDRHash
{size_t operator()(const string& s){// BKDRsize_t value = 0;for (auto ch : s){value *= 31;value += ch;}return value;}
};
struct APHash
{size_t operator()(const string& s){size_t hash = 0;for (long i = 0; i < s.size(); i++){if ((i & 1) == 0){hash ^= ((hash << 7) ^ s[i] ^ (hash >> 3));}else{hash ^= (~((hash << 11) ^ s[i] ^ (hash >> 5)));}}return hash;}
};
struct DJBHash
{size_t operator()(const string& s){size_t hash = 5381;for (auto ch : s){hash += (hash << 5) + ch;}return hash;}
};
template<size_t N,size_t X = 5,class K = string,class HashFunc1 = BKDRHash,class HashFunc2 = APHash,class HashFunc3 = DJBHash>
class BloomFilter
{
public:void Set(const K& key){size_t len = X * N;size_t index1 = HashFunc1()(key) % len;size_t index2 = HashFunc2()(key) % len;size_t index3 = HashFunc3()(key) % len;/* cout << index1 << endl;cout << index2 << endl;cout << index3 << endl<<endl;*/_bs.set(index1);_bs.set(index2);_bs.set(index3);}bool Test(const K& key){size_t len = X * N;size_t index1 = HashFunc1()(key) % len;if (_bs.test(index1) == false)return false;size_t index2 = HashFunc2()(key) % len;if (_bs.test(index2) == false)return false;size_t index3 = HashFunc3()(key) % len;if (_bs.test(index3) == false)return false;return true;  // 存在误判的}// 不支持删除,删除可能会影响其他值。void Reset(const K& key);
private:bitset<X* N> _bs;
};

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

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

相关文章

智能工厂4G无线设备预测维护云端联动的DI、AI、DO混合信号处理单元

在现代工业智能化进程中&#xff0c;一款集成了丰富I/O接口并能与各大云平台无缝对接的智能设备显得尤为重要。比如最近推出的这款创新产品&#xff0c;它集合了8路数字输入通道&#xff0c;涵盖了干湿节点的识别功能&#xff0c;适用于多种开关量信号的读取&#xff1b;同时&a…

(八)springboot实战——springboot3下的webflux项目全局异常处理

前言 在webflux响应式编程中&#xff0c;如何处理系统运行时异常是本节的主要内容。在传统的Servlet阻塞式web项目中主要通过HandlerExceptionResolver处理器来处理&#xff0c;而在webflux响应式web项目中&#xff0c;则是通过DispatchExceptionHandler异常处理器来处理异常。…

[SWPUCTF 2018]SimplePHP1

打开环境 有查看文件跟上传文件&#xff0c;查看文件里面显示没有文件url貌似可以文件读取 上传文件里面可以上传文件。 先看一下可不可以文件读取 /etc/passwd不能读取&#xff0c;源码提示flag在f1ag.php 看看能不能读取当前的文件&#xff0c; 先把代码摘下来 file.php …

JavaScript 之 作用域变量提升闭包

一、JavaScript 代码的执行 浏览器内核是由两部分组成的&#xff0c;以 webkit 为例 WebCore&#xff1a;负责HTML解析、布局、渲染等等相关的工作JavaScriptCore&#xff1a;解析、执行 JavaScript 代码 另外一个强大的 JavaScript 引擎就是 V8 引擎 二、深入 V8 引擎原理 …

Java面向对象三大特征之多态

在之前的文章&#xff0c;我们分别介绍了类与对象、面向对象三大特征的封装、以及继承&#xff08;一&#xff09;、继承&#xff08;二&#xff09;。这一篇文章&#xff0c;我们介绍Java面向对象三大特征的最后一个——多态。 多态 多态的概述 概念&#xff1a;完成某个行为…

CVE-2024-23897 Jenkins 任意文件读取漏洞

项目介绍 Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件项目可以进行持续集成。Jenkins是开源CI&CD软件领导者&#xff0c; 提供超过1000个插…

Java集合相关面试题

&#x1f4d5;作者简介&#xff1a; 过去日记&#xff0c;致力于Java、GoLang,Rust等多种编程语言&#xff0c;热爱技术&#xff0c;喜欢游戏的博主。 &#x1f4d7;本文收录于java面试题系列&#xff0c;大家有兴趣的可以看一看 &#x1f4d8;相关专栏Rust初阶教程、go语言基…

C# 设置一个定时器函数

C#中&#xff0c;创建设置一个定时器&#xff0c;能够定时中断执行特定操作&#xff0c;可以用于发送心跳、正计时和倒计时等。 本文对C#的定时器简单封装一下&#xff0c;哎&#xff0c;以方便定时器的创建。 定义 using Timer System.Timers.Timer;class SetTimer {Timer …

OSPF协议基础(OSPF工作过程)

目录 OSPF基本工作原理邻居建立过程Router ID发现并建立邻居 - Hello报文OSPF邻居建立过程 链路状态信息丰富的数据链路层支持能力网络类型 - P2P网络网络类型 - 广播型网络网络类型 - NBMA网络网络类型 - P2MP网络OSPF的度量方式 报文类型及作用OSPF协议报文头部OSPF报文类型O…

k8s-调度

调度 从上面的架构图我们可以看到,调度是工作在Master,负责调度Pod&#xff0c;为POD分配Node。 调度的工作原理 #查看所有的Node kubectl get nodes 我们可以看到节点有一个Name,这就是调度的关键。 调度的步骤&#xff1a; 1 创建POD的时候每一个POD都会有一个叫NodeName的…

老板为何都对项目经理毕恭毕敬!因为这个职位一念成佛一念成魔

hello宝子们...我们是艾斯视觉擅长ui设计和前端开发10年经验&#xff01;希望我的分享能帮助到您&#xff01;如需帮助可以评论关注私信我们一起探讨&#xff01;致敬感谢感恩&#xff01; 老板为何都对项目经理毕恭毕敬&#xff01;因为这个职位一念成佛一念成魔 曾几何时&am…

Linux:命名管道及其实现原理

文章目录 命名管道指令级命名管道代码级命名管道 本篇要引入的内容是命名管道 命名管道 前面的总结中已经搞定了匿名管道&#xff0c;但是匿名管道有一个很严重的问题&#xff0c;它只允许具有血缘关系的进程进行通信&#xff0c;那如果是两个不相关的进程进行通信&#xff0…

《佛法修学概要》009-012集研讨

课程摘要 9、只有走出心中的妄想&#xff0c;才可能接觸彌陀的光明&#xff01; 佛陀在經典裡講出一個譬喻&#xff0c;說有一座動物園&#xff0c;這座動物園關了很多動物。其中有一隻袋鼠&#xff0c;就是澳洲那種很會跳的袋鼠。動物園的管理員&#xff0c;給牠圈了一個十公尺…

《合成孔径雷达成像算法与实现》Figure5.16

clc clear close all距离向参数 R_eta_c 20e3; % 景中心斜距 Tr 25e-6; % 发射脉冲时宽 Kr 0.25e12; % 距离向调频率 Fr 7.5e6; % 距离向采样率 Nrg 256; % 距离线采样点数 Bw abs(Kr*Tr); …

【vue oidc-client】invalid_requestRequest Id: 0HN0OOPFRLSF2:00000002

需求&#xff1a;完成统一登录&#xff0c;需要从三方平台跳到我们的平台。 oidc-client报错记录。这个一般是配置信息出错&#xff0c;需要和三方平台进行沟通&#xff0c;一定要把client_id&#xff0c;密钥进行对应&#xff1b; 同时关于此次出错还修改了以下代码&#xff…

主成分分析(PCA)Python

实际问题研究中&#xff0c;常常遇到多变量问题&#xff0c;变量越多&#xff0c;问题往往越复杂&#xff0c;且各个变量之间往往有联系。于是&#xff0c;我们想到能不能用较少的新变量代替原本较多的旧变量&#xff0c;且使这些较少的新变量尽可能多地保留原来变量所反映的信…

按配置数据绘制配置型地图marker的icon,自定义marker

一、需求 需要自定义配置数据的marker&#xff0c;其中图片内容要灵活可配置自动生成。此处项目用的百度地图。 效果图&#xff1a; 二、思路 用背景图canvas绘制数字的方式生成icon的图片资源。 再将icon生成对应地图marker。 三、代码 canvasImg.js <!-- * descrip…

进程地址空间(Linux)

进程地址空间 一、引入概念1. 程序的地址分布2. 线性地址和物理地址 二、进程地址空间1. 初步认识2. 地址空间和物理内存的联系3. 区域划分4. 拓展——关于“线” 三、进一步理解进程地址空间四、页表总结 一、引入概念 1. 程序的地址分布 测试代码&#xff1a; #include &l…

Nginx安装以及具体应用

文章目录 Centos7安装NginxNginx命令Nginx具体应用反向代理 location指令说明负载均衡动静分离 Nginx.conf配置详解 Centos7安装Nginx 下载地址&#xff1a;nginx: download 中间这个就是tar.gz包 Centos7安装Nginx 下载nginx-1.16.1.tar.gz上传到Centos7中的/user/local目…

java8 流到底是什么呢?

引入背景&#xff1a; 1、想像写SQL那样操作集合 2、为了提高性能&#xff0c;需要并行处理&#xff0c;并利用多核架构 流到底是什么呢&#xff1f; 流是Java API的新成员&#xff0c;它允许你 以声明性方式处理数据集合&#xff08;通过查询语句来表达&#xff0c;而不是临时…