【数据结构】哈希表的原理及其实现

文章目录

  • 哈希表的概念
  • 哈希函数的设计
    • 常见的哈希函数
  • 哈希冲突
    • 1. 闭散列
      • 代码实现
    • 2. 开散列
    • 拉链法的优点
  • 针对开散列哈希的扩展
  • 基于开散列拉链法封装哈希表
    • MyHash.h
  • 基于哈希表实现unordered_map类
    • Myunordered_map.h
  • 基于哈希表实现unordered_set类
    • Myunordered_map.h

哈希表的概念

哈希表,也被称为散列表。是一种通过键可以快速找到对应值的一种数据结构。像我们常使用的unordered系列的容器,其本质就是一个哈希表。哈希表最显著的特点就是查找数据的效率非常的高,平均查找的时间复杂度为O(1)。通过哈希函数将键值映射到数组的具体位置,再对其进行操作。

哈希表查找的过程为:通过键来找到数组中的一个位置,通过这个位置我们能找到该键唯一映射的值
哈希表的y重要设计模块就是哈希函数以及解决哈希冲突的方法。下面将对此做出解释:

哈希函数的设计

哈希表的性能的一个关键因素就是哈希函数的设计。具体的来说,哈希函数指的是通过键值找到映射到数组具体位置的方法。一个好的哈希函数能将键均匀的分布到哈希表的各个位置。

常见的哈希函数

  1. 直接定值法
    取关键字的某个线性函数为散列地址,该线性函数的返回值应该是一个整数。比如Hash(key)=A*Key+B
  2. 除留余数法
    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址

哈希冲突

什么叫哈希冲突?哈希冲突又叫做哈希碰撞。具体是指,多个键通过一个哈希函数得到的位置相同。造成哈希冲突的主要原因是,哈希表的大小是有限的,而输入的数据可能会非常多,因此不可能避免出现哈希冲突。

那出现哈希冲突之后该如何解决呢?下面给出几种常见的解决哈希冲突的方法:

1. 闭散列

闭散列又叫开放定址法。当发生哈希冲突时,如果该哈希表还有空位,那么就从冲突位置开始往后找,直到找到空位。该如何往后找空位呢?一个一个找嘛?

  • 线性探测:线性探测,从发生冲突的位置开始,一个一个往后面找,直到找到空位。
    在这里插入图片描述
  • 二次探测:从发生冲突的位置开始,使用一个二次探测公式来寻找下一个探测的位置,探测公式为:
    H(k)=(Hash(k)+C1*i+C2*(i^2))%p
    • 其中H(k)表示的是第i次探测的位置。
    • Hash(k)表示通过哈希函数得到的初始地址
    • C1C2是一个常数,C1通常为0,C2通常为1,即H(k)=(Hash(k)+(i^2))%p
    • i是探测的次数,从0开始
    • p是哈希表的大小

二次探测的优点是能有效减少一次探测中数据过于“集中”的问题,即连续的冲突位置,会形成长链。而二次探测会使得探测的位置更加分散。

研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。

装载因子又叫做负载因子,是哈希表中元素个数与哈希表大小的比值

代码实现

哈希表类主要有两个模块需要实现,一个是节点类,还有一个是存储节点的容器。容器我们选择使用STL中的vector。对于节点类,我们希望至少能实现以下功能:

  • 存储键值对
  • 存储状态

值得注意的是,由于线性探测法的特性,删除一个元素之后可能会影响后续查找元素。我们需要给每个节点赋予三种状态表示:空节点、存在值得节点、被删除得节点。有了这三种种状态,我们在查找元素时,就不会因为中间曾经被删除的节点而影响。

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<vector>
using namespace std;
namespace Close_Hash{
	enum Statu{
		EMPTY,
		EXIST,
		DEL
	};


	template<class K,class V>
	struct Node {
		Node():_statu(EMPTY){}
		pair<K, V> _kv;
		Statu _statu;
	};

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

