【C++高阶】哈希的应用(封装unordered_map和unordered_set)

news2025/1/10 11:56:20

✨                                              世事漫随流水,算来一生浮梦      🌏

📃个人主页:island1314

🔥个人专栏:C++学习

🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏  💞 💞 💞


🚀 前言

哈希类的实现参考上一篇文章:【C++高阶】哈希函数底层原理全面探索和深度解析-CSDN博客

之前我们已经学习了如何手搓哈希,现在让我们来对哈希进行改造,并且封装成unordered_map和unordered_set。

注意:本篇我们采用开散列的方式来模拟实现unordered

1. 哈希的改造

  📒改造HashTable以适配unordered_map和unordered_set容器,主要涉及到如何根据这两种容器的特性来设计和实现HashTable节点的存储以及相应的操作。unordered_map和unordered_set的主要区别在于它们存储的元素类型map存储键值对(key-value pairs),而set仅存储唯一的键值(通常是键本身作为值)。尽管如此,它们在底层数据结构(如HashTable)的实现上有很多相似之处

改造内容如下:

  • K:key的类型
  • T:如果是unordered_map,则为pair<K, V>; 如果是unordered_set,则为K
  • KeyOfT:通过T来获取key的一个仿函数类
  • Hash: 哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将Key转换为整形数字才能取模
// unordered_set 与 unordered_map
// unordered_set -> HashTable<K, K>
// unordered_map -> HashTable<K, pair<K, V>>

🌈1.1. 哈希节点的改造

template<class T> //用一个值来确定底层存的是什么
struct HashNode
{
	T _data;
	HashNode<T>* _next;

	HashNode(const T&data)
		: _data(data)
		, _next(nullptr)
	{}
};

注意:在上一篇文章中,我们有介绍了一个关于非整形求关键值的仿函数HashFunc,在模拟实现是可以直接加在模拟实现的类上。

template <class K>
struct HashFunc{
	size_t operator()(const K& key){
		return (size_t)key;  //转成数字,把key
	}
};

// 特化
template<>
struct HashFunc<string>{
	size_t operator()(const string& key){
		size_t hash = 0;
		for (auto e : key){
			hash *= 31;
			hash += e;
		}

		return hash;
	}
};

🌈1.2 哈希的主体框架

template<class K, class T, class KeyOfT, class Hash>
class HashTable
{
	//友元声明
	template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash> //普通类的友元直接友元声明即可,而类模板需要把模板参数带上
	friend struct HTIterator;

	typedef HashNode<T> Node; //节点

public:
	typedef HTIterator<K, T, T*, T&, KeyOfT, Hash> Iterator;  //迭代器
	typedef HTIterator<K, T, const T*,const T&, KeyOfT, Hash> ConstIterator; //const迭代器

   // ... 其他功能的实现

private:
	vector<Node*> _tables; //指针数组,数组的每个位置存的是指针
	size_t _n; //表中存储数据个数
};

2. 哈希的迭代器

🎈2.1 迭代器基本设计

// 为了实现简单,在哈希桶的迭代器类中需要用到hashBucket本身,所以我们要进行一下前置声明,并且我们在 HashTable 中也要设置一个友元(friend)

//前置声明
template<class K, class T, class KeyOfT, class Hash>
class HashTable;

// 通过模板来达到const的迭代器的复用
template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
struct __HTIterator
{
	typedef HashNode<T> Node;
	typedef __HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
	Node* _node;

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &(_node->_data);
	}

	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}
    Self& operator++()
    {}
};

🎈2.2 begin()与end()

关于构建迭代器的begin()end()当我们模拟实现const版本时,又会遇到新的问题,const版本在调用构造时,调不动,因为我最开始实现的构造函数不是const版本,当const版本想要调用构造函数时,这时造成了权限的扩大,因此为了解决这个问题,我们重载了构造函数

示例代码如下:

typedef HTIterator<K, T, T*, T&, KeyOfT, Hash> Iterator;  //迭代器
typedef HTIterator<K, T, const T*,const T&, KeyOfT, Hash> ConstIterator; //const迭代器

Iterator Begin()
{
	if (_n == 0) return End(); //如果没有数据
	for (size_t i = 0; i < _tables.size(); i++)
	{
		Node* cur = _tables[i];
		if (cur)
		{
			return Iterator(cur, this);
		}
	}
	return End();
}
Iterator End()
{
	return Iterator(nullptr, this);
}


ConstIterator Begin() const
{
	if (_n == 0) return End(); //如果没有数据
	for (size_t i = 0; i < _tables.size(); i++)
	{
		Node* cur = _tables[i];
		if (cur)
		{
			return ConstIterator(cur, this);
		}
	}
	return End();
}
ConstIterator End()const
{
	return ConstIterator(nullptr, this); 
}

