011 数据结构_哈希

前言

本文将会向你介绍哈希概念,哈希方法,如何解决哈希冲突,以及闭散列与开散列的模拟实现

1. 哈希概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即 O( l o g 2 N log_2N log2N),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一 一映射的关系,那么在查找时通过该函数可以很快找到该元素。 当向该结构中: 插入元素根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放搜索元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功
该方式即为哈希方法,哈希方法中使用的转换函数称为哈希函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表
例如:数据集合{1,7,6,4,5,9};
哈希函数设置为:hash(key) = key % size; size为存储元素底层空间总的大小。
在这里插入图片描述

2. 哈希方法

哈希方法:我们通常对关键码key进行转换来确定存储的位置,比如由字符串abc转换成一个整数作为存储的位置,这个转换的方法称为哈希方法,哈希方法中运用的函数叫做哈希函数

(1)直接定址法

ps:哈希方法是一个广义的概念,而哈希函数是哈希方法的一种具体实现。

1、直接定址法 值和位置关系唯一关系,每个值都有一个唯一位置,但是值很分散,直接定址会导致空间开很大,导致空间浪费
(此方法运用于关键字范围集中,量不大的情况,关键字和存储位置是一对一的关系,不存在哈希冲突)

在这里插入图片描述

引入哈希冲突

哈希冲突概念:不同关键字通过相同的哈希函数计算出相同的哈希存储位置(不同的值映射到相同的位置上去),这种现象被称为哈希冲突或哈希碰撞,哈希冲突的发生与哈希函数的设计有关

(2)除留余数法

主要应用于关键字可以很分散,量可以很大,关键字和存储位置是多对一的关系的情况,但是存在哈希冲突
在这里插入图片描述

3. 解决哈希冲突

(1)闭散列

概念: 闭散列又称开放定址法,指当前位置被占用(哈希冲突),开放空间里按照某种规则,找一个没有被占用的位置存储
1、线性探测
从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止 Hashi = hashi + i(i>=0)
2、二次探测
探测公式发生变化 hashi + i^2(i>=0)

(2)开散列

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地
址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
如图可观察到,val值为44的节点和节点val值为4的节点发生哈希冲突
开散列中每个桶中放大都是发生哈希冲突的元素

在这里插入图片描述

引入负载因子

负载因子:存储个数/空间的大小(注意这里的空间的大小是size而不是capacity)
由于在哈希表中,operator[]操作会根据已有的元素数量(即size())进行检查。因此,在计算负载因子时,要使用已有元素的个数除以哈希表的大小(即size())
size()函数返回的是当前哈希表中实际存储的元素数量,而capacity()函数返回的是哈希表的容量(即内部存储空间的大小)
负载因子:存储关键字个数/空间大小 负载因子太大,冲突可能会剧增,冲突增加,效率降低 负载因子太小,冲突降低,但是空间利用率就低了

5. 哈希表扩容

扩容的核心是先开辟新空间,然后遍历旧空间的数据,按照hashi = hashi % Newsize重新建立映射,然后将旧空间的数据拷贝到新空间去,最后交换新旧哈希表,本质上我们还是要对旧哈希表进行扩容,因此最后要swap交换两表
在这里插入图片描述

6. 哈希表插入

三种状态EMPTY、EXIST、DELETE

EMPTY,表示该位置为空。
EXIST,表示该位置被占用了。
DELETE,表示该位置被删除了。

删除状态存在的含义

或许你会有疑问:删除为什么不能直接设为空状态,而是将被删除的状态设置为DELETE

在这里插入图片描述

7. 闭散列模拟实现

数据结构

struct Elem
{
	pair<K, V> _val;
	State _state = EMPTY;
};
vector<Elem<K, V>> _ht;

在这里插入图片描述

闭散列插入