	template<>
	struct HashFunc<string> {
		size_t operator()(const string& key) {
			size_t res = 0;
			for (auto it : key) {
				int u = it - '0';
				res = res * 131 + u;
			}
			return res;
		}
	};


	template<class K,class V,class Hash=HashFunc<K>>
	class HashTable {
	public:
		HashTable(size_t capacity = 10)
			: _totalSize(0)
			, _ht(capacity)

		{

		}
		V& operator[](const K& key) {

			Node<K, V>* node = Find(key);
			if (node) {
				return (node->_kv).second;
			}
			else {
				this->Insert(make_pair(key,0));

				return Find(key)->_kv.second;
			}
		}
		// 插入
		bool Insert(const pair<K, V>& val) {
			//负载超标
			if (_totalSize * 10 >= 7 * _ht.size()) {
				HashTable<K, V> newtable;
				
				newtable._ht.resize(_ht.size() * 2);
				for (int i = 0; i < _ht.size(); i++) {
					if (_ht[i]._statu == EXIST) {
						newtable.Insert(_ht[i]._kv);
					}
				}
				_ht.swap(newtable._ht);
			 }
			Hash ha;
			size_t pos = ha(val.first) % _ht.size();
			//线性探测

			while (_ht[pos]._statu == EXIST) {
				pos++;
				pos = pos % _ht.size();
			}
			_ht[pos]._kv = val;
			_ht[pos]._statu = EXIST;
			_totalSize++;
			return true;

		}

		// 查找
		Node<K,V>* Find(const K& key) {
			Hash ha;
			size_t pos = ha(key) % _ht.size();
			while (_ht[pos]._statu != EMPTY) {
				if (_ht[pos]._statu == EXIST && _ht[pos]._kv.first == key) {
					return &_ht[pos];
				}
				pos++;
				pos = pos % _ht.size();
			}
			return nullptr;
		}

		// 删除
		bool Erase(const K& key) {
			Node<K, V>* node = Find(key);
			if (!node)return false;
			node->_statu = DEL;
			return true;

		}

		size_t Size() {
			return _ht.size();
		}
		bool Empty() const
		{
			return _ht.empty();
		}


	private:
		vector<Node<K,V>> _ht;
		size_t _totalSize;
	};

}

2. 开散列

开散列法又被叫做链地址法或者拉链法。和闭散列不同的是,具有相同地址的关键码属于同一集合,并用一个单链表维护这个集合。我们将这个集合称为。开散列得到哈希表的每一个元素实际上是一个名为桶的单链表。于是,当我们发生哈希冲突时,不用去哈希表中找空位了,因为当前桶是一个单链表,插入元素时直接头插就好了。
在这里插入图片描述

拉链法的优点

拉链法处理哈希冲突的效率是非常高的。即使多个元素通过哈希函数得到的地址是一样的,也不会显著影响哈希表的性能。此外,拉链法下的负载因子是可以超过1的,因此拉链法下的哈希表可以灵活的应对动态数据集的增长,不需要频繁的调整哈希表的大小。值得一提的是,虽然看上去会使用额外的空间来存放指针,但由于不需要严格维护平衡因子,空间效率并不会比开放定址法低。因为开放定址法需要开大量额外的空间来保证负载因子不溢出。

代码实现:

跟线性探测代码不一样的地方在于,拉链法的节点实际上是一个链表的头节点(桶)。所以对于哈希表的节点类,我们希望有以下功能:

  • 指向下一个元素的指针
  • 存储键值对

在插入元素时,将新节点插入到桶中。

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

namespace hash_bucket {
	
	template<class K,class V>
	struct HashNode {
		HashNode(const pair<K, V>& kv)
			: _kv(kv)
			,_next(nullptr)
		{

		}
		HashNode()
		   :_next(nullptr)
		{

		}
		pair<K, V> _kv;
		HashNode<K, V>* _next;
	};

	template<class K>
	struct HashFunc {
		size_t operator()(const K& k) {
			return (size_t)k;
		}
	};
	template<>
	struct HashFunc<string> {
		size_t operator()(const string& str) {
			size_t res = 0;
			for (auto it : str) {
				int u = it;
				res = res * 131 + u;
			}
			return res;
		}
	};

