当前位置: 首页 > news >正文

哈希及其模拟实现

1.哈希的概念

顺序结构以及平衡树中,元素的关键码与其存储位置之间没有对应的关系。因此,在查找一个元素时,必须要经过关键码的多次比较。顺序查找的时间复杂度为O(N),平衡树中为树的高度,即O(log_2 N),搜索的效率取决于搜索过程中元素的比较次数。理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过某种函数(HashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

1.1 哈希思想

哈希(Hash)是一种映射思想,规定存在一个唯一的哈希值键值之间建立映射关系,由这种思想而构成的数据结构称为哈希表

哈希表中查找数据的时间复杂度可以做到O(1),这是非常高效的,比AVL树还要快。

哈希表中插入和查找数据的具体操作如下:
插入数据:根据当前待插入的元素的键值,计算出哈希值,并存入到相应的位置中。
查找元素:根据待查找元素的键值,计算出哈希值,判断对应的位置中存储的值是否与键值相等。

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

例如:数据集合{1,7,6,4,5,9};哈希函数设置为:hash(key)=key%capacity;capacity为存储元素底层空间总的大小。

显然,这个哈希表并没有把所有的位置都填满,数据分布无序且分散。因此,哈希表又称为散列表

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快。问题:按照上述哈希方式,向集合中插入元素44,会出现什么问题?这就是下面要介绍的哈希冲突。

1.2 哈希冲突

对于两个数据元素的关键字Ki和Kj(i!=j),有Ki!=Kj,但有Hash(Ki)==Hash(Kj),即不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或者哈希碰撞。把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。发生哈希冲突该如何处理呢?
引起哈希冲突的一个原因可能是:哈希函数设计不够合理。
哈希函数设计原则:
1、哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表运允许有m个地址时,其值域必须在0到m-1之间;
2、哈希函数计算出来的地址能均匀分布在整个空间中;
3、哈希函数应该比较简单。

1.3 常见的哈希函数

1.3.1 直接定址法--(常用)

取关键字的某个线性函数为单列地址:Hash(Key)=A*Key+B。
优点:简单、均匀;
缺点:需要事先知道关键字的分布情况;
使用场景:适合找比较小且连续的情况。

1.3.2 除留余数法--(常用)

设散列表中允许的地址数位m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key)key%p(p<=m),将关键码转换成哈希地址。
除了以上两种常见的哈希函数之外,还有平方取中法、折叠法、随机数法、数学分析法

2.哈希冲突

哈希冲突(哈希碰撞)是面试中的常客,可用通过一个哈希冲突间接引出哈希中的其他知识点。

2.1哈希冲突产生的原因

哈希值键值通过哈希函数计算得出的位置标识符,难以避免重复的问题。比如在下面的哈希表中,存入元素20,哈希值HashI=20%8=4,此时4位置中并没有元素,可以直接存入。

但是如果继续插入元素36,哈希值HashI=36%8=4,此时,4位置处已经有了元素20了,无法继续存入,此时就发生了哈希冲突。

不同的哈希函数引发哈希冲突的概率不同,但最终都会面临哈希冲突这个问题,因此需要一些方法来解决哈希冲突。

2.2 哈希冲突的解决方法

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

2.2.1 闭散列解决哈希冲突

闭散列也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个”空位置中去。那如何寻找下一个空位置呢?有两种常见的方法,1、线性探测;2、二次探测。

2.2.1.1 线性探测

如下的场景中,现在需要插入元素36,先通过哈希函数计算哈希地址,hashAddr为4。因此,36理论上应该插在该位置,但是该位置已经存放了值为20的元素,即发生哈希冲突。此时,继续向后探测、找到可用的位置。如下图所示,将36存入位置0处。

规定:当哈希表中存储的数据量与哈希表的容量比值(负载因子)过大时,扩大哈希表的容量,并重新进行映射。
线性探测:因为有负载因子的存在,所以哈希表一定是有剩余空间的。当发生哈希冲突时,从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

  1. 插入
    (1)通过哈希函数获取待插入元素在哈希表中的位置。
    (2)如果该位置没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。
  2. 2、删除
    采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已经有的元素,若直接删除元素会影响其他元素的搜索。比如直接删除元素4,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。

负载因子=存储的有效数据个数/空间的大小
负载因子越大,冲突的概率越高,增删查改的效率越低;
负载因子越小,冲突的概率越低,增删查改的效率越高,但是空间利用率低,浪费多。
负载因子<1,就能保证发生哈希冲突时一定能找到空位置。

2.2.1.2 线性探测的代码实现

存储数据结构的定义:闭散列中存储的数据至少包含两个信息:键值对、状态表示。键值对既可以是K也可以是K/V,我们这里实现的是K/V