闭散列的插入步骤是:判断是否存在,判断是否需要扩容(结合负载因子),遍历旧空间拷贝数据
关于闭散列的模拟实现,核心步骤在上文都有讲,这里就不再多作赘述,具体可看下面的代码与注释

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

	//因为字符串做键值非常常见,库里面也特化了一份
	//BKDR算法,这里不会展开来讲
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			size_t hashi = 0;
			for (auto ch : key)
			{
				hashi = hashi * 31 + ch;
			}
			return hashi;
		}
	};

	enum State 
	{ 
		EMPTY
		,EXIST
		,DELETE
	};
	template <class K, class V>
	struct Elem
	{
		pair<K, V> _val;
		State _state = EMPTY;
	};
	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		HashTable(size_t capacity = 3)
			: _ht(capacity)
			,_size(0)
			, _totalSize(0)
		{
			for (size_t i = 0; i < capacity; ++i)
				_ht[i]._state = EMPTY;
		}

		// 插入
		bool Insert(const pair<K, V>& val)
		{
			Hash hf;
			_size = _ht.size();
			//已有
			if (Find(val.first))
			{
				return false;
			}
			else
			{
				//扩容,负载因子==0.6
				if ((double)_totalSize / _size >= 0.6)
				{
					//开辟新空间
					size_t newsize = _size * 2;
					HashTable<K, V, Hash> NewHt;
					NewHt._ht.resize(newsize);

					//遍历旧空间
					for (int i = 0; i < _size; i++)
					{
						if (_ht[i]._state == EXIST)
						{
							NewHt.Insert(_ht[i]._val);
						}
					}
					NewHt._ht.swap(_ht);
				}
				size_t hashi = hf(val.first) % _size;
				//不为空,向后查找
				while (_ht[hashi]._state == EXIST)
				{
					hashi++;
					//如果超出数组长度
					hashi %= _size;
				}
				//为空,插入
				_ht[hashi]._val.first = val.first;
				_ht[hashi]._val.second = val.second;
				_ht[hashi]._state = EXIST;
				++_totalSize;
				return true;
			}
		}

		// 查找
		Elem<K, V>* Find(const K& key)
		{
			Hash hf;
			//线性探测
			size_t hashi = hf(key) % _ht.size();
			while (_ht[hashi]._state != EMPTY)
			{	
				if (_ht[hashi]._state == EXIST 
					&& _ht[hashi]._val.first == key)
				{
					return &_ht[hashi];
				}
				hashi++;
				//超出数组长度
				hashi %= _ht.size();

			}
			//没有找到a
			return nullptr;
		}

		// 删除
		bool Erase(const K& key)
		{
			Elem<K, V>* ret = Find(key);
			//不为空就说明找到
			if (ret)
			{
				ret->_state = DELETE;
				--_totalSize;
				return true;
			}
			else return false;
		}

	private:
		size_t HashFunc(const K& key)
		{
			return key % _ht.capacity();
		}

		void CheckCapacity();
	private:
		vector<Elem<K, V>> _ht;
		size_t _size;
		size_t _totalSize;  // 哈希表中的所有元素:有效和已删除, 扩容时候要用到
	};
}

