哈希:哈希函数 | 哈希概念 | 哈希冲突 | 闭散列 | 开散列

在这里插入图片描述

🌈个人主页: 南桥几晴秋
🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git
🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈
本科在读菜鸡一枚,指出问题及时改正

文章目录

  • unordered系列关联式容器
    • unordered_map
      • unordered_map介绍
        • 接口说明
          • unordered_map 的构造
          • unordered_map的容量
          • unordered_map的迭代器
          • unordered_map的元素访问
          • unordered_map的查询
          • unordered_map的修改操作
          • unordered_map的桶操作
    • unordered_set文档介绍
  • 底层结构
    • 哈希概念
    • 哈希冲突
      • 解决哈希冲
        • 闭散列
          • 线性探测
            • 模拟实现
          • 二次探测
        • 开散列
          • 模拟实现

unordered系列关联式容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2N log2N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。

unordered_map

unordered_map介绍

介绍文档

  1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value
  2. unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的valueunordered_map将相同哈希值的键值对放在相同的桶中。
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value
  6. 它的迭代器至少是前向迭代器。
接口说明
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,没有一个默认值
unordered_map的查询
函数声明功能介绍
iterator find(const K& key)返回key在哈希桶中的位置
size_t count(const K& key)返回哈希桶中关键码为key的键值对的个数

注意:unordered_map中key是不能重复的,因此count函数的返回值最大为1

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所在的桶号

unordered_set文档介绍

setunordered_set使用方法类似
文档介绍

底层结构

unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构

哈希概念

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

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

哈希也叫做散列,是一种映射,把值和值进行一对一或者一对多关联。

哈希表:使用哈希思想实现的数据结构。一般都是将值和存储位置建立映射关系。

当向该结构中:

  • 插入元素
    根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放搜索元素
  • 对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

存在的问题:

  • 值很分散时,效率不高
  • 有些值不好映射,比如:string、结构体对象

在这里插入图片描述

除留余数法解决了数据分散问题,但是会导致哈希冲突,不同的值可能会映射到相同位置。例如下面10001和11就映射到一位置。
在这里插入图片描述

哈希冲突

对于两个数据元素的关键字 k i k_i ki k j k_j kj(i != j),有 k i k_i ki != k j k_j kj,但有:Hash( k i k_i ki) ==Hash( k j k_j kj),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。

解决哈希冲

闭散列

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。也就是说,自己位置被占了,去抢别的位置。冲突次数越高,效率越低。

寻找下一个空位置:

线性探测

从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
在这里插入图片描述
插入:
通过哈希函数获取待插入元素在哈希表中的位置
如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。
删除:
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。
扩容:
散列表的载荷因子定义为:α=填入表中的元素个数 / 散列表的长度
负载因子越高,冲突率越高,效率越低;负载因子越小,冲突效率越低,效率就越高,空间利用率就越低。

模拟实现

在这里插入图片描述

扩容机制:
首先扩容,然后原有的值不能直接拷贝,需要重新映射。
在这里插入图片描述

#include<iostream>
#include<vector>
#include<utility>
using namespace std;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:HashTable(){_tables.resize(10);}bool Insert(const pair<K, V>& kv){if (Find(kv.first)) return false;//扩容if (_n * 10 / _tables.size() >= 7){//size_t newsize = _tables.size() * 2;//vector<HashData<K, V>> newtables(newsize);//for (size_t i = 0; i < _tables.size(); i++)//{}size_t newsize = _tables.size() * 2;HashTable<K, V> newHT;newHT._tables.resize(newsize);for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST){newHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);}size_t hashi = kv.first % _tables.size();//线性探测while (_tables[hashi]._state == EXIST)  //如果该位置存在继续往后探测{++hashi;hashi %= _tables.size();}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;}HashData<K, V>* Find(const K& key){size_t hashi = 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){HashData<K, V>* ret = Find(key);if (ret == nullptr) return false;else{ret->_state = DELETE;--_n;return  true;}}private:vector<HashData<K, V>> _tables;size_t _n = 0;  //有效数据个数
};

在这里插入图片描述

上述类型是整型,可以直接进行取模运算,但是对于其他类型,例如:string,自定义类型该如何处理?
对于其他类型,可以先转换成整型,然后进行映射。注意这里不是类型转换!key不支持强转整型取模,需要自己提供转换成整型的仿函数。
在书写代码时,需要增加一个仿函数,用于转换类型:

#include<iostream>
#include<vector>
#include<string>
#include<utility>
using namespace std;enum State
{EMPTY,EXIST,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;}
};struct StringHashFunc
{size_t operator()(const string& key){return key[0];}
};template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:HashTable(){_tables.resize(10);}bool Insert(const pair<K, V>& kv){if (Find(kv.first)) return false;//扩容if (_n * 10 / _tables.size() >= 7){//size_t newsize = _tables.size() * 2;//vector<HashData<K, V>> newtables(newsize);//for (size_t i = 0; i < _tables.size(); i++)//{}size_t newsize = _tables.size() * 2;HashTable<K, V, Hash> newHT;newHT._tables.resize(newsize);for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST){newHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);}Hash hs;size_t hashi = hs(kv.first) % _tables.size();//线性探测while (_tables[hashi]._state == EXIST)  //如果该位置存在继续往后探测{++hashi;hashi %= _tables.size();}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;}HashData<K, V>* Find(const K& key){Hash hs;size_t hashi = hs(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){HashData<K, V>* ret = Find(key);if (ret == nullptr) return false;else{ret->_state = DELETE;--_n;return  true;}}private:vector<HashData<K, V>> _tables;size_t _n = 0;  //有效数据个数
};

在这里插入图片描述

对于处理string类型时,会出现冲突,因为英文单词中首字母相同的单词有很多,我们可以对一个单词中所有字母的ASII码值进行相加:

struct StringHashFunc
{size_t operator()(const string& key){size_t hash = 0;for (auto ch : key){hash += ch;}return hash;}
};

但是这种方法仍然有缺陷 ,例如:abcdbcadaadd这几个的ASII码仍然还是有冲突。

字符串哈希算法

二次探测

线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为: H i H_i Hi = ( H 0 H_0 H0 + i 2 i^2 i2 )% m, 或者: H i H_i Hi = ( H 0 H_0 H0 - i 2 i^2 i2 )% m。其中:i =1,2,3…, H 0 H_0 H0是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小。

开散列

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

在这里插入图片描述
从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

模拟实现

插入时,需要实现头插:先将待插入的元素插入进去,然后使它变成头结点。
在这里插入图片描述

扩容:

  • 方案一:将旧表中的数据通过映射的方式拷贝到新表中,然后再释放掉旧表中的内容,就表中虽然vector数组可以通过析构的方式释放掉,但是对应的接点删除效率不高。
if (_n == _tables.size())
{size_t newsize = _tables.size() * 2;HashTable<K, V, Hash> newHT;newHT._tables.resize(newsize);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){newHT.Insert(cur->_kv);cur = cur->_next;}}_tables.swap(newHT._tables);
}
  • 方案二:将节点从旧表中拿出来,通过映射的方式放在新表中
// 扩容:负载因子为1(平均每个桶下面一个)if (_n == _tables.size()){//方案二:vector<Node*> newTables(_tables.size() * 2, nullptr);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;//头插到新表的位置size_t hashi = cur->_kv.first % newTables.size();cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}

删除:
再删除的时候需要考虑删除的数据是头结点还是中间的节点,如果是头结点直接删除即可,中间节点之间让前一个节点指向被删除节点的下一个节点。

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};// 特化
template<>
struct HashFunc<string>
{// abcd// bcad// aadd// BKDRsize_t operator()(const string& key){size_t hash = 0;for (auto ch : key){hash *= 131;hash += ch;}return hash;}
};bool Erase(const K& key)
{
size_t hashi = 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;
}

完整代码:

namespace gwj_hash_bucket
{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{typedef HashNode<K, V> Node;public:HashTable(){_tables.resize(10, nullptr);_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;}}bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;Hash hs;// 扩容:负载因子为1(平均每个桶下面一个)if (_n == _tables.size()){//方案一://	size_t newsize = _tables.size() * 2;//	HashTable<K, V, Hash> newHT;//	newHT._tables.resize(newsize);//	for (size_t i = 0; i < _tables.size(); i++)//	{//		Node* cur = _tables[i];//		while (cur)//		{//			newHT.Insert(cur->_kv);//			cur = cur->_next;//		}//	}//	_tables.swap(newHT._tables);//方案二:vector<Node*> newTables(_tables.size() * 2, nullptr);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;//头插到新表的位置size_t hashi = hs(cur->_kv.first) % newTables.size();cur->_next = newTables[hashi];newTables[hashi] = 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;cur = cur->_next;}return nullptr;}bool Erase(const K& key){Hash hs;size_t hashi = hs(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;}}private:vector<Node*> _tables;size_t _n;};void TestHT1(){int a[] = { 10001,11,55,24,19,12,31,4,34,44 };HashTable<int, int> ht;for (auto e : a){ht.Insert(make_pair(e, e));}ht.Insert(make_pair(32, 32));ht.Insert(make_pair(32, 32));ht.Erase(31);ht.Erase(11);}void TestHT2(){HashTable<string, int> ht;ht.Insert(make_pair("sort", 1));ht.Insert(make_pair("left", 1));ht.Insert(make_pair("insert", 1));}}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

GEE APP——Bellingcat 雷达影像监测平台分析

简介 许多军用雷达在开启时会干扰开源卫星图像。 一个新工具可以让任何人监控这些雷达部署的时间和地点。 该资源库包含该工具的源代码。 下面是该工具使用时的截图,其中有五个标注组件,我们将逐一查看。 在此示例中,该工具以驻扎在沙特阿拉伯达曼的 MIM-104 爱国者 PAC-2 …

早上醒来嗓子干、喉咙痛、咳嗽……快用这个润养好物,给嗓子做个spa,让身体润起来~

进入秋季&#xff0c;很多人出现了眼睛干涩、大便干燥、嘴唇干裂、咽喉疼痛等症状&#xff0c;虽说这些还能够忍受&#xff0c;但它却影响了正常的饮食和休息。 秋季气候干燥&#xff0c;外界燥邪侵犯肺部&#xff0c;易伤津液&#xff0c;肺失滋润&#xff0c;清肃失司&#x…

探讨马丁格尔策略应用的3问和昂首平台的3答

问&#xff1a;为什么在使用马丁格尔策略时要如此谨慎?毕竟最大的市场波动可能根本不会发生。 答&#xff1a;让我们以一个具体的例子来说明这个问题。假设我们进行交易&#xff0c;计算出一个小于最大预期值的市场动量&#xff0c;比如说这个值为90便士。试想&#xff0c;如…

Acunetix v24.8 发布,新增功能概览

Acunetix v24.8 发布&#xff0c;新增功能概览 Acunetix v24.8 (Linux, Windows) - Web 应用程序安全测试 Acunetix | Web Application Security Scanner 请访问原文链接&#xff1a;https://sysin.org/blog/acunetix/&#xff0c;查看最新版。原创作品&#xff0c;转载请保…

Linux---文件(1)---初识文件

目录 预备知识 文件操作接口 打开文件接口 重定向与文件操作关系 "w"方式与重定向 “a”方式与追加重定向 写入文件接口 读取文件接口 系统调用接口 参数解析 预备知识 我们知道&#xff0c;创建出一个空文件也要在内存中占空间。 文件文件内容文件属性 操…

网络安全服务基础Windows--第12节-域与活动目录

工作组 在Windows环境中配置⼯作组相对简单&#xff0c;适合⼩型⽹络环境&#xff0c;如家庭或⼩型办公室⽹络。⼯作组通过简单的⽹络共享和本地管理来实现资源共享&#xff0c;⽽不依赖于中央控制的服务器。 ● 定义&#xff1a;⼯作组是⼀种对等⽹络模型&#xff0c;在这种…

总结一下windows电脑字体模糊的优化方案

问题&#xff1a;谷歌浏览器上页面显示的字体非常细&#xff0c;有点费眼睛了&#x1f47e; 解决方案&#xff1a; 方案1&#xff1a;手动调整ClearType文本。方案2&#xff1a;英伟达显卡控制面板->管理3d设置->关闭全局平滑FXAA&#xff08;如果某个软件需要使用平滑处…

【vue css】background设置背景图片不显示问题

问题&#xff1a; 如上图所示&#xff0c;添加背景图片页面没有显示 解决&#xff1a; 添加background-position: center center 即可显示 但是不知道为什么添加这个属性就可以&#xff0c;求大神解惑

出现 /www/server/mysql/bin/mysqld: Shutdown complete 的解决方法

目录 1. 基本知识1.1 查找my.cnf目录1.2 配置错误日志2. 问题所示3. 原理分析4. 解决方法1. 基本知识 主要补充一些基本知识的拓展 1.1 查找my.cnf目录 查看mysql默认读取my.cnf的目录: mysql --help|grep my.cnf 截图如下:(为了方便查看具体使用的配置文件在哪个路径)…

数据库悲观锁和乐观锁的区别

前言 MySQL本身不直接提供悲观锁&#xff08;Pessimistic Locking&#xff09;和乐观锁&#xff08;Optimistic Locking&#xff09;的实现机制&#xff0c;因为这些锁的概念通常是在应用层面通过不同的策略和工具来实现的。然而&#xff0c;我们可以利用MySQL的一些特性来模拟…

泰克THDP0100(Tektronix)thdp0100高压差分探头详情资料

泰克 THDP0100 高压差分探头具有较大的差分动态范围功能&#xff0c;为用户提供了安全的高压测量探头解决方案。每个探头都配有两种尺寸的钩尖&#xff0c;并具有超范围视觉和声音指示器&#xff0c;当用户超出探头的线性范围时会发出警告。泰克 THDP0100 探头配备 TEkVPI 接口…

实训day42(9.3)

⼀、编排分类 单机容器编排: docker-compose 容器集群编排: docker swarm、mesosmarathon、kubernetes 应⽤编排: ansible(模块&#xff0c;剧本&#xff0c;⻆⾊) ⼆、系统管理进化史 1. 传统部署时代 早期&#xff0c;各个组织是在物理服务器上运⾏应⽤程序。 由于⽆法限…

【Linux】倒计时|进度条|git|gdb的实现

目录 一、缓冲区 1.缓冲区概念&#xff1a; 2.缓冲区作用&#xff1a; 3.缓冲区刷新策略 4.缓冲区位置 5.总结 二、实现倒计时 三、进度条版本1️⃣ 四、进度条版本2️⃣ 五、使用git命令行 六、Linux调试器-gdb使用 背景 开始使用 一、缓冲区 1.缓冲区概念&…

如何提升网站权重?

提升网站权重的方法有很多&#xff0c;常规的方法包括内容优化、关键词研究、页面结构调整、提高用户体验等。但这些方法往往需要时间来见效。如果你希望在短时间内看到显著的提升&#xff0c;发外链是一个非常有效的策略。 外链是提升网站权重的有效方法&#xff0c;但需要注…

【Transformer】Tokenization

文章目录 直观理解分词方式词粒度-Word字粒度-Character子词粒度-Subword&#xff08;目前最常使用&#xff09; 词表大小的影响参考资料 直观理解 在理解Transformer或者大模型对输入进行tokenize之前&#xff0c;需要理解什么是token&#xff1f; 理工科的兄弟姐妹们应该都…

无人机+应用综合实训室解决方案

随着无人机技术的飞速发展&#xff0c;其在航拍、农业、环境监测、物流运输等多个领域展现出巨大的应用潜力。为了满足职业院校及企业对无人机应用技术型人才的培养需求&#xff0c;唯众紧跟市场趋势&#xff0c;推出了全面且详尽的《无人机应用综合实训室解决方案》。本方案旨…

TeamTalk路由服务器

路由相关信令和协议设计 enum BuddyListCmdID {// ...... 暂时省略无关信令CID_BUDDY_LIST_USERS_STATUS_REQUEST 522,CID_BUDDY_LIST_USERS_STATUS_RESPONSE 523,// ...... 暂时省略无关信令 };message IMUsersStatReq{//cmd id: 0x020arequired uint32 user_id 1;repeat…

python 正则表达式“.*”和“.*? ”的区别

“.*”和“.*? ”的区别 点号表示任意非换行符的字符&#xff0c;星号表示匹配它前面的字符0次或者任意多次。所以“.*”表示匹配一串任意长度的字符串任意次。这个时候必须在“.*”的前后加其他的符号来限定范围&#xff0c;否则得到的结果就是原来的整个字符串。 “.*? &…

“软件即仪器”——全新架构 Xtreme Vision显微测量软件平台

“软件即仪器”&#xff0c;工业测量软件较为复杂&#xff0c;涵盖了软件架构、信号处理、图像处理、数值计算、空间几何、三维建模、3D渲染、并行计算、人机交互等多种交叉软学科&#xff0c;是测量仪器系统极为重要的组成部分&#xff0c;中图仪器一直致力于自主化工业测量软…

使用AI写WebSocket知识是一种怎么样的体验?

一、WebSocket基础知识 1. WebSocket概念 1.1 为什么会出现WebSocket 一般的Http请求我们只有主动去请求接口&#xff0c;才能获取到服务器的数据。例如前后端分离的开发场景&#xff0c;自嘲为切图仔的前端大佬找你要一个配置信息的接口&#xff0c;我们后端开发三下两下开…