🎈2.3 operator++()

 📚因为哈希桶在底层是单链表结构,所以哈希桶的迭代器不需要operator--()操作,在operator++()的设计上,我们的问题是在走完这个桶之后,如何找到下一个桶,因此我们需要记录来方便寻找,于是我们引入了两个变量

// HashTable
const HashTable<K, T, KeyOfT, Hash>* _pht;
// 当前桶的位置
size_t _hashi;

对引入两变量的构造:

//需要用const版本,防止权限放大
HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht) 
	:_node(node)
	,_pht(pht)
{}

operator++()代码示例如下:

Self& operator++()
{
	if (_node->_next) //当前桶还有节点
	{ 
		_node = _node->_next;
	}
	else //当前桶遍历完毕,找下一个不为空的桶
	{
		KeyOfT kot;
		Hash hs;
		size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();
		++hashi;
		while (hashi < _pht->_tables.size()) //由于pht要访问其成员私有,但是无法访问,我们解决方法就是提法gettable或者提供友元
		{
			if (_pht->_tables[hashi]) 
				break; //找到不为空的桶 
			++hashi;
		}
		if (hashi == _pht->_tables.size()) //说明已经走完
		{
			_node = nullptr;
		}
		else _node = _pht->_tables[hashi];
	}
	return *this;
}

3. 红黑树相关接口的改造

✨3.1 Find 函数的改造

查找成功,返回查找到的那个节点的迭代器,查找失败,就返回 nullptr。

Iterator Find(const K& key)
{
	KeyOfT kot;

	Hash hs;
	size_t hashi = hs(key) % _tables.size();
	Node* cur = _tables[hashi];
	while (cur){
		if (kot(cur->_data) == key) return Iterator(cur, this);
		cur = cur->_next;
	}
	return End();
}

✨3.2 Insert 函数的改造

map 里的 operator[] 需要依赖 Insert 的返回值

pair<Iterator, bool> Insert(const T& data) //使用的是头插
{
	KeyOfT kot;
	Iterator it = Find(kot(data));
	//去重
	if (it != End()) //return false;
		return make_pair(it, false);
		
	Hash hs;
	size_t hashi = hs(kot(data)) % _tables.size();
	//负载因子 == 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 = hs(kot(cur->_data)) % newtables.size();
				//头插到新表
				cur->_next = newtables[hashi];
				newtables[hashi] = cur;
				
				cur = next;
			}
			_tables[i] = nullptr;
		}
		_tables.swap(newtables);
	}

	//头插
	Node* newnode = new Node(data);
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	++_n;

	return make_pair(Iterator(newnode, this),true);
}

4. Unordered_Set的模拟实现

🧩4.1 Unordered_Set的设计

template<class K, class Hash = HashFunc<K>>
class unordered_set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	
	// 因为 unordered_set的特性K是不能够修改的,
	// 所以我们在 const迭代器和非const迭代器上,都用 const来修饰K来起到不能修改K的特点
	typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;
	typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator const_iterator;


	iterator begin()
	{
		return _ht.Begin();
	}

	iterator end()
	{
		return _ht.End();
	}

	const_iterator begin() const
	{
		return _ht.Begin();
	}

	const_iterator end() const
	{
		return _ht.End();
	}

	pair<iterator, bool> insert(const K& key)
	{
		return _ht.Insert(key);
	}

	iterator Find(const K& key)
	{
		return _ht.Find(key);
	}

	bool Erase(const K& key)
	{
		return _ht.Erase(key);
	}

private:
	hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
};

🧩4.2Unordered_Set的测试

void test_set()
{
	unordered_set<int> s;
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	for (auto e : a){
		s.insert(e);
	}

	for (auto e : s){
		cout << e << " ";
	}
	cout << endl;

	unordered_set<int>::iterator it = s.begin();
	while (it != s.end()){
		//*it += 1; //const迭代器不支持修改
		cout << *it << " ";
		++it;
	}
	cout << endl;

	Print(s);
}

5. Unordered_Map的模拟实现

🌸5.1 Unordered_Map的设计