(1)状态

区分哈希表的一个位置有没有数据,如果用两种状态标识,在(1)或不在(0),那么就会带来两个问题:
1、0表示不在,那么如何存储数据0呢?
2、如果数据发生冲突,当前位置和后面位置都存放的是冲突数据,假如当前位置的数据被删除了,那么查找key时发现当前位置状态为不在时,那么就不会再向后查找了。
因此,要用3个状态位分别表示空、已存在、已删除,用枚举表示状态位。
空EMPTY--初始状态;
存在EXITS--插入数据后的状态;
删除DELETE--删除数据后的状态。
其实也可以分为可用/不可用两种状态,细分出EMPTY与DELETE是为了在进行探测时,提高效率。
探测时,如果探测到空,就不必再往后继续探测了,因为后面必然不存在我们想要找的数据。如果存在,这里就不会为空了。
所以闭散列中的存储数据结构可以用一个结构体定义。

//枚举表示某位置的状态
enum State
{EMPTY,EXITS,DELETE,
};
(2)定义HashData

哈希数据应包含两个成员:数据状态

//定义结构体存储数据
template<class T>
struct HashData
{T _data;State _state;
};
(3)哈希表

哈希表包含两个成员:哈希数据、存储的有效数据的个数,模板有3个参数K,T,KeyOfT。
①由于不知道key是K还是pair,所以需要定义两个模板参数K、T来包含key是K或者pair的两种情况;
②由于不知key的数据类型是int还是string、pair、struct,计算key的映射位置时需要取模,但是只能对int类型进行取模,string、pair、struct无法直接取模,KeyOfT作为仿函数,它的实例可用分别应对这些类型的取模。

//定义一个哈希表类
template<class K,class T,class KeyOfT>
class MyHashTable
{typedef HashTable<T> HashData;
public://...private:vector<HashData> _tables;//底层顺序表,用于存储数据size_t _num = 0;//保存的有效数据的个数
};
(4)查找函数

①无论传给哈希表的数据是K还是pair,查找时,都需要用K做key来进行查找;
②计算元素位置;
③如果当前位置元素为key,那么就返回该元素,否则可能发生了冲突,继续向后探测。

//1、查找数据
HashData* Find(const K& key)
{if (_tables.size() == 0)return nullptr;KeyOfT keyoft;size_t hashI = key % _tables.size();//取余查找位置//线性探测size_t i = 1;size_t index = hashI;while (_tables[index]._state != EMPTY){if (keyoft(_tables[index]._data) == key&& _tables[index]._state == EXITS){return &_tables[index];}//如果哈希表中的值与key值不相等,或者与key值相等,但是_state为DELETE,//则继续往后查找index = hashI + i;//线性查找  //index=hashI+i*i;//二次查找index %= _tables.size();//取余防止越界++i;//如果已经查找一圈,那么说明全是存在+删除if (index == hashI){break;}}return nullptr;
}
(5)插入函数

①根据额键值计算出下标(哈希值);
②线性探测,找到可用的位置[空/删除];
③插入数据,有效数据量+1。

//2、插入一个数据
bool Insert(const T& data)
{KeyOfT keyoft;if (Find(keyoft(data)))return false;//扩容//假如负载因子大于或者等于7时,以及哈希表的容量为0时,则需要进行扩容操作。//此时,在计算负载因子时应注意哈希表容量的大小不能为0。//取余查找位置,计算data中的key值在表中映射的位置size_t hashI = keyoft(data) % _tables.size();//线性探测写法size_t i = 1;size_t index = hashI;//状态为空就插入节点while (_tables[index]._state == EXITS){index = hashI + i;index %= _tables.size();//取余防止越界++i;}_tables[index]._data = data;_tables[index]._state = EXITS;_num++;return true;
}

如果想降低踩踏发生的概率,可用将线性探测改为二次探测

HashI += (i * i);	//二次探测

以上就是插入操作的具体实现,此时面临着一个棘手的问题:扩容问题。
当数据第一次插入或负载因子达到临界值时,需要进行扩容(因为vector<HashData> _tables一开始为空,所以第一次插入数据时也要扩容)。
当空间扩容后,容量发生了改变,这就意味着数据与下标的映射关系也发生了改变,需要更新数据,即重新进行线性探测。
扩容可分为传统写法和现代写法。
传统写法:

//2、插入一个数据
bool Insert(const T& data)
{KeyOfT keyoft;if (Find(keyoft(data)))return false;//假如负载因子大于或者等于7时,以及哈希表的容量为0时,则需要进行扩容操作。//此时,在计算负载因子时应注意哈希表容量的大小不能为0。if (_tables.size() == 0 || _num * 10 / _tables.size() >= 7){//1、开辟是原旧表2倍大小的新表(不一定是2倍);//2、遍历旧表的数据,重新计算原旧表数据在新表中的位置;//3、释放旧表//1、写法1:传统的开空间vector<HashData> newtables;//开辟一个新的vector顺序表size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;newtables.resize(newsize);//重新设置newtables的容量大小for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]._state == EXITS){//计算原旧表数据在新表中的位置,并处理冲突size_t index = keyoft(_tables[i]._data) % newtables.size();while (newtables[index]._state == EXITS){++index;if (index == newtables.size()){index = 0;}}newtables[index] = _tables[i];}}_tables.swap(newtables);}//插入//...
}

传统写法的思路:创建一个容量足够的信标,将原表中的数据映射至新表中,映射完成后,交换新表和原表,目的是为了更新当前哈希表对象中的表。
关于平衡因子的控制:根据别人的试验结果,哈希表中存储的有效数据量超过哈希表容器的70%时,推荐进行扩容。假设容量为M,存储的额有效数据量为N,两者比率N/M==0.7时进行扩容。因为我们这里的容量和数据量都是整数,所以在等式两边*10,可得N*10/M==7。

传统写法比较繁琐,下面来看看现代写法:

//2、插入一个数据
bool Insert(const T& data)
{KeyOfT keyoft;if (Find(keyoft(data)))return false;//假如负载因子大于或者等于7时,以及哈希表的容量为0时,则需要进行扩容操作。//此时,在计算负载因子时应注意哈希表容量的大小不能为0。if (_tables.size() == 0 || _num * 10 / _tables.size() >= 7){//1、开辟是原旧表2倍大小的新表(不一定是2倍);//2、遍历旧表的数据,重新计算原旧表数据在新表中的位置;//3、释放旧表//写法2:扩容的现代写法MyHashTable<K, T, KeyOfT> newht;size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;newht._tables.resize(newsize);for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]._state == EXITS){newht.Insert(_tables[i]._data);}}_tables.swap(newht._tables);
}//插入//...
}

其实传统写法中的插入部分逻辑与Insert中的插入操作重复了,因此我们可以借助现代思想,创建一个容量足够的哈希表,将原表中的数据遍历插入新的哈希表中,插入的过程调用Insert,代码很简洁,并且不容易出错。
细节:需不需要将当前对象中的有效数据量_n进行更新?
答案是不需要,往新的哈希表中插入_n个数据,意味着无论是新的哈希表还是当前对象,他们的有效数据量都是一致的,因此不需要更新。

(6)删除

删除操作就十分简单了,获取待删除数据,将其中的状态改为删除--DELETE即可,不必清空数据,只要访问不到就行了。当然,有效数据量-1。

//3、删除一个元素
bool Erase(const T& key)
{HashData* ret = Find(key);if (ret){ret->_state = DELETE;--_num;return true;}else{return false;}
}

像这种线性探测(暴力探测)可用解决哈希冲突的问题,但是会带来新的问题:踩踏。
踩踏:元素的存储位置被别的元素占用了,于是该元素也只能被迫线性探测,引发连锁反应,插入、查找等操作都会越来越慢。哈希冲突越多、效率越低。
优化方案:二次探测,每次向后探测i^2步,尽量减少踩踏。尽管如此,闭散列的实际效果不尽人意。闭散列的实战价值不大,因此只做简单了解即可,真正的重点是开散列。

2.2.2 开散列解决哈希冲突

所谓开散列就是在 原存储位置处 带上一个单链表,如果发生哈希冲突,就将冲突的值依次挂载即可,因此也叫做链地址法、开链法、哈希桶。
开散列中不需要负载因子,开散列的每个桶中存放的都是哈希冲突的元素,如果每个位置都被存满了,直接扩容就好了,当然扩容后也需要重新建立映射关系。
 

开散列中进行查找时,需要先根据哈希值找到对应的位置,并在单链表中进行遍历。一般情况下,单链表的长度不会太长,因为扩容后,整体长度会降低。
如果单链表真的过长了(几十个节点),我们还可以将其转为红黑树,此时效率依旧非常高。

2.2.2.1 哈希仿函数

在闭散列中,已经实现了Hash仿函数,用来获取哈希表中的元素key,方便后续计算映射位置。

//仿函数
template<class K>
struct _Hash
{const K& operator()(const K& key){return key;}
};

模板特化:string元素使用频率较高,进行模板特化。