	template<class K,class V,class Hash=HashFunc<K>>
	class HashTable {
	public:
		typedef HashNode<K, V> Node;
		typedef Node* pNode;
		HashTable()
			:_table(10, nullptr)
			, _n(0)
		{

		}
		//随机访问
		V& operator[](const K& k) {
			if (!Find(k)) {
				Insert(make_pair(k,V()));
			}
			return Find(k)->_kv.second;
		}

		//插入
		bool Insert(const pair<K, V>& kv) {
			Hash ha;
			//负载因子为1时扩容
			if (_n == _table.size()) {
				HashTable<K, V> newtable;
				newtable._table.resize(_table.size() * 2);

				for (size_t i = 0; i < _table.size(); i++) {
					if (_table[i]) {
						pNode cur = _table[i];
						while (cur) {
							pNode temp = cur->_next;
							size_t pos = ha(cur->_kv.first) % newtable._table.size();
							if (newtable._table[pos]) {
								cur->_next = newtable._table[pos];
							}
							newtable._table[pos] = cur;
							cur = temp;
						}
					}
				}
				_table.swap(newtable._table);
			}
	
			size_t pos = ha(kv.first) % _table.size();
			pNode newnode = new Node(kv);
	 		if (_table[pos] != nullptr) {
				newnode->_next = _table[pos];
			}
			_table[pos] = newnode;
			_n++;
			return true;
		}

		//查找
		pNode Find(const K& k) {
			Hash ha;
			size_t pos= ha(k) % _table.size();
			pNode cur = _table[pos];
			while (cur) {
				if (cur->_kv.first == k)return cur;
				cur = cur->_next;
			}
			return nullptr;
		}

		//删除
		bool Erase(const K& k) {
			Hash ha;
			if (!Find(k))return false;
			size_t pos = ha(k) % _table.size();
			pNode pre = nullptr;
			pNode cur = _table[pos];
			if (cur->_kv.first == k) {
				delete cur;
				_table[pos] = nullptr;
				return true;
			}
			else {
				while (cur) {
					if (cur->_kv.first == k) {
						pre->_next = cur->_next;
						delete cur;
						cur = nullptr;
						return true;
					}
					pre = cur;
					cur = cur->_next;
				}
				return false;
			}
			
		}

	private:
		vector<pNode> _table;
		int _n;//存储有效桶的个数
	};

}

上述代码大致实现了基于开散列法的哈希表,支持键值对的插入,删除、查询以及修改。

注意开散列法哈希表的扩容方案。当负载因子达到一定值时,我们选择扩大哈希表的大小,对于旧哈希表的元素不用再重新拷贝构造,而是移植到新表中。这样就节省了很多空间。具体实现如下:

//负载因子为1时扩容
if (_n == _table.size()) {
	HashTable<K, V> newtable;//新表
	newtable._table.resize(_table.size() * 2);

	for (size_t i = 0; i < _table.size(); i++) {
		if (_table[i]) {//遍历旧表,移植节点
			pNode cur = _table[i];
			while (cur) {
				pNode temp = cur->_next;
				size_t pos = ha(cur->_kv.first) % newtable._table.size();
				if (newtable._table[pos]) {
					cur->_next = newtable._table[pos];
				}
				newtable._table[pos] = cur;
				cur = temp;
			}
		}
	}
	_table.swap(newtable._table);
}

针对开散列哈希的扩展

如何给上述开散列哈希表类增加迭代器呢?
由于迭代器中需要访问哈希表而不仅仅是桶,而哈希表又属于哈希表类的私有成员。因此,设计迭代器模块时,我们可以将迭代器类设置为哈希表类的内部类。而一个类的内部类是这个类的友元,因此迭代器就能访问到哈希表。给出以迭代器代码,每一个迭代器对象指向哈希表中桶内的一个节点