template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:

	// 在 unordered_map我们就只需要考虑 kv.first不能修改
	// 但是 kv.first->second是可以修改的,因此我们需要将 K用 const修饰
	typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;
	typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator const_iterator;

	iterator begin()
	{
		return _ht.Begin();
	}

	iterator end()
	{
		return _ht.End();
	}

	const_iterator begin() const
	{
		return _ht.Begin();
	}

	const_iterator end() const
	{
		return _ht.End();
	}

	/*bool insert(const pair<K, V>& kv)
	{
		return _ht.Insert(kv);
	}*/

	pair<iterator, bool> insert(const pair<K, V>& kv)
	{
		return _ht.Insert(kv);
	}

	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));

		return ret.first->second;
	}

	iterator Find(const K& key)
	{
		return _ht.Find(key);
	}

	bool Erase(const K& key)
	{
		return _ht.Erase(key);
	}

private:
	hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};

🌸5.2 Unordered_Map的测试

void test_map()
{
	unordered_map<string, string> dict;
	dict.insert({ "sort", "排序" });
	dict.insert({ "left", "左边" });
	dict.insert(make_pair("right", "右边"));

	dict["left"] = "左边,剩余";
	dict["insert"] = "插入";
	dict["string"];

	unordered_map<string, string>::iterator it = dict.begin();
	while (it != dict.end()){
		// 不能修改first,可以修改second
		//it->first += 'x'; //Key不能修改,因此pair<K, V> 要写成pair<const K, V>
		it->second += 'x';
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;
}

📖哈希改造的完整代码及总结

#pragma once
#include <iostream>
#include <vector>
using namespace std;

template <class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;  //转成数字,把key
	}
};


// 特化
template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (auto e : key)
		{
			hash *= 31;
			hash += e;
		}

		return hash;
	}
};


namespace hash_bucket  //哈希桶-链式
{
	template<class T> //用一个值来确定底层存的是什么
	struct HashNode{
		/*pair<K, V> _kv;
		HashNode<K, V>* _next;*/

		T _data;
		HashNode<T>* _next;

		HashNode(const T&data)
			: _data(data)
			, _next(nullptr)
		{}
	};

	//前置声明
	template<class K, class T, class KeyOfT, class Hash>
	class HashTable;

	//template<class K, class T, class KeyOfT, class Hash>
	template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash> 
	struct HTIterator
	{
		typedef HashNode<T> Node;
		typedef HTIterator<K, T, Ptr, Ref, KeyOfT, Hash> Self;

		Node* _node;
		const HashTable<K, T, KeyOfT, Hash>* _pht; //由于向上找不到,老是会报_pht找不到,因此我们需要加个前置声明

		HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht) //需要用const版本,防止权限放大
			:_node(node)
			,_pht(pht)
		{}

		Ref operator*(){return _node->_data;}

		Ptr operator->(){return &_node->_data;}

		bool operator!=(const Self& s){return _node != s._node;}


		Self& operator++()
		{
			if (_node->_next) //当前桶还有节点{ 
				_node = _node->_next;
			}
			else //当前桶遍历完毕,找下一个不为空的桶
			{
				KeyOfT kot;
				Hash hs;
				size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();
				
				++hashi;
				while (hashi < _pht->_tables.size()) //由于pht要访问其成员私有,但是无法访问,我们解决方法就是提法gettable或者提供友元
				{
					if (_pht->_tables[hashi]) 
						break; //找到不为空的桶 
					++hashi;
				}
				if (hashi == _pht->_tables.size()) //说明已经走完
				{
					_node = nullptr;
				}
				else _node = _pht->_tables[hashi];
			}
			return *this;
		}


	};

	template<class K, class T, class KeyOfT, class Hash>
	class HashTable
	{
		//友元声明
		template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash> //普通类的友元直接友元声明即可,而类模板需要把模板参数带上
		friend struct HTIterator;

		typedef HashNode<T> Node;
	
	public:
		typedef HTIterator<K, T, T*, T&, KeyOfT, Hash> Iterator;  //迭代器
		typedef HTIterator<K, T, const T*,const T&, KeyOfT, Hash> ConstIterator; //const迭代器


		Iterator Begin()
		{
			if (_n == 0) return End(); //如果没有数据
			for (size_t i = 0; i < _tables.size(); i++){
				Node* cur = _tables[i];
				if (cur){
					return Iterator(cur, this);
				}
			}
			return End();
		}

		Iterator End(){return Iterator(nullptr, this);}


		ConstIterator Begin() const
		{
			if (_n == 0) return End(); //如果没有数据
			for (size_t i = 0; i < _tables.size(); i++){
				Node* cur = _tables[i];
				if (cur){
					return ConstIterator(cur, this);
				}
			}
			return End();
		}

		ConstIterator End()const {return ConstIterator(nullptr, this); }

	public:
		HashTable()
		{
			_tables.resize(10, nullptr);
		}

		~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;
			}
		}


		pair<Iterator, bool> Insert(const T& data) //使用的是头插
		{
			KeyOfT kot;
			Iterator it = Find(kot(data));
			//去重
			if (it != End()) //return false;
				return make_pair(it, false);
				
			Hash hs;
			size_t hashi = hs(kot(data)) % _tables.size();
			//负载因子 == 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 = hs(kot(cur->_data)) % newtables.size();
						//头插到新表
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;
						
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newtables);

			}

			//头插
			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;

			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur){
				if (kot(cur->_data) == key) return Iterator(cur, this);
				cur = cur->_next;
			}
			return End();
		}

		bool Erase(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr) //如果是第一个节点 
					{
						_tables[hashi] = cur->_next;
					}
					else //否则,则让前一个指向我的后一个
					{
						prev->_next = cur->_next;
					}

					delete cur;
					--_n;
					return true;
				}

				prev = cur;  
				cur = cur->_next;
			}

			return false;
		}

	private:
		vector<Node*> _tables; //指针数组,数组的每个位置存的是指针
		size_t _n; //表中存储数据个数
	};

}