//特化,针对某些类型进行特殊处理
template<>
struct _Hash<string>
{size_t operator()(const string& key){//BKDR Hashsize_t hash = 0;for (size_t i = 0; i < key.size(); ++i){hash *= 131;hash += key[i];}return hash;}
};
2.2.2.2 存储节点结构的定义

哈希桶中需要维护一个单链表,因此需要一个_next指针指向下一个节点。

template<class T>
struct HashNode
{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data),_next(nullptr){}
};

因为引入了单链表,所以哈希桶的存储类型也变成了节点指针

template<class K,class T,class KeyOfT,class Hash=_Hash<K>>
class MyHashTable
{typedef HashNode<T> Node;
public://...private:vector<Node*> _tables;size_t _num;//记录表中存储的数据的个数
};
2.2.2.3 析构函数

因为有单链表,所以在对象析构时,需要手动遍历其中的节点,将其释放,避免内存泄漏。

~MyHashTable()
{Clear();
}void Clear()
{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;}
}

为什么哈希表(闭散列)中不需要些析构函数?
因为在闭散列中,表中存储的数据不涉及自定义类型的动态内存管理,并且vector在对象调用默认析构时,会被调用其析构函数,释放其中的内存。

2.2.2.4 查找

先计算key在哈希表中的位置,然后在该位置的哈希桶中遍历查找。

Node* Find(const K& key)
{if (_tables.size() == 0)return nullptr;KeyOfT keyoft;size_t index = HashFunc(key) % _tables.size();Node* cur = _tables[index];while (cur){if (keyoft(cur->_data) == key){return cur;}else{cur = cur->_next;}}return nullptr;
}
2.2.2.5 插入

在进行数据插入时,既可以尾插,也可以头插,因为桶中的存储顺序没有要求。为了操作简单,我们选择头插。同样的,哈希桶在扩容时,也有传统写法和现代写法,这里采用传统写法。

bool Insert(const T& data)
{KeyOfT keyoft;if (Find(data) != nullptr)return false;//如果负载因子等于1,则增容,避免大量的哈希冲突if (_tables.size() == _num){//使用传统的增容写法vector<Node*> newtables;size_t newsize = _tables.size() == 0 ? 10 : 2 * _tables.size();newtables.resize(newsize);for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];while (cur){//整个过程为单链表的头插//将旧表中的节点取下来,并重新计算在新表中的位置,并插入到新表中Node* next = cur->_next;//计算在新表中的位置size_t index = HashFunc(keyoft(cur->_data)) % newtables.size();cur->_next = newtables[index];newtables[index] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}//计算数据在表中映射的位置size_t index = HashFunc(keyoft(data)) % _tables.size();//1、先检查这个值子在不在表中Node* cur = _tables[index];while (cur){if (keyoft(cur->_data) == keyoft(data)){return false;}else{cur = cur->_next;}}//2、头插到挂的链表中(尾插也可以)Node* newnode = new Node(data);newnode->_next = _tables[index];_tables[index] = newnode;++_num;return true;
}

这里的传统写法有一个很巧妙的地方:节点回收,既然旧表中节点不用了,那么我可用直接把其中的节点转移至新表中,这样可用提高效率。

2.2.2.6 删除

①计算key在表中的位置;
②要删除的数据是不是该位置的第一个哈希桶,如果是,那就让哈希表的第一个节点变成第二个桶,否则让这个桶的前一个桶指向这个桶的下一个桶。