//迭代器内部类
template<class Ptr, class Ref>
struct _HashIterator {
	typedef _HashIterator<Ptr, Ref> self;
	pNode _node;
   const HashTable* _pht;

	_HashIterator(pNode pnode, const HashTable* pht)
		:_node(pnode)
		, _pht(pht)
	{

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

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

	self& operator=(const self it) {
		_node = it._node;
		_pht = it._pht;
		return *this;
	}

	self& operator++() {
		if (_node->_next) {
			_node = _node->_next;
		}
		else {
			KeyOft kft;
			Hash ha;
			size_t pos = ha(kft(_node->_data)) % _pht->_table.size();
			size_t i = pos + 1;
			for (; i < _pht->_table.size(); i++) {
				if (_pht->_table[i]) {
					break;
				}
			}
			if (i == _pht->_table.size()) {
				_node = nullptr;
			}
			else {
				_node = _pht->_table[i];
			}

		}
		return *this;
	}

	bool operator!=(const self& iterator) {
		return iterator._node != _node;
	}
};

该迭代器主要实现了重载操作符的作用,希望能将迭代器当指针来使用。

基于开散列拉链法封装哈希表

MyHash.h

该文件封装实现了一个哈希表类,哈希函数是除留余数,解决哈希冲突的方法采用拉链法。
代码:

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

namespace hash_bucket {
	template<class T>
	struct HashNode {
		HashNode(const T& data)
			: _data(data)
			, _next(nullptr)
		{

		}
		HashNode()
			:_next(nullptr)
		{

		}
		T _data;
		HashNode<T>* _next;
	};

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


	template<>
	struct HashFunc<string> {
		size_t operator()(const string& str) {
			size_t res = 0;
			for (auto it : str) {
				int u = it;
				res = res * 131 + u;
			}
			return res;
		}
	};


	template<class K, class T, class KeyOft, class Hash = HashFunc<K>>
	class HashTable {
	public:
		typedef HashNode<T> Node;
		typedef Node* pNode;
		HashTable()
			:_table(10, nullptr)
			, _n(0)
		{

		}
		//迭代器内部类
		template<class Ptr, class Ref>
		struct _HashIterator {
			typedef _HashIterator<Ptr, Ref> self;
			pNode _node;
		   const HashTable* _pht;

			_HashIterator(pNode pnode, const HashTable* pht)
				:_node(pnode)
				, _pht(pht)
			{

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

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

			self& operator=(const self it) {
				_node = it._node;
				_pht = it._pht;
				return *this;
			}

			self& operator++() {
				if (_node->_next) {
					_node = _node->_next;
				}
				else {
					KeyOft kft;
					Hash ha;
					size_t pos = ha(kft(_node->_data)) % _pht->_table.size();
					size_t i = pos + 1;
					for (; i < _pht->_table.size(); i++) {
						if (_pht->_table[i]) {
							break;
						}
					}
					if (i == _pht->_table.size()) {
						_node = nullptr;
					}
					else {
						_node = _pht->_table[i];
					}

				}
				return *this;
			}

			bool operator!=(const self& iterator) {
				return iterator._node != _node;
			}
		};

		typedef _HashIterator<T*, T&> Iterator;
		typedef _HashIterator<const T*, const T&> Const_Iterator;

		//迭代器
		Iterator begin() {
			for (size_t i = 0; i < _table.size(); i++) {
				if (_table[i]) {
					return Iterator(_table[i], this);
				}
			}
			return Iterator(nullptr, this);
		}

		Iterator end() {
			return Iterator(nullptr, this);
		}
		Const_Iterator begin()const {
			for (size_t i = 0; i < _table.size(); i++) {
				if (_table[i]) {
					return Iterator(_table[i], this);
				}
			}
			return Iterator(nullptr, this);
		}

		Const_Iterator end() const {
			return Iterator(nullptr, this);
		}



		//随机访问
		/*V& operator[](const K& k) {
			if (!Find(k)) {
				Insert(make_pair(k,V()));
			}
			return Find(k)->_kv.second;
		}*/