以上就是哈希改造的全部内容,让我们来总结以下哈希的实现及改造封装吧

——————————步骤——————————

1、实现哈希表
2、封装unordered_map和unordered_set 解决KetOfT
3、实现 iterator 迭代器
 4、实现 const_iterator 迭代器

 5、修改Key的问题
 6、解决operate[]

本篇到此就结束,希望我的这篇博客可以给你提供有益的参考和启示,感谢大家支持!!!

祝大家天天顺心如意。

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

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

相关文章

KubeSphere部署:(三)MySQL安装

MySQL没有什么特殊的&#xff0c;这里记录一下部署过程(本文示例中安装的版本为5.7.29)。步骤大致如下&#xff1a; 拉取docker镜像 -> 标记并推送至私有harbor -> 创建有状态负载 -> 创建服务 一、拉取镜像&#xff0c;并推送至私有harbor # 拉取镜像 docker pull …

PP氮气柜的特点和使用事项介绍

PP材质&#xff0c;全称为聚丙烯&#xff0c;是一种热塑性塑料&#xff0c;具有质轻、强度高、耐化学腐蚀性好、无毒无味、耐热性佳等优点。它在众多塑料材料中脱颖而出&#xff0c;特别是在需要耐腐蚀和长期使用的应用中&#xff0c;表现尤为出色。 PP材质具有优秀的化学稳定性…

Loadrunner12 回放脚本查看接口响应数据

1、如下图所示&#xff0c;回放脚本后&#xff0c;点击快照-http数据-点击需要查看的接口-点击Json视图&#xff0c;最后点击响应正文&#xff0c;即可查看接口的响应数据

生信初学者教程(癌症转录组学):手把手教你如何发生信文章

网址 生信初学者教程&#xff08;癌症转录组学&#xff09; : https://bioinformatic-learner.github.io/BCT-page/ 提供了预览版本。 该教程包含从开题、数据下载、数据分析、结果解读、串联结果和撰写文章等等&#xff0c;是一份非常好的生信初学者发文章的好材料。 出发点…

【Linux】UDP 协议

目录 1. UDP 协议2. UDP 协议的特点:3. UDP 协议的格式4. UDP 的缓冲区基于UDP的应用层协议 1. UDP 协议 UDP (User Datagram Protocol) 是一种面向数据报的传输层协议, 是传输层的重要协议之一; UDP协议提供了一种无连接, 不可靠的数据传输服务; 适用于要求源主机以恒定速率…

响应式建站陶瓷企业类公司网站源码系统 带完整的安装代码包以及搭建部署教程

系统概述 响应式建站陶瓷企业类公司网站源码系统是一款专为陶瓷企业设计的网站建设解决方案。该系统采用响应式设计&#xff0c;能够自动适应不同设备的屏幕尺寸&#xff0c;为用户提供一致的浏览体验。无论用户是通过电脑、平板还是手机访问网站&#xff0c;都能获得清晰、美…

html+css 实现3D分层悬停按钮

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽效果&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 文…

太牛了!恭喜7位毕业急录、评职晋升作者,2天录用,1-8天见刊!

本周投稿推荐 SCI&EI • 4区“水刊”&#xff0c;纯正刊&#xff08;来稿即录&#xff09; • CCF-B类&#xff0c;IEEE一区-Top&#xff08;3天初审&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09; 知网&#xff08;CNKI&#xff09;、谷歌学术 …

posthog,一个超酷的 Python 库!

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个超酷的 Python 库 - posthog。 Github地址&#xff1a;https://github.com/PostHog/posthog 在现代数据驱动的开发过程中&#xff0c;了解用户行为和应用性能是至关重要的…