测试

		void Print()
		{
			for (int i = 0; i < _ht.size(); i++)
			{
				if (_ht[i]._state == EXIST)
				{
					//printf("[%d]->%d\n", i, _tables[i]._kv.first);
					cout << "[" << i << "]->" << _ht[i]._val.first << ":" << _ht[i]._val.second << endl;
				}
				else if (_ht[i]._state == EMPTY)
				{
					printf("[%d]->\n", i);
				}
				else
				{
					printf("[%d]->D\n", i);
				}
			}
			
void TestHT1()
{
	Close_Hash::HashTable<int, int> ht;
	int a[] = { 4,14,24,34,5,7,1 };
	for (auto e : a)
	{
		ht.Insert(make_pair(e, e));
	}
	ht.Print();
	ht.Insert(make_pair(3, 3));
	ht.Insert(make_pair(3, 3));
	ht.Insert(make_pair(-3, -3));
	ht.Print();
	cout << endl;

	ht.Erase(3);;
	ht.Print();

	if (ht.Find(3))
	{
		cout << "3存在" << endl;
	}
	else
	{
		cout << "3不存在" << endl;
	}
	ht.Insert(make_pair(23, 3));
	ht.Insert(make_pair(3, 3));
	if (ht.Find(3))
	{
		cout << "3存在" << endl;
	}
	else
	{
		cout << "3不存在" << endl;
	}
	ht.Print();
}

8. 开散列模拟实现

数据结构

	struct HashNode
	{
		HashNode* _next;
		pair<K, V> _val;
		HashNode(const pair<K, V>& val)
			:_next(nullptr)
			,_val(val)
		{}
	};
	typedef HashNode<K, V> Node;
	vector<Node*> _ht;

在这里插入图片描述

开散列插入

插入的主要逻辑是:先查找是否存在,判断是否需要扩容(依据平衡因子),开辟新空间然后遍历旧空间,将旧空间的数据拷贝到新空间上(需要根据新的映射关系,待会会细讲),最后插入节点

bool Insert(const pair<K, V>& val)
{
	Hash hf;
	//已有
	if (Find(val.first))
	{
		return false;
	}
	//扩容,负载因子==1
	if (_totalSize == _ht.size())
	{
		//开辟新空间
		size_t newsize = _ht.size() * 2;
		vector<Node*> NewHt;
		NewHt.resize(newsize);

		//遍历旧空间
		for (int i = 0; i < _ht.size(); i++)
		{
			Node* cur = _ht[i];
			while (cur)
			{
				//保存下一个结构体指针
				Node* next = cur->_next;
				size_t hashi = hf(cur->_val.first) % NewHt.size();
				//将新空间上hashi位置处的哈希桶链接到需要处理的当前节点
				cur->_next = NewHt[hashi];
				NewHt[hashi] = cur;
				//处理旧空间上哈希桶的下一个节点
				cur = next;
			}
			//防止出现悬空指针的问题
			_ht[i] = nullptr;
		}   
		_ht.swap(NewHt);
	}
		//插入节点
		size_t hashi = hf(val.first) % _ht.size();
		Node* newnode = new Node(val);
		//头插
		newnode->_next = _ht[hashi];
		_ht[hashi] = newnode;
		++_totalSize;
		return true;
}

以下是遍历旧空间,拷贝数据的图解
在这里插入图片描述
插入过程图解
在这里插入图片描述
全部代码


namespace Open_Hash
{
	template<class T>
	struct HashFunc
	{
		size_t operator()(const T& key)
		{
			if (key >= 0)
			{
				return (size_t)key;
			}
			else
			{
				return abs(key);
			}
		}
	};

	//字符串哈希算法这里不展开讲,采用的是BKDR算法
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			size_t hashi = 0;
			for (auto ch : key)
			{
				hashi = hashi * 31 + ch;
			}
			return hashi;
		}
	};
	template <class K, class V>
	struct HashNode
	{
		HashNode* _next;
		pair<K, V> _val;
		HashNode(const pair<K, V>& val)
			:_next(nullptr)
			,_val(val)
		{}
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:	
		HashTable()
		{
			_ht.resize(10);
		}
		~HashTable()
		{
			for (int i = 0; i < _ht.size(); i++)
			{
				Node* cur = _ht[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				//将当前哈希桶置空
				_ht[i] = nullptr;
			}
		}
		typedef HashNode<K, V> Node;
		// 插入
		bool Insert(const pair<K, V>& val)
		{
			Hash hf;
			//已有
			if (Find(val.first))
			{
				return false;
			}
			//扩容,负载因子==1
			if (_totalSize == _ht.size())
			{
				//开辟新空间
				size_t newsize = _ht.size() * 2;
				vector<Node*> NewHt;
				NewHt.resize(newsize);

				//遍历旧空间
				for (int i = 0; i < _ht.size(); i++)
				{
					Node* cur = _ht[i];
					while (cur)
					{
						//保存下一个结构体指针
						Node* next = cur->_next;
						size_t hashi = hf(cur->_val.first) % NewHt.size();
						//将新空间上hashi位置处的哈希桶链接到需要处理的当前节点
						cur->_next = NewHt[hashi];
						NewHt[hashi] = cur;
						//处理旧空间上哈希桶的下一个节点
						cur = next;
					}
					//防止出现悬空指针的问题
					_ht[i] = nullptr;
				}
				_ht.swap(NewHt);
			}
				//插入节点
				size_t hashi = hf(val.first) % _ht.size();
				Node* newnode = new Node(val);
				//头插
				newnode->_next = _ht[hashi];
				_ht[hashi] = newnode;
				++_totalSize;
				return true;
		}

		//查找
		Node* Find(const K& key)
		{
			Hash hf;
			//线性探测
			size_t hashi = hf(key) % _ht.size();
			Node* cur = _ht[hashi];
			//遍历对应hashi位置处的哈希桶
			while (cur)
			{
				if (cur->_val.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			//没有找到
			return nullptr;
		}
		// 删除
		bool Erase(const K& key)
		{
			Hash hf;
			Node* ret = Find(key);
			size_t hashi = hf(key) % _ht.size();
			//不为空就说明找到
			if (ret)
			{
				Node* cur = _ht[hashi];
				Node* prev = nullptr;
				//遍历当前哈希桶
				while (cur)
				{
					if (cur->_val.first == key)
					{
						//判断是头删还是中间位置处的删除
						if (prev == nullptr)
						{
							_ht[hashi] = cur->_next;
						}
						else
						{
							prev->_next = cur->_next;
						}
						delete cur;
						return true;
					}
					prev = cur;
					cur = cur->_next;
				}
			}
			//未找到
			return false;
		}

	private:
			vector<Node*> _ht;
			Node* _next = nullptr;
			size_t _totalSize = 0;  // 哈希表中的所有元素:有效和已删除, 扩容时候要用到
	};
}

测试

		//打印
		void Print1()
		{
			for (int i = 0; i < _ht.size(); i++)
			{
				Node* cur = _ht[i];
				cout << "[" << i << "]:";
				//哈希桶不为空
				while(cur)
				{
					cout << "(" << cur->_val.first << "," << cur->_val.second << ")" << "->";
					cur = cur->_next;
				}
				cout << endl;
			}
			cout << endl;
		}

		void Print2()
		{
			for (int i = 0; i < _ht.size(); i++)
			{
				Node* cur = _ht[i];
				//哈希桶不为空
				while (cur)
				{
					cout << cur->_val.first << ":"<< cur->_val.second << " ";
					cur = cur->_next;
				}
			}
			cout << endl;
		}
//测试
		void TestHT1()
		{
			HashTable<int, int> ht;
			int a[] = { 4,14,24,34,5,7,1 };
			for (auto e : a)
			{
				ht.Insert(make_pair(e, e));
			}

			ht.Insert(make_pair(3, 3));
			ht.Insert(make_pair(3, 3));
			ht.Insert(make_pair(-3, -3));
			ht.Print1();

			ht.Erase(3);
			ht.Print1();

			if (ht.Find(3))
			{
				cout << "3存在" << endl;
			}
			else
			{
				cout << "3不存在" << endl;
			}

			ht.Insert(make_pair(3, 3));
			ht.Insert(make_pair(23, 3));
			//ht.Insert(make_pair(-9, -9));
			ht.Insert(make_pair(-1, -1));
			ht.Print1();
		}

		void TestHT2()
		{
			string arr[] = { "香蕉", "甜瓜","苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
			//HashTable<string, int, HashFuncString> ht;
			HashTable<string, int> ht;
			for (auto& e : arr)
			{
				//auto ret = ht.Find(e);
				HashNode<string, int>* ret = ht.Find(e);
				if (ret)
				{
					ret->_val.second++;
				}
				else
				{
					ht.Insert(make_pair(e, 1));
				}
			}

			ht.Print2();

			ht.Insert(make_pair("apple", 1));
			ht.Insert(make_pair("sort", 1));

			ht.Insert(make_pair("abc", 1));
			ht.Insert(make_pair("acb", 1));
			ht.Insert(make_pair("aad", 1));

			ht.Print2();
		}

		void Some()
		{
				const size_t N = 100;
				vector<int> v;
				v.reserve(N);
				srand(time(0));
				for (size_t i = 0; i < N; ++i)
				{
					//v.push_back(rand()); // N比较大时,重复值比较多
					v.push_back(rand()%100+i); // 重复值相对少
					//v.push_back(i); // 没有重复,有序
				}
				HashTable<int, int> ht;
				for (auto e : v)
				{
					ht.Insert(make_pair(e, e));
				}
				ht.Print1();
		}

小结

今日的分享就到这里啦,后续将会向你带来位图与布隆过滤器的知识,如果本文存在疏漏或错误的地方还请您能够指出,另外如果你存在疑问,也可以评论留言哦!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【MATLAB】基于CEEMDAN分解的信号去噪算法(基础版)

代码的使用说明 【MATLAB】基于CEEMDAN分解的信号去噪算法&#xff08;基础版&#xff09; 代码流程图 代码效果图 获取代码请关注MATLAB科研小白的个人公众号&#xff08;即文章下方二维码&#xff09;&#xff0c;并回复CEEMDAN去噪 本公众号致力于解决找代码难&#xff0c;…

数据结构与算法-Rust 版读书笔记-2线性数据结构-队列

数据结构与算法-Rust 版读书笔记-2线性数据结构-队列 1、队列&#xff1a;先进先出 队列是项的有序集合&#xff0c;其中&#xff0c;添加新项的一端称为队尾&#xff0c;移除项的另一端称为队首。一个元素在从队尾进入队列后&#xff0c;就会一直向队首移动&#xff0c;直到…

最大上升子序列和

欢迎大家到我的博客浏览&#xff0c;请点击 YinKai s Blog。 题目&#xff1a;最大上升子序列和 一个数的序列 bi&#xff0c;当 b1<b2<…<bS 的时候&#xff0c;我们称这个序列是上升的。 对于给定的一个序列(a1,a2,…,aN)&#xff0c;我们可以得到一些上升的子序列…

c语言中怎么把字符串变成浮点型

大家好&#xff0c;今天给大家介绍c语言中怎么把字符串变成浮点型&#xff0c;文章末尾附有分享大家一个资料包&#xff0c;差不多150多G。里面学习内容、面经、项目都比较新也比较全&#xff01;可进群免费领取。 可以使用Python内置函数float()将字符串转换为浮点型。例如&a…

应对复杂环境:配网故障定位系统的挑战与解决方案

随着电力系统的不断发展&#xff0c;配电网络日益庞大&#xff0c;复杂的环境和多样化的故障类型给电网运维带来了巨大的挑战。为了提高配电网络的安全性和可靠性&#xff0c;需要研发一套高效的配网故障定位系统。恒峰智慧科技将探讨这一系统面临的挑战以及解决方案。 一、监测…

【Java 基础】30 JDK动态代理

文章目录 1.定义2.原理3.使用1&#xff09;定义业务接口2&#xff09;实现 InvocationHandler 接口3&#xff09;生成代理类 4.优点5.缺点总结 动态代理是一种重要的 设计模式&#xff0c;它允许在运行时生成代理类来代替实际的类。动态代理主要通过反射机制实现&#xff0c;为…

idea__SpringBoot微服务09——员工管理系统,(Springboot解决乱码),thymeleaf语法,404页面。

员工管理系统 完整项目地址&#xff1a;一、首页实现&#xff08;注意的点&#xff09;二、国际化三、乱码解决四、登录功能实现&#xff08;注意的点&#xff09;五、登录拦截器&#xff08;注意的点&#xff09;六、展示员工列表&#xff08;注意的点&#xff09;1、前端页面…

【EMNLP 2023】面向Stable Diffusion的自动Prompt工程算法

近日&#xff0c;阿里云人工智能平台PAI与华南理工大学朱金辉教授团队合作在自然语言处理顶级会议EMNLP2023上发表了BeautifulPrompt的深度生成模型&#xff0c;可以从简单的图片描述中生成高质量的提示词&#xff0c;从而使文生图模型能够生成更美观的图像。BeautifulPrompt通…

被忽悠选择那些价格昂贵的知识付费平台?我有才知识服务平台手把手教你如何正确选择!

在当今的知识经济时代&#xff0c;一个高效、便捷的知识服务平台对于企业和个人至关重要。然而&#xff0c;市面上的众多知识服务平台中&#xff0c;许多产品存在高昂的费用、无用功能的堆砌、无法定制化等问题&#xff0c;让用户进退两难&#xff0c;甚至被忽悠掉入使用陷阱。…

深度解析TCP协议:特点、应用场景及市面上常见软件案例

目录 引言 TCP的特点 TCP的应用场景 市面上使用TCP的软件案例 引言 TCP&#xff08;Transmission Control Protocol&#xff09;是计算机网络中一种基于连接的、可靠的传输层协议。它具有一系列独特的特点&#xff0c;适用于广泛的应用场景。本文将深入研究TCP的特点、应用…

系统报错;由于找不到hid.dll,无法继续执行代码”的解决方案分享

在计算机使用过程中&#xff0c;我们可能会遇到一些错误提示&#xff0c;其中之一就是“找不到hid.dll&#xff0c;无法继续执行代码”。这个错误提示通常表示计算机缺少了一个重要的动态链接库文件&#xff0c;即hid.dll。本文将详细介绍hid.dll丢失对电脑的影响以及hid.dll是…

了解 git rebase

了解 git rebase 大多数人习惯使用 git merge 将更改从功能分支合并到主分支&#xff0c;但还有其他方法。我们是否曾经遇到过 git rebase 这个术语并想知道它是什么&#xff1f;或者我们可能听说过 rebase 和 merge &#xff0c;但不确定何时使用哪个&#xff1f;不用担心&am…

报表生成器Stimulsoft用户手册:预览中具有动态数据排序的报告

Stimulsoft Reports 是一款报告编写器&#xff0c;主要用于在桌面和Web上从头开始创建任何复杂的报告。可以在大多数平台上轻松实现部署&#xff0c;如ASP.NET, WinForms, .NET Core, JavaScript, WPF, Angular, Blazor, PHP, Java等&#xff0c;在你的应用程序中嵌入报告设计器…

LAMP与LNMP架构

一、概述 LAMP架构是目前成熟的企业网站应用模式之一&#xff0c;指的是协同工作的一整套系统和相关软件&#xff0c;能够提供动态Web站点服务及其应用开发环境。LAMP是一个缩写词&#xff0c;具体包括Linux操作系统、Apache网站服务器、MySQL数据库服务器、PHP&#xff08;或…

win 10 hp hotkey uwp service占用内存高解决方法

hp hotkey uwp service hp hotkey uwp service high cpu hp audio analytics service high cpu 我是惠普战66笔记本, 这个问题断断续续好久了都没有得到解决, 作为一个能折腾的人, 热键也就亮度和声音是常用的, 而且鼠标进行这些操作也很简单, 最后想了想干脆直接把该服务关闭了…

​SSD在AI发展中的关键作用:从高速缓存到数据湖-2

二、大规模长期存储数据湖 大规模数据集&#xff1a; AI应用需要处理大量的数据&#xff0c;这些数据可能来自多个来源&#xff0c;包括图像、视频、文本、音频等。为了有效地管理这些数据&#xff0c;组织通常将其存储在大型的数据湖中。 容量扩展&#xff1a; 由于数据集的…

MS2502视频8位数模转换器

MS2502是低功率、超高速视频数模转换器。MS2502以从DC至20MHz的采样速率将 数字信号转换成模拟信号。由于高速工作&#xff0c;MS2502适合于数字电视、电脑视频处 理及雷达信号处理等数字视频应用。 MS2502工作于-20℃至85℃。 特点 1&#xff09;8位分辨率 2&#xff09…

ubuntu安装MySQL8

1.下载mysql8 MySQL :: Download MySQL Installer (Archived Versions) 选择对应的mysql版本和对应的ubuntu版本图即可 2.下载后上传到sftp文件夹中&#xff0c;然后通过以下命令解压 tar -xvf mysql-server_8.0.29-1ubuntu20.04_amd64.deb-bundle.tar 3.依次安装即可 &#…

飞越 Flyway!

在数据库 Schema 变更这个领域&#xff0c;业界最老牌的两个产品是 Liquibase 和 Flyway&#xff0c;两者都有超过 15 年的历史。 Liquibase 和 Flyway 都是由商业公司在背后支撑的开源项目。Liquibase 相对更偏商业化一些&#xff0c;而 Flyway 的社区感更强。在中国&#xff…