		//插入
		pair<Iterator, bool> Insert(const T& val) {

			Hash ha;
			KeyOft kft;
			Iterator it = Find(kft(val));
			if (it._node != nullptr)return make_pair(it, false);
			//负载因子为1时扩容
			if (_n == _table.size()) {
				HashTable<K, T, KeyOft> newtable;
				newtable._table.resize(_table.size() * 2);

				for (size_t i = 0; i < _table.size(); i++) {
					if (_table[i]) {
						pNode cur = _table[i];
						while (cur) {
							pNode temp = cur->_next;
							size_t pos = ha(kft(cur->_data)) % newtable._table.size();
							if (newtable._table[pos]) {
								cur->_next = newtable._table[pos];
							}
							newtable._table[pos] = cur;
							cur = temp;
						}
					}
				}
				_table.swap(newtable._table);
			}

			size_t pos = ha(kft(val)) % _table.size();
			pNode newnode = new Node(val);
			if (_table[pos] != nullptr) {
				newnode->_next = _table[pos];
			}
			_table[pos] = newnode;
			_n++;
			return make_pair(Iterator(newnode, this), true);
		}

		//查找
		Iterator Find(const K& k) {
			Hash ha;
			KeyOft kft;
			size_t pos = ha(k) % _table.size();
			pNode cur = _table[pos];
			while (cur) {
				if (kft(cur->_data) == k)return Iterator(cur, this);
				cur = cur->_next;
			}
			return Iterator(nullptr, this);
		}

		//删除
		bool Erase(const K& k) {
			Hash ha;
			KeyOft kft;
			if (!Find(k))return false;
			size_t pos = ha(k) % _table.size();
			pNode pre = nullptr;
			pNode cur = _table[pos];
			if (kft(cur->_data) == k) {
				delete cur;
				_table[pos] = nullptr;
				return true;
			}
			else {
				while (cur) {
					if (kft(cur->_data) == k) {
						pre->_next = cur->_next;
						delete cur;
						cur = nullptr;
						return true;
					}
					pre = cur;
					cur = cur->_next;
				}
				return false;
			}

		}