this关键字的简明指南与理解

this关键字是执行上下文中的一个属性&#xff0c;它主要用在函数内部&#xff0c;指向最后一次调用该函数的对象。然而&#xff0c;this 的值并不是在函数定义时确定的&#xff0c;而是在函数被调用时根据函数的调用方式动态绑定的。以下是对 this 的一些相关理解。 一、this的…

Scrapy 爬取旅游景点相关数据(七):利用指纹实现“不重复爬取”

本期学习&#xff1a; 利用网页指纹去重 众所周知&#xff0c;代理是要花钱的&#xff0c;那么在爬取&#xff08;测试&#xff09;巨量网页的时候&#xff0c;就不可能对已经爬取过的网站去重复的爬&#xff0c;这样会消耗大量的时间&#xff0c;更重要的是会消耗大量的IP (金…

redis的高可用及性能管理和雪崩

redis的高可用 redis当中&#xff0c;高可用概念更宽泛一些。 除了正常服务以外&#xff0c;数据量的扩容&#xff0c;数据安全。 实现高可用的方式&#xff1a; 1、持久化 最简单的高可用方法&#xff0c;主要功能就是备份数据。 把内存当中的数据保存到硬盘当中。 2、主…

如何有效增加谷歌外链?

想有效增加谷歌外链&#xff0c;其实还是要看你想要哪一种外链&#xff0c;但无论哪一种外链&#xff0c;都不能乱发&#xff0c;想有效的增加谷歌外链&#xff0c;看见明显的数据变化&#xff0c;这里只推荐三种外链&#xff0c;GPB&#xff0c;GNB,GMB 目前市面上最有效的外链…

负载驱动下的滚珠丝杆预压力优化策略!

滚珠丝杆的预紧力是指在未受到负载时&#xff0c;滚珠丝杆轴承内部的压力&#xff0c;主要是为了消除轴向后隙&#xff0c;‌减小轴向力引起的弹性位移&#xff0c;‌从而提高滚珠丝杆的刚度。‌这种预压机制通过独特的滚珠与珠槽接触模式实现&#xff0c;‌旨在增加系统的刚性…

物联网服务器搭建及部署详细说明:掌握 Node.js、MongoDB、Socket.IO 和 JWT 的实用指南

关键知识点目录 1. 环境准备 1.1 硬件要求 1.2 软件要求 2. 搭建步骤 3. 数据处理与存储 3.1 数据存储 3.2 数据实时处理 3.2.1 安装 Socket.IO 3.2.2 修改服务器代码 4. 安全性 4.1 身份验证与授权 4.2 加密通信 4.2.1 生成自签名证书&#xff08;开发环境&#…

html必知必会-html内嵌JavaScript和文件路径

文章目录 HTML JavaScriptHTML <script> 标签JavaScript 的简单示例HTML <noscript> 标签HTML 文件路径绝对文件路径相对文件路径总结 HTML JavaScript JavaScript 使 HTML 页面更具动态性和交互性。 示例 <!DOCTYPE html> <html> <body><…

iOS18使用技巧:iPhone通话录音开启教程和注意事项

今日早些时候&#xff0c;苹果为iPhone 15 Pro系列的开发者预览版用户推送了iOS 18.1 Beta1测试版的更新&#xff0c;已经注册Apple Beta版软件计划的用户只需打开设置--通用--软件更新即可在线OTA升级至最新的iOS 18.1 Beta1测试版。 说起iOS 18.1最重磅的更新&#xff0c;莫过…

Redis7-入门-安装

1.Redis是什么 REmote Dictionary Server(远程字典服务器) Remote Dictionary Server(远程字典服务)是完全开源的&#xff0c;使用ANSIC语言编写遵守BSD协议&#xff0c;是一个高性能的Key-Value数据库提供了丰富的数据结构&#xff0c;例如String、Hash、List、set、Sorteds…

程序员学长 | 快速学会一个算法,ANN

本文来源公众号“程序员学长”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;快速学会一个算法&#xff0c;ANN 今天给大家分享一个强大的算法模型&#xff0c;ANN。 人工神经网络 (ANN) 是一种深度学习方法&#xff0c;源自人类…

【C++BFS】1020. 飞地的数量

本文涉及知识点 CBFS算法 LeetCode1020. 飞地的数量 给你一个大小为 m x n 的二进制矩阵 grid &#xff0c;其中 0 表示一个海洋单元格、1 表示一个陆地单元格。 一次 移动 是指从一个陆地单元格走到另一个相邻&#xff08;上、下、左、右&#xff09;的陆地单元格或跨过 gr…