bool Erase(const K& key)
{KeyOfT keyoft;size_t index = HashFunc(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[index];while (cur){if (keyoft(cur->_data) == key){if (prev == nullptr){//表示要删除的值在第一个节点_tables[index] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}else{prev = cur;cur = cur->_next;}}return false;
}

完整代码可用参考:哈希及其模拟实现

相关文章:

哈希及其模拟实现

1.哈希的概念 顺序结构以及平衡树中&#xff0c;元素的关键码与其存储位置之间没有对应的关系。因此&#xff0c;在查找一个元素时&#xff0c;必须要经过关键码的多次比较。顺序查找的时间复杂度为O(N)&#xff0c;平衡树中为树的高度&#xff0c;即O(log_2 N)&#xff0c;搜…...

Day 32 动态规划part01

今天正式开始动态规划! 理论基础 无论大家之前对动态规划学到什么程度,一定要先看 我讲的 动态规划理论基础。 如果没做过动态规划的题目,看我讲的理论基础,会有感觉 是不是简单题想复杂了? 其实并没有,我讲的理论基础内容,在动规章节所有题目都有运用,所以很重要!…...

【娱乐项目】竖式算术器

Demo介绍 一个加减法随机数生成器&#xff0c;它能够生成随机的加减法题目&#xff0c;并且支持用户输入答案。系统会根据用户输入的答案判断是否正确&#xff0c;统计正确和错误的次数&#xff0c;并显示历史记录和错题记录。该工具适合用于数学练习&#xff0c;尤其适合练习基…...

XRP 深度解析:从技术到 Meme 币交易指南

撰文&#xff1a;Ignas | DeFi Research 编译&#xff1a;Yuliya&#xff0c;PANews 本文来源Techub News:XRP 深度解析&#xff1a;从技术到 Meme 币交易指南 在当前加密货币市场&#xff0c;一个令人瞩目的现象正在上演&#xff1a;XRP 在短短一个月内暴涨 3.5 倍&#xf…...

机器学习周志华学习笔记-第13章<半监督学习>

机器学习周志华学习笔记-第13章&#xff1c;半监督学习&#xff1e; 卷王&#xff0c;请看目录 13半监督学习13.1 生成式方法13.2 半监督SVM13.3 基于分歧的方法13.4 半监督聚类 13半监督学习 前面我们一直围绕的都是监督学习与无监督学习&#xff0c;监督学习指的是训练样本包…...

【MySql】navicat连接报2013错误

navicat连接mysql报2013错误 报错信息1、检验Mysql数据库是否安装成功2、对Mysql的配置文件进行修改配置2.1、找到配置文件2.2、Linux下修改配置文本 3、连接进入mysql服务4、在mysql下执行授权命令 报错信息 Navicat连接mysql报2013错误 2013-Lost connection to MYSQL serve…...

【微服务】Docker

一、Docker基础 1、依赖的兼容问题&#xff1a;Docker允许开发中将应用、依赖、函数库、配置一起打包&#xff0c;形成可移植镜像Docker应用运行在容器中&#xff0c;使用沙箱机制&#xff0c;相互隔离。 2、如何解决开发、测试、生产环境有差异的问题&#xff1a;Docker镜像…...

renderExtraFooter 添加本周,本月,本年

在 Ant Design Vue 中&#xff0c;a-date-picker 组件提供了一个 renderExtraFooter 属性&#xff0c;可以用来渲染额外的页脚内容。你可以利用这个属性来添加“本周”、“本月”和“本年”的按钮。下面是如何在 Vue 2 项目中实现这一功能的具体步骤&#xff1a; 1.确保安装了…...

警惕开源信息成为泄密源头

文章目录 前言一、信息公开需谨慎1、警惕采购招标泄密。2、警惕信息公开泄密。3、警惕社交媒体泄密。 二、泄密风险需严防1、健全制度&#xff0c;明确责任。2、加强管控&#xff0c;严格审查。3、提高意识&#xff0c;谨言慎行。 前言 大数据时代&#xff0c;信息在网络空间发…...

密码学和CA证书

参考视频 一. 公钥私钥的理解 我们提到的使用公钥私钥进行加密解密只是一种口头表达方式&#xff0c;准确来说应该是公钥和私钥通过加密 算法生成&#xff0c;也需要通过配合加密算法进行解密。而不是直接用公钥和私钥进行加密解密。 二. 对称加密和非对称加密算法 1. 非对…...

Python 入门教程(2)搭建环境 | 2.4、VSCode配置Node.js运行环境

文章目录 一、VSCode配置Node.js运行环境1、软件安装2、安装Node.js插件3、配置VSCode4、创建并运行Node.js文件5、调试Node.js代码 一、VSCode配置Node.js运行环境 1、软件安装 安装下面的软件&#xff1a; 安装Node.js&#xff1a;Node.js官网 下载Node.js安装包。建议选择L…...

Nginx Web服务器管理、均衡负载、访问控制与跨域问题

Nginx Web 服务器的均衡负载、访问控制与跨域问题 Nginx 的配置 1. 安装Nginx 首先安装Nginx apt install nginx -ycaccpurgatory-v:~$ sudo apt install nginx [sudo] password for cacc: Reading package lists... Done Building dependency tree... Done Reading state i…...

排序学习整理(2)

上集回顾 排序学习整理&#xff08;1&#xff09;-CSDN博客 2.3 交换排序 交换排序的基本思想是&#xff1a;根据序列中两个记录键值的比较结果&#xff0c;交换这两个记录在序列中的位置。 特点&#xff1a; 通过比较和交换操作&#xff0c;将键值较大的记录逐步移动到序列…...

【前端】将vue的方法挂载到window上供全局使用,也方便跟原生js做交互

【前端】将vue的方法挂载到window上供全局使用&#xff0c;也方便跟原生js做交互 <template><div><el-button click"start">调用方法</el-button></div> </template> <script> // import { JScallbackProc } from ./JScal…...

单片机的中断系统

作者简介 彭煜轩&#xff0c;男&#xff0c;银川科技学院计算机与人工智能学院&#xff0c;2022级计算机与科学技术8班本科生&#xff0c;单片机原理及应用课程第3组。 指导老师&#xff1a;王兴泽 电子邮件&#xff1a;1696409709qq.com 前言 本篇文章是参考《单片机原理…...

Java基础面向对象(接口高级)

高版本的接口 JDK8.0 普通的公开非抽象方法(默认方法) [public] default 返回值类型 方法名(形参列表){//操作语句 } default: 在此位置身份为非抽象标识 接口中的非抽象方法实现类不需要进行重写且通常不会进行重写 当父类与接口的方法体出现冲突时, 优先执行父类内容 (类优…...

OpenCV圆形标定板检测算法findCirclesGrid原理详解

OpenCV的findCirclesGrid函数检测圆形标定板的流程如下:   findCirclesGrid函数源码: //_image,输入图像 //patternSize,pattern的宽高 //_centers,blobs中心点的位置 //flags,pattern是否对称 //blobDetector,这里使用的是SimpleBlobDetector bool cv::findCirclesGrid(…...

Linux 网卡收包流程如下

Linux 网卡收包流程如下 网卡收到数据包将数据包从网卡硬件缓存移动到服务器内存中(DMA方式&#xff0c;不经过CPU)通过硬中断通知CPU处理CPU通过软中断通知内核处理经过TCP/IP协议栈处理应用程序通过read()从socket buffer读取数据 网卡丢包 我们先看下ifconfig的输出&#…...

普中51单片机——LED流水灯模块

1、GPIO概念 GPIO&#xff08;general purpose intput output&#xff09;是通用输入输出端口的简称&#xff0c;可以通过软件来控制其输入和输出。51 单片机芯片的 GPIO 引脚与外部设备连接起来&#xff0c;从而实现与外部通讯、 控制以及数据采集的功能。 1.1、GPIO分类 &a…...

Linux 各个目录作用

刚毕业的时候学习Linux基础知识&#xff0c;发现了一份特别好的文档快乐的 Linux 命令行&#xff0c;翻译者是happypeter&#xff0c;作者当年也在慕课录制了react等前端相关的视频&#xff0c;通俗易懂&#xff0c;十分推荐 关于Linux的目录&#xff0c;多数博客已有详细介绍…...

【包教包会】CocosCreator3.x——重写Sprite,圆角、3D翻转、纹理循环、可合批调色板、不影响子节点的位移旋转缩放透明度

一、效果演示 重写Sprite组件&#xff0c;做了以下优化&#xff1a; 1、新增自变换&#xff0c;在不影响子节点的前提下位移、旋转、缩放、改变透明度 新增可合批调色板&#xff0c;支持色相、明暗调节 新增圆角矩形、3D透视旋转、纹理循环 所有功能均支持合批、原生平台&…...

腾讯阅文集团Java后端开发面试题及参考答案

Java 的基本数据类型有哪些?Byte 的数值范围是多少? Java 的基本数据类型共有 8 种,可分为 4 类: 整数类型:包括 byte、short、int 和 long。byte 占 1 个字节,其数值范围是 - 128 到 127,用于表示较小范围的整数,节省内存空间,在处理一些底层的字节流数据或对内存要求…...

Kafka如何保证消息可靠?

大家好&#xff0c;我是锋哥。今天分享关于【Kafka如何保证消息可靠&#xff1f;】面试题。希望对大家有帮助&#xff1b; Kafka如何保证消息可靠&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Kafka通过多种机制来确保消息的可靠性&#xff0c;主要包…...

【layui】tabs 控件内通过 iframe 加载url 方式渲染tab页面

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>tabs 内部使用 iframe 嵌套 url 页面</title><link rel"stylesheet" href"../../../libs/layui/layui-2.4.5/dist/css/layui.css"><scr…...

EtherCAT转DeviceNe台达MH2与欧姆龙CJ1W-DRM21通讯案例

一.案例背景 台达MH2设备通常采用EtherCAT通信协议&#xff0c;这种协议在高速实时通信方面表现出色&#xff0c;适合设备之间的快速数据交换和精准控制。而欧姆龙CJ1W-DRM21 模块基于DeviceNet通信协议&#xff0c;DeviceNet在工业现场总线领域应用广泛&#xff0c;侧重于设备…...

清华、智谱团队:「6000亿合成交错语音文本」预训练,问答性能提升近3倍

与基于文本的大语言模型&#xff08;LLM&#xff09;相比&#xff0c;语音语言模型&#xff08;SpeechLM&#xff09;接受语音输入并生成语音输出&#xff0c;能够实现更自然的人机交互。然而&#xff0c;传统的 SpeechLM 因缺乏无监督语音数据和并行语音-文本数据&#xff0c;…...

Python办公——openpyxl处理Excel每个sheet每行 修改为软雅黑9号剧中+边框线

目录 专栏导读背景1、库的介绍①&#xff1a;openpyxl 2、库的安装3、核心代码4、完整代码5、最快的方法(50万行44秒)——表头其余单元格都修改样式总结 专栏导读 &#x1f338; 欢迎来到Python办公自动化专栏—Python处理办公问题&#xff0c;解放您的双手 &#x1f3f3;️‍…...

遇到问题:hive中的数据库和sparksql 操作的数据库不是同一个。

遇到的问题&#xff1a; 1、hive中的数据库和sparksql 操作的数据库不同步。 观察上面的数据库看是否同步 &#xff01;&#xff01;&#xff01; 2、查询服务器中MySQL中hive的数据库&#xff0c;发现创建的位置没有在hdfs上&#xff0c;而是在本地。 这个错误产生的原因是&…...

《网络攻防实践》实践五报告

1.实践内容 防火墙 &#xff08;1&#xff09;基本概念 所谓“防火墙”是指一种将内部网和公众访问网&#xff08;如Internet&#xff09;分开的方法&#xff0c;它实际上是一种建立在现代通信网络技术和信息安全技术基础上的应用性安全技术&#xff0c;隔离技术。越来…...

深入傅里叶级数与傅里叶变换:从基础到应用

傅里叶分析是数学、物理和工程领域的一项基础工具&#xff0c;其核心思想是将复杂的信号或函数分解为一系列简单的正弦和余弦函数的叠加。本文将带你从傅里叶级数入门&#xff0c;逐步深入傅里叶变换的概念及其应用场景。 一、傅里叶级数&#xff1a;周期信号的分解 1. 什么是傅…...

C++入门一

一、命名空间 #include <stdio.h> int rand 0; int main() {printf("hello world\n");printf("%d\n", rand); } 这样是可以运行的&#xff0c;可是当我们加入一个头文件的时候 #include <stdio.h> #include <stdlib.h> int rand 0;…...

Spring Boot 项目集成camunda流程引擎

Spring Boot 项目集成camunda流程引擎 camunda地址 camunda中文地址 使用camunda开源工作流引擎有&#xff1a;通过docker运行、使用springboot集成、部署camunda发行包、基于源代码编译运行等多种方式。 文本重点介绍如何在Spring Boot应用程序中如何集成Camunda Platform开…...

Ubuntu20.04编译安装Carla全过程

前言 Carla的安装是我现阶段解决的第一个问题&#xff0c;现记录一下我安装Carla的过程以及我在安装过程中遇到的一些问题。 一、安装前准备 1、硬件环境 carla是一款基于UE4开发的模拟仿真软件&#xff0c;本身对硬件的要求比较高。 我是windows与ubuntu双系统&#xff0…...

typecho 自动订阅 RSS

昨天学习了一下 RSS 订阅知识之后&#xff0c;经过一番百度搜索&#xff0c;终于在自己的博客上实现了 RSS 订阅功能&#xff0c;但苦于技术有限&#xff0c;不能对 Feed 文件进行定时缓存&#xff0c;每次打开链接都会比较延迟。今天继续对这个功能进行了学习&#xff0c;突然…...

MFC图形函数学习13——在图形界面输出文字

本篇是图形函数学习的最后一篇&#xff0c;相关内容暂告一段落。 在图形界面输出文字&#xff0c;涉及文字字体、大小、颜色、背景、显示等问题&#xff0c;完成这些需要系列函数的支持。下面做简要介绍。 一、输出文本函数 原型&#xff1a;virtual BOOL te…...

11.25.2024刷华为OD

文章目录 HJ76 尼科彻斯定理&#xff08;观察题&#xff0c;不难&#xff09;HJ77 火车进站&#xff08;DFS&#xff09;HJ91 走格子方法&#xff0c;&#xff08;动态规划&#xff0c;递归&#xff0c;有代表性&#xff09;HJ93 数组分组&#xff08;递归&#xff09;语法知识…...

Python练习55

Python日常练习 题目&#xff1a; 补充函数getLastDay(y,m)&#xff0c;其功能是计算y年m月共有多少天。 --------------------------------------------------------- 注意&#xff1a; 部分源程序给出如下。请勿改动主函数main和其它函数中的 任何内容&#xff0c;…...

DDR5和DDR4之区别(The Difference between DDR5 and DDR4)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 DDR是什么意思? DDR5和D…...

分层架构 IM 系统之 Router 架构分析

通过前面文章的分析&#xff0c;我们已经明确&#xff0c;Router 的核心职责是作为中央存储记录在线客户端的连接状态&#xff0c;Router 在本质上是一个内存数据库。 内存是一种易失性的存储&#xff0c;既如此&#xff0c;Router 的可用性如何保障呢&#xff1f; 副本是分布…...

用函数实现模块化程序设计(七)--数组作为函数参数(排序算法)

调用有参函数时&#xff0c;需要实参&#xff0c;实参可以是常量&#xff0c;变量&#xff0c;表达式&#xff0c;数组元素的作用与变量相当&#xff0c;凡是变量出现的地方都可用数组代替&#xff0c;数组元素可以用作函数实参&#xff0c;数组名可以作实参和形参&#xff0c;…...

M31系列LoRa分布式IO模块功能简介

M31系列LoRa 分布式 IO 模块简介 M31系列LoRa分布式IO主机模块是一款强大的无线远程控制与采集设备&#xff0c;该设备采用 LoRa 无线技术&#xff08;内置了无线模块&#xff09;&#xff0c;可通过串口或远程 LoRa 组网设备发送 Modbus RTU 指令进行控制&#xff0c;可搭配E…...

Dockerfile 安装echarts插件给java提供服务

java调用echarts插件&#xff0c;生成图片保存到磁盘然后插入到pptx中报表。 Dockerfile文件内容&#xff1a; #基础镜像&#xff0c;如果本地仓库没有&#xff0c;会从远程仓库拉取 openjdk:8 FROM docker.io/centos:centos7 #暴露端口 EXPOSE 9311 # 避免centos 日志输出 …...

学习threejs,使用VideoTexture实现视频Video更新纹理

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️VideoTexture 视频纹理 二、…...

【二分查找】Leetcode例题

【1】69. x 的平方根 - 力扣&#xff08;LeetCode&#xff09; &#x1f361;解题思路&#xff1a;首先想到的是暴力查找&#xff0c;从1开始依次比较x与num*num的大小&#xff0c;然后找出满足num*num<x且(num1)*(num1)>x的num值&#xff1b;再来看看能不能优化一下&…...

稳定运行的以MySQL数据库为数据源和目标的ETL性能变差时提高性能方法和步骤

在ETL&#xff08;Extract, Transform, Load&#xff09;过程中&#xff0c;数据源和目标都为MySQL数据库时&#xff0c;性能变差可能由多种原因引起。提高以MySQL为数据源和目标的ETL性能需要综合考虑数据库性能、ETL任务的处理方式、硬件资源和工具的选择。通过批量处理、并行…...

Springboot(四十九)SpringBoot3整合jetcache缓存

上文中我们学习了springboot中缓存的基本使用。缓存分为本地caffeine缓存和远程redis缓存。现在有一个小小的问题,我想使用本地caffeine缓存和远程redis缓存组成二级缓存。还想保证他们的一致性,这个事情该怎么办呢? Jetcache框架为我们解决了这个问题。 ‌JetCache‌是一个…...

JVM 参数前缀 -XX: 含义 详解

在 Java 虚拟机&#xff08;JVM&#xff09;中&#xff0c;参数前缀 -XX: 表示的是 JVM 的非标准&#xff08;实验性&#xff09;选项。这些参数用于调整和优化 JVM 的性能、垃圾回收行为、内存分配策略等。 1. 参数分类 -XX: 参数大致分为三类&#xff0c;根据其格式区分&…...

【Mac】安装Gradle

1、说明 Gradle 运行依赖 JVM&#xff0c;需要先安装JDK&#xff0c;Gradle 与 JDK的版本对应参见&#xff1a;Java Compatibility IDEA的版本也是有要求Gradle版本的&#xff0c;二者版本对应关系参见&#xff1a;Third-Party Software and Licenses 本次 Gradle 安装版本为…...

证明切平面过定点的曲面是锥面

目录 证明&#xff1a;切平面过定点的曲面是锥面. 证明&#xff1a;切平面过定点的曲面是锥面. 证明&#xff1a; 方法一&#xff1a; 设曲面 S : r r ( u , v ) S:\mathbf{r}\mathbf{r}(u,v) S:rr(u,v)的切平面过定点 P 0 P_0 P0​,其位置向量为 p 0 . \mathbf{p}_0. p0​…...

【WPS】【EXCEL】将单元格中字符按照分隔符拆分按行填充到其他单元格

问题&#xff1a;实现如下图的效果 解答&#xff1a; 一、函数 IFERROR(TRIM(MID(SUBSTITUTE($A$2,",",REPT(" ",LEN($A$2))),(ROW(A1)-1)*LEN($A$2)1,LEN($A$2))),"") 二、在单元格C2中填写如下函数 三、全选要填充的单元格并且按CTRLD 函数…...