	private:
		vector<pNode> _table;
		int _n;
	};

}

基于哈希表实现unordered_map类

基于MyHash.h封装的哈希表类提供的接口,实现一个unordered_map。unordered_map的值是一个键值对。

Myunordered_map.h

该文件基本实现了unordered_map类。
代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include"MyHash.h"

namespace bit {
	template<class K, class V, class Hash =	hash_bucket::HashFunc<K> >
	class unordered_map {
		struct MapKeyOft {
			const K& operator()(const pair<K, V>& kv) {
				return kv.first;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOft, Hash>::Iterator iterator;
		//typedef typename hash_bucket::HashTable<K, pair<const K, V>, KeyOft, Hash>::Const_Iterator const_iterator;
		iterator begin() {
			return _ht.begin();
		}
		iterator end() {
			return _ht.end();
		}

		V& operator[](const K& k) {
			pair<iterator,bool>res=insert(make_pair(k, V()));
			return res.first->second;
		}

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

		bool erase(const K& k) {
			return _ht.Erase(k);
		}
		pair<iterator, bool> find(const K& k) {
			return _ht.Find(k);
		}

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

}

在这里插入图片描述

基于哈希表实现unordered_set类

基于MyHash.h封装的哈希表类提供的接口,实现一个unordered_set类。

Myunordered_map.h

该文件基本实现了unordered_set类。
代码:

#include"MyHash.h"

namespace bit {
	template<class K,class Hash = hash_bucket::HashFunc<K> >
	class unordered_set {
		struct SetKeyOft {
			const K& operator()(const K& k) {
				return k;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, const K, SetKeyOft, Hash>::Iterator iterator;
		typedef typename hash_bucket::HashTable<K, const K, SetKeyOft, Hash>::Const_Iterator const_iterator;
		iterator begin() {
			return _ht.begin();
		}
		iterator end() {
			return _ht.end();
		}

		

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

		bool erase(const K& k) {
			return _ht.Erase(k);
		}
		pair<iterator, bool> find(const K& k) {
			return _ht.Find(k);
		}

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

}


在这里插入图片描述

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

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

相关文章

ROCm上运行Transformer

10.7. Transformer — 动手学深度学习 2.0.0 documentation (d2l.ai) 代码 import math import pandas as pd import torch from torch import nn from d2l import torch as d2l#save class PositionWiseFFN(nn.Module):"""基于位置的前馈网络""&qu…

解决Error: error:0308010C:digital envelope routines::unsupported的四种解决方案

问题描述&#xff1a; 报错&#xff1a;Error: error:0308010C:digital envelope routines::unsupported 报错原因&#xff1a; 主要是因为 nodeJs V17 版本发布了 OpenSSL3.0 对算法和秘钥大小增加了更为严格的限制&#xff0c;nodeJs v17 之前版本没影响&am…

Rust后台管理系统Salvo-admin源码编译

1.克隆salvo-admin后台管理系统源码: https://github.com/lyqgit/salvo-admin.git 2.编译 编译成功 3.创建mysql数据库与执行sql脚本 输入名称ry-vue 执行sql脚本 全部执行上面3个sql 修改数据库用户名与密码: 清理及重新编译 cargo clean cargo build 4.运行并测试 cargo…

基于JAVA GUI体育馆管理系统的会员功能

Java GUI即Java图形用户界面&#xff0c;是一种使用图形化元素&#xff08;如窗口、按钮、文本框等&#xff09;来构建用户界面的技术。它基于Java的Swing框架&#xff0c;可以用于创建各种复杂的用户界面&#xff0c;包括窗口、对话框、菜单、按钮、文本框、复选框、下拉列表等…

仅需一块 4GB 的 GPU ,就能运行开源大语言模型:Llama3 70B

最强的开源大语言模型 Llama3 已经发布一段时间了&#xff0c;一些盆友资源有限&#xff0c;私信询问是否可以使用 4GB 的 VRAM 在本地运行 Llama3 70B。 与 GPT-4 相比&#xff0c;Llama3 的性能如何&#xff1f;Llama3 使用了哪些关键的前沿技术使其变得如此强大&#xff1f…

Oracle 并行和 session 数量的

这也就是为什么我们指定parallel为4&#xff0c;而实际并行度为8的原因。 insert create index&#xff0c;发现并行数都是加倍的 Indexes seem always created with parallel degree 1 during import as seen from a sqlfile. The sql file shows content like: CREATE INDE…

SwiftUI中List的样式及使用详解(添加、移动、删除、自定义滑动)

SwiftUI中的List可是个好东西&#xff0c;它用于显示可滚动列表的视图容器&#xff0c;类似于UITableView。在List中可以显示静态或动态的数据&#xff0c;并支持垂直滚动。List是一个数据驱动的视图&#xff0c;当数据发生变化时&#xff0c;列表会自动更新。 针对List&#x…

windows 7 10 11快捷键到启动页面

1.快速打开用户启动文件夹 shell:startup 方式2&#xff1a;快速打开系统启动文件夹 shell:Common Startup shell:Common Startup

燃数科技前端25-40K*14薪一面超简单,下周二面啦

​​​​​​​ 文章末尾扫描二维码领取地址 一面 1、自我介绍 2、低代码如何设计的 3、react路由原理 4、react生命周期 5、什么是回调地狱&#xff0c;如何解决 6、jwt和session有什么区别 7、js文件相互引用有什么问题&#xff1f;如何解决 8、一个很大的json文件…

腾讯TDSQL-C灰度发布列存索引; Azure Copilot集成支持Azure上各种托管数据库;

重要更新 1. Copilot for Azure新增了对Azure SQL、 Azure Database for MySQL的支持([8] [14])。Copilot for Azure是微软云提供的基于大模型技术的助手工具&#xff0c;主要能力包括了&#xff1a;该大模型可以获得最新的文档&#xff0c;以及客户的Azure资源情况&#xff0c…

每日百万交易的支付系统,如何设置JVM堆内存大小?

每日百万交易的支付系统,如何设置JVM堆内存大小? 1、支付背景的引入2、支付的核心业务流程3、每日百万交易支付系统的压力在哪里?4、支付系统每秒钟需要处理多少笔支付单5、每个支付订单处理需要耗时多久6、每个支付订单大概需要多大的内存空间7、每秒发起的支付请求对内存的…

9.3 Go语言入门(变量声明和函数调用)

Go语言入门&#xff08;变量声明和函数调用&#xff09; 目录二、变量声明和函数调用1. 变量声明1.1 使用 var 关键字声明1.2 简短声明1.3 零值1.4 常量 2. 函数调用2.1 函数定义2.2 多个返回值2.3 命名返回值2.4 可变参数2.5 匿名函数和闭包 目录 Go 语言&#xff08;Golang&a…

按月爬取天气数据可视化展示

从天气网分析,可以查询每个月的天气情况,这里按照url规则,传入年月,获取数据,最后进行可视化展示,最终效果: 下面是获取过程: 第一步: import requestsdef get_weather(month):url = f"https://lishi.tianqi.com/nanning/{month}.html"response = reques…

最新:windows下安装pcl点云库

&#x1f4da;博客主页&#xff1a;knighthood2001 ✨公众号&#xff1a;认知up吧 &#xff08;目前正在带领大家一起提升认知&#xff0c;感兴趣可以来围观一下&#xff09; &#x1f383;知识星球&#xff1a;【认知up吧|成长|副业】介绍 ❤️如遇文章付费&#xff0c;可先看…

日志的介绍及简单实现

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 日志是什么&#xff1f; 为什么需要日志&#xff1f; 实现一个简单日志 时间戳 clock_gettime time & localtime 可变模板参数(使用C语言)&#xff0c;va_start & va_end & vsprintf 宏 __LINE__…

Anaconda+CUDA+CUDNN+Pycharm+Pytorch安装教程(第一节 Anconda安装)

1.选择和对应的anconda版本 官网地址&#xff1a;Index of / (anaconda.com) 下载地址&#xff1a;Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 2.安装流程 (1)下载安装包 (2)点击next &#xff08;3&#xff09;点击I agree &a…

跨域计算芯片,一把被忽视的汽车降本尖刀

作者 |王博 编辑 |德新 2019年前后&#xff0c;「中央运算单元区域控制」的架构被提出。基于这一趋势&#xff0c;从板级的多芯片&#xff0c;到板级的单芯片&#xff0c;集成度越来越高&#xff0c;跨域计算芯片随之来到聚光灯下。 跨域计算芯片的特点是&#xff0c;与专为智…

[CISCN2024]-PWN:gostack解析(go语言程序,syscall)

查看保护 ida比较复杂&#xff0c;建议动调配合静态分析程序运行 这里函数返回不用leave和ret&#xff0c;而是利用add rsp和ret&#xff0c;所以要动调查看到底要覆盖哪里。 完整exp&#xff1a; from pwn import* pprocess(./gostack) syscall0x4616c9 pop_rax0x40f984 po…

THREE.JS中的向量点乘,以及他的几何意义。

1. THREE.JS中的向量点乘&#xff0c;以及他的几何意义 向量点乘的公式 : 2. 在three.js 中计算向量点乘 const a new THREE.Vector3(10, 10, 0); const b new THREE.Vector3(20, 0, 0); const dot a.dot(b);从这里可以看出&#xff0c;向量的点乘的结果是一个数字(标量…

【调和级数】100321. 优质数对的总数 II

本文涉及知识点 调和级数 质数、最大公约数、菲蜀定理 LeetCode100321. 优质数对的总数 II 给你两个整数数组 nums1 和 nums2&#xff0c;长度分别为 n 和 m。同时给你一个正整数 k。 如果 nums1[i] 可以被 nums2[j] * k 整除&#xff0c;则称数对 (i, j) 为 优质数对&#…