移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——14.哈希(2)(模拟实现)

news2024/11/19 21:48:09

1.概念介绍

1.1开散列

开散列(Open Hashing),也叫链地址法,是一种解决哈希冲突的方法。每个哈希表槽位保存一个链表,所有散列到同一位置的元素都存储在该链表中。当插入元素发生冲突时,将新元素添加到相应槽位的链表末尾。

ba807042c8964dd09d0c795f8be5ac37.png

1.2闭散列

闭散列(Closed Hashing),也叫开放地址法,是一种解决哈希冲突的方法。当插入元素发生冲突时,通过寻找下一个空槽位来存储冲突元素,常见策略包括1.线性探测、2.二次探测等,不使用链表存储冲突元素。

bd45a379160c427bbe211a88b230c64c.png

2.模拟实现 

2.1闭散列模拟实现

1.枚举——status

enum status  //使用枚举保存数据的“状态”,如果为EMPTY,DELETE则可以插入
{
	EMPTY,//记得EMPTY要写在最上面,这样,系统默认的构造函数才会将s初始化为EMPTY
	EXIST,
	DELETE
};

2.数组中的元素——hashdata

template<class K,class V>
struct hashdata
{
	pair<K, V> data;     //数据
	status s;    //状态
};

3.将key元素转化为可计算的形式——hashfunc

为了确定元素应当插入到哪个位置,需要把key取出来

template<class K>
struct hashfunc     //因为key不一定是整形,如果能强制转换成整形,那就要转换
{
	size_t operator()(const K& key)
	{
		return size_t(key);          
	}
};

单独为string类写一份:

template<>
struct hashfunc<string>     //单独为k=string写一份,还记得嘛,这是模板的特化!!!!!!!这样,在初始化hashtable时就不用再传仿函数模板参数了
{
	size_t operator()(const string& key)
	{
		size_t flag = 0;;
		for (auto e : key)
		{
			flag *= 31;//这一步可以保证abc,acb的flag不同,防止第一步就发生冲突
			flag += e;//本质是遍历整个string并使每个字母的ascii码相加,当然也可以使用其他的方式
		}
		return flag;
	}
};

模板特化相关知识:移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——9.模板进阶-CSDN博客

4.容器——hashtable

私有成员:

 private:
  vector<hashdata<K,V>> table;
  size_t num=0;//储存的关键字的个数

2.2.hashtable的功能实现

1.初始化

hashtable()
{
	table.resize(10);
}

2.插入!!!!!!

bool insert(const pair<K, V>& kv)
{
	if (find(kv.first))
	{
		return false;
	}
	//负载因子,指关键字个数在总size中的占比,(越大代表发生hash冲突的概率越大)普遍超出0.7时就要扩容了,扩容需要重新开一份空间!!!!!!!!!因为映射关系被打乱了
	if (num * 10 / table.size() == 7)//这里很巧妙
	{
		size_t newsize = table.size() * 2;
		hashtable<K, V,hash> newtable;
		newtable.table.resize(newsize);
		//遍历旧表
		for (size_t i = 0; i < table.size();i++)
		{
			if (table[i].s == EXIST)
			{
				newtable.insert(table[i].data);
			}
		}

		table.swap(newtable.table);//记得交换一下
	}


	hash hf;
	//1.线性探测(解决hash冲突的方法)
	size_t position = hf(kv.first) % table.size();//应用映射公式 hash(key) = key %  capacity (注意!!!!!这里要用table.size(),而不是table.capacity(),所以要除余
	while ((table[position]).s == EXIST)//如果当前的位置非空,则往后顺延一位!!!!!!
	{
		position++;
		position %= table.size();//positin走到底后回到0
	}
	table[position].data = kv;
	table[position].s= EXIST;
	++num;
    
	return true;
}

3.查找

hashdata<K, V>* find(const K& key)//查找是从确定的初始位置查找到nullptr!!!!!结束,因为没到nullptr前,都有可能是因为冲突导致数据后移
{
	hash hf;
	size_t position =hf(key)% table.size();
	while (table[position].s != EMPTY)
	{
		if (table[position].data.first == key&& table[position].s==EXIST)
		{
			return &table[position];
		}
		position++;
		position %= table.size();
	}
	return NULL;
}

4.删除

bool erase(const K& key)
{
	hashdata<K, V>* ret = find(key);
	if (ret)
	{
		ret->s = DELETE;
		--num;
		return true;
	}
	else
	{
		return false;
	}
}

2.3开散列模拟实现

开散列存储的本质是指针数组

1.数组中的元素——hashnode

template<class T> 
struct hashnode
{
	T data;     //数据
	hashnode* next;


	hashnode(T kv)
		:data(kv)
		,next(nullptr)
	{}

};

2. 容器——hashtable

私有成员:

private:
	vector<node*> table;
	size_t num = 0;

};

2.4.hashtable内容实现

1.初始化

hashtable()
{
	table.resize(10);
}

2.析构函数

~hashtable()
{
	for (size_t i = 0; i < table.size(); i++)
	{
		node* cur = table[i];
		while (cur)
		{
			node* next = cur->next;
			delete cur;
			cur = next;
		}
		table[i] = nullptr;//最后置空
	}
	
}

3.查找

node* find(const K& key)
{

	hash hf;
	typeoft tt;

	size_t position = hf(key) % table.size();
	node* cur = table[position];
	while (cur)
	{
		if (tt(cur->data)== key)
			return cur;
		cur = cur->next;
	}
	return nullptr;
}

4.插入

bool insert(const T& kv)
{
	hash hf;
	typeoft tt;

	if (find(tt(kv)))
	return true;

	if (num == table.size())//当负载因子等于1时要扩容
	{
		vector<node*> newtable;
		newtable.resize(table.size()* 2, nullptr);
		//遍历旧表
		for (size_t i = 0; i < table.size(); i++)
		{
			node* cur = table[i];
			while (cur)
			{
				node* next = cur->next;
				size_t newposition = hf(tt(cur->data)) % newtable.size();
				cur->next = newtable[newposition];
				newtable[newposition] = cur;
				cur = next;
			}

			table[i] = nullptr;//数据原来的位置处一定要置空,否则会因为二次析构产生问题
		}

		table.swap(newtable);//直接交换两个哈希桶(底层指针的交换)
	}
	size_t position = hf(tt(kv)) % table.size();
	node* newnode = new node(kv);
	node* cur = table[position];
	
	//头插
	newnode->next = cur;
	table[position] = newnode;
	num++;
	
}

 3. 迭代器的设置(以开散列为例)!!!!!!!!

1.hsiterator的设置与功能

//前置声明,因为哈希表用到了迭代器,迭代器也用到了哈希表,这叫做相互依赖,需要做前置声明
template<class K, class T, class typeoft, class hash >
class hashtable;

template<class K, class T,class ref,class ptr, class typeoft,class hash=hashfunc<K>>
struct hsiterator
{

	typedef hashnode<T> node;
	const hashtable< K, T, typeoft, hash> &point;//这里使用引用是为了防止析构影响原来的table
	typedef hsiterator<K, T,ref,ptr, typeoft,hash> Self;

	node* _node;
	size_t place;

	hsiterator(node* node_, const hashtable< K, T, typeoft, hash> &_point,size_t _place)
		:_node(node_)
		,point(_point)
		,place(_place)
	{}


	Self operator++()
	{
		if (_node->next)//如果—_node->next不为空,那么桶里面还有数据,走next
		{
			_node = _node->next;
		}
		else     //如果为空,那么需要走到下一个桶
		{
			typeoft tt;
			hash hf;
			//size_t head = hf(tt(_node->data)) % point.table.size();//找到初始位置,方便转移至下一个桶
			++place;
			while (place < point.table.size())
			{
				if (point.table[place])
				{
					_node = point.table[place];
					break;
				}
				
				else
				{
					place++;
				}
			}


			if (place == point.table.size())
			{
				_node = nullptr;
			}

			return *this;

		}
	}


	ref operator*()
	{
		return _node->data;
	}

	ptr operator->()
	{
		return &_node->data;
	}


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

2.hashtable中对hsiterator的封装 

template<class K, class T, class ref, class ptr,class typeoft,class hash>
friend struct hsiterator;//这里设置了友元,这样,hsiterator就可以直接取到hashtable的private成员table数组了

typedef hsiterator<K, T,T&,T*, typeoft, hash> iterator;//普通迭代器
typedef hsiterator<K, T, const T&, const T*, typeoft, hash> const_iterator;//const迭代器

iterator begin()
{
	for (size_t i = 0; i < table.size(); i++)
	{
		if (table[i])
		return iterator(table[i], *this, i);
	}

	return end();
}


iterator end()
{
	return iterator(nullptr, *this, -1);//-1是随便给的
}

4.unorderedmap&&unorderedset封装

1.取出K元素(仿函数)

struct setkeyoft //仿函数
{
	   const K& operator()(const K& key)
	   {
		   return key;
	   }

};

2.迭代器封装

set的iterator全部使用const迭代器:

 typedef typename hashtable<K,K,setkeyoft>::const_iterator iterator;
 typedef typename hashtable<K,K,setkeyoft>::const_iterator const_iterator;
/* iterator begin()
 {
		   return table.begin();
 }

 iterator end()
 {
		   return table.end();
 }*/
 const_iterator begin()const
 {
		   return table.begin();
 }

 const_iterator end() const
 {
		   return table.end();
 }

map的迭代器正常分类使用:

typedef typename hashtable<K, pair<const K, V>, setkeyoft>::iterator iterator;
typedef typename hashtable<K, pair<const K, V>, setkeyoft>::const_iterator const_iterator;

iterator begin()
{
	return table.begin();
}

iterator end()
{
	return table.end();
}

const_iterator begin() const
{
	return table.begin();
}

const_iterator end()  const
{
	return table.end();
}

5.代码全览

1.hash.h

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

namespace close_address//闭散列
{
	enum status  //使用枚举保存数据的“状态”,如果为EMPTY,DELETE则可以插入
	{
		EMPTY,//记得EMPTY要写在最上面,这样,系统默认的构造函数才会将s初始化为EMPTY
		EXIST,
		DELETE
	};


	template<class K,class V>
	struct hashdata
	{
		pair<K, V> data;     //数据
		status s;    //状态
	};


	template<class K>
	struct hashfunc     //因为key不一定是整形,如果能强制转换成整形,那就要转换
	{
		size_t operator()(const K& key)
		{
			return size_t(key);          
		}
	};

	template<>
	struct hashfunc<string>     //单独为k=string写一份,还记得嘛,这是模板的特化!!!!!!!这样,在初始化hashtable时就不用再传仿函数模板参数了
	{
		size_t operator()(const string& key)
		{
			size_t flag = 0;;
			for (auto e : key)
			{
				flag *= 31;//这一步可以保证abc,acb的flag不同,防止第一步就发生冲突
				flag += e;//本质是遍历整个string并使每个字母的ascii码相加,当然也可以使用其他的方式
			}
			return flag;
		}
	};



	
	//struct hashfuncstring     //这是单独为string类写的转换仿函数
	//{
	//	size_t operator()(const string& key)
	//	{
	//		size_t flag = 0;;
	//		for (auto e : key)
	//		{
	//			flag *= 31;//这一步可以保证abc,acb的flag不同
	//			flag += e;//本质是遍历整个string并使每个字母的ascii码相加,当然也可以使用其他的方式
	//		}
	//		return flag;
	//	}
	//};



  template<class K,class V,class hash=hashfunc<K>>//这里hash直接给了缺省值,如果K可以转化,就可以在初始化的时候可以不给hash的模板参数
  class hashtable
  {
    public:

		hashtable()
		{
			table.resize(10);
		}

		
		hashdata<K, V>* find(const K& key)//查找是从确定的初始位置查找到nullptr!!!!!结束,因为没到nullptr前,都有可能是因为冲突导致数据后移
		{
			hash hf;
			size_t position =hf(key)% table.size();
			while (table[position].s != EMPTY)
			{
				if (table[position].data.first == key&& table[position].s==EXIST)
				{
					return &table[position];
				}
				position++;
				position %= table.size();
			}
			return NULL;
		}


		bool insert(const pair<K, V>& kv)
		{
			if (find(kv.first))
			{
				return false;
			}
			//负载因子,指关键字个数在总size中的占比,(越大代表发生hash冲突的概率越大)普遍超出0.7时就要扩容了,扩容需要重新开一份空间!!!!!!!!!因为映射关系被打乱了
			if (num * 10 / table.size() == 7)//这里很巧妙
			{
				size_t newsize = table.size() * 2;
				hashtable<K, V,hash> newtable;
				newtable.table.resize(newsize);
				//遍历旧表
				for (size_t i = 0; i < table.size();i++)
				{
					if (table[i].s == EXIST)
					{
						newtable.insert(table[i].data);
					}
				}

				table.swap(newtable.table);//记得交换一下
			}


			hash hf;
			//1.线性探测(解决hash冲突的方法)
			size_t position = hf(kv.first) % table.size();//应用映射公式 hash(key) = key %  capacity (注意!!!!!这里要用table.size(),而不是table.capacity(),所以要除余
			while ((table[position]).s == EXIST)//如果当前的位置非空,则往后顺延一位!!!!!!
			{
				position++;
				position %= table.size();//positin走到底后回到0
			}
			table[position].data = kv;
			table[position].s= EXIST;
			++num;
            
			return true;
		}


		bool erase(const K& key)
		{
			hashdata<K, V>* ret = find(key);
			if (ret)
			{
				ret->s = DELETE;
				--num;
				return true;
			}
			else
			{
				return false;
			}
		}


		void print()
		{
			for (size_t i = 0; i < table.size(); i++)
			{
				if (table[i].s == EXIST)
				{
					//printf("[%d]->%d\n", i, table[i].data.first);
					cout << "[" << i << "]->" << table[i].data.first << endl;
				}
				else if (table[i].s == EMPTY)
				{
					//printf("[%d]->空余\n", i);
					cout << "[" << i << "]->空余" << endl;
				}
				else
				{
					//printf("[%d]->删除\n", i);
					cout << "[" << i << "]->删除" << endl;
				}
			}
		}

	  private:
	   vector<hashdata<K,V>> table;
	   size_t num=0;//储存的关键字的个数
  };

  void test1()
  {
	  hashtable<int, int> it;
	  int a[] = { 4,14,24,34,5,7,1 };
	  for (auto e : a)
	  {
		  it.insert(make_pair(e, e));
	  }
	  it.insert(make_pair(3, 3));
	  it.insert(make_pair(3, 3));
	  it.insert(make_pair(-3, -3));

	  it.print();
	  cout << endl;
	  it.erase(3);
	  it.print();
  }

  void test2()
  {
	  hashtable<string, int> it;
	  string arr[] = { "香蕉","苹果" ,"西瓜" ,"苹果" ,"香蕉" ,"香瓜" ,"苹果" ,"香蕉" };
	  for (auto e : arr)
	  {
		  auto f = it.find(e);//hashdata<K,V>*
		  if (f)
		  {
			  f->data.second++;
		  }

		  else
		  {
			  it.insert(make_pair(e, 1));
		  }
	  }
	  it.print();
  }
}




namespace open_address//开散列
{

	template<class K>
	struct hashfunc     //因为key不一定是整形,如果能强制转换成整形,那就要转换
	{
		size_t operator()(const K& key)
		{
			return size_t(key);
		}
	};

	template<>
	struct hashfunc<string>     //还记得嘛,这是模板的特化!!!!!!!这样,在初始化hashtable时就不用再传仿函数模板参数了
	{
		size_t operator()(const string& key)
		{
			size_t flag = 0;;
			for (auto e : key)
			{
				flag *= 31;//这一步可以保证abc,acb的flag不同,防止第一步就发生冲突
				flag += e;//本质是遍历整个string并使每个字母的ascii码相加,当然也可以使用其他的方式
			}
			return flag;
		}
	};




	template<class T>
	struct hashnode
	{
		T data;     //数据
		hashnode* next;


		hashnode(T kv)
			:data(kv)
			,next(nullptr)
		{}

	};


	//前置声明,因为哈希表用到了迭代器,迭代器也用到了哈希表,这叫做相互依赖,需要做前置声明
	template<class K, class T, class typeoft, class hash >
	class hashtable;

	template<class K, class T,class ref,class ptr, class typeoft,class hash=hashfunc<K>>
	struct hsiterator
	{

		typedef hashnode<T> node;
		const hashtable< K, T, typeoft, hash> &point;//这里使用引用是为了防止析构影响原来的table
		typedef hsiterator<K, T,ref,ptr, typeoft,hash> Self;

		node* _node;
		size_t place;

		hsiterator(node* node_, const hashtable< K, T, typeoft, hash> &_point,size_t _place)
			:_node(node_)
			,point(_point)
			,place(_place)
		{}


		Self operator++()
		{
			if (_node->next)//如果—_node->next不为空,那么桶里面还有数据,走next
			{
				_node = _node->next;
			}
			else     //如果为空,那么需要走到下一个桶
			{
				typeoft tt;
				hash hf;
				//size_t head = hf(tt(_node->data)) % point.table.size();//找到初始位置,方便转移至下一个桶
				++place;
				while (place < point.table.size())
				{
					if (point.table[place])
					{
						_node = point.table[place];
						break;
					}
					
					else
					{
						place++;
					}
				}


				if (place == point.table.size())
				{
					_node = nullptr;
				}

				return *this;

			}
		}


		ref operator*()
		{
			return _node->data;
		}

		ptr operator->()
		{
			return &_node->data;
		}


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





	template<class K, class T, class typeoft ,class hash = hashfunc<K>>
	class hashtable
	{
		

	public:
		typedef hashnode<T> node;
		


		template<class K, class T, class ref, class ptr,class typeoft,class hash>
		friend struct hsiterator;//这里设置了友元,这样,hsiterator就可以直接取到hashtable的private成员table数组了

		typedef hsiterator<K, T,T&,T*, typeoft, hash> iterator;//普通迭代器
		typedef hsiterator<K, T, const T&, const T*, typeoft, hash> const_iterator;//const迭代器

		iterator begin()
		{
			for (size_t i = 0; i < table.size(); i++)
			{
				if (table[i])
				return iterator(table[i], *this, i);
			}

			return end();
		}


		iterator end()
		{
			return iterator(nullptr, *this, -1);//-1是随便给的
		}


		const_iterator begin()const
		{
			for (size_t i = 0; i < table.size(); i++)
			{
				if (table[i])
					return const_iterator(table[i], *this, i);
			}

			return end();
		}



		const_iterator end() const
		{
			return const_iterator(nullptr, *this, -1);//-1是随便给的
		}


		hashtable()
		{
			table.resize(10);
		}


		~hashtable()
		{
			for (size_t i = 0; i < table.size(); i++)
			{
				node* cur = table[i];
				while (cur)
				{
					node* next = cur->next;
					delete cur;
					cur = next;
				}
				table[i] = nullptr;//最后置空
			}
			
		}

		node* find(const K& key)
		{

			hash hf;
			typeoft tt;

			size_t position = hf(key) % table.size();
			node* cur = table[position];
			while (cur)
			{
				if (tt(cur->data)== key)
					return cur;
				cur = cur->next;
			}
			return nullptr;
		}

		

		bool insert(const T& kv)
		{
			hash hf;
			typeoft tt;

			if (find(tt(kv)))
			return true;

			if (num == table.size())//当负载因子等于1时要扩容
			{
				vector<node*> newtable;
				newtable.resize(table.size()* 2, nullptr);
				//遍历旧表
				for (size_t i = 0; i < table.size(); i++)
				{
					node* cur = table[i];
					while (cur)
					{
						node* next = cur->next;
						size_t newposition = hf(tt(cur->data)) % newtable.size();
						cur->next = newtable[newposition];
						newtable[newposition] = cur;
						cur = next;
					}

					table[i] = nullptr;//数据原来的位置处一定要置空,否则会因为二次析构产生问题
				}
		
				table.swap(newtable);//直接交换两个哈希桶(底层指针的交换)
			}
			size_t position = hf(tt(kv)) % table.size();
			node* newnode = new node(kv);
			node* cur = table[position];
			
			//头插
			newnode->next = cur;
			table[position] = newnode;
			num++;
			
		}


		bool erase(const K& key)
		{
			hash hf;
			typeoft tt;

			size_t position = hf(key) % table.size();
			node* cur = table[position];
			node* prev = nullptr;
			while (cur)
			{

				if (tt(cur->data) == key)
				{
					if (prev)
					{
						prev->next = cur->next;
						delete cur;
						num--;
					}

					else
					{
						table[position] = nullptr;
						num--;
					}
					return true;
				}


				prev = cur;
				cur = cur->next;

			}
			return false;
		}


	private:
		vector<node*> table;
		size_t num = 0;

	};
	

}

2.myunorderedmap.h

#include"hash.h"

using namespace open_address;

namespace zone
{
	template<class K, class V>
	class unorderedmap
	{
	public:

		

		struct setkeyoft
		{
			const K& operator()(const pair<const K, V>& key)
			{
				return key.first;
			}
		};


		typedef typename hashtable<K, pair<const K, V>, setkeyoft>::iterator iterator;
		typedef typename hashtable<K, pair<const K, V>, setkeyoft>::const_iterator const_iterator;

		iterator begin()
		{
			return table.begin();
		}

		iterator end()
		{
			return table.end();
		}

		const_iterator begin() const
		{
			return table.begin();
		}

		const_iterator end()  const
		{
			return table.end();
		}






		bool insert(const pair<K,V>& key)
		{
			return table.insert(key); 
		}


	private:
		hashtable<K, pair<const K,V>, setkeyoft> table;
	};


	void testmap()
	{
		unorderedmap<string, string> it;
		it.insert(make_pair("sort","排序"));
		it.insert(make_pair("right","右"));
		it.insert(make_pair("left","左"));
		it.insert(make_pair("middle","中"));
		for (auto e : it)
		{
			e.second += 'x';//map的value可改变,但key不能改变
			cout << e.first<<' '<<e.second<<endl;//记得加一个.first,因为重载的operator*,只会取得data,在map中就是pair<k,v>,所以要用.first取得key
		}
		

	}


}

3.myunorderedset.h

#include"hash.h"

using namespace open_address;

namespace zone
{
	template<class K>
	class unorderedset
	{
	   public:
		   struct setkeyoft //仿函数
		   {
			   const K& operator()(const K& key)
			   {
				   return key;
			   }

		   };

		   typedef typename hashtable<K,K,setkeyoft>::const_iterator iterator;
		   typedef typename hashtable<K,K,setkeyoft>::const_iterator const_iterator;
		  /* iterator begin()
		   {
			   return table.begin();
		   }

		   iterator end()
		   {
			   return table.end();
		   }*/
		   const_iterator begin()const
		   {
			   return table.begin();
		   }

		   const_iterator end() const
		   {
			   return table.end();
		   }




		   bool insert(const K& key)
		   {
			   return table.insert(key);
		   }


	   private:
		   hashtable<K,K, setkeyoft> table;

	};

	void testset()
	{
		unorderedset<int> it;
		it.insert(2);
		it.insert(3);
		it.insert(14);
		it.insert(24);
		it.insert(34);
		unorderedset<int>::iterator arr = it.begin();
		while (arr != it.end())
		{
			//*arr += 5;//set的key不可修改
			cout << *arr << endl;

			++arr;
		}
	
		
	}

}

 

 

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

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

相关文章

使用Web Speech API实现语音识别与合成技术

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Web Speech API实现语音识别与合成技术 使用Web Speech API实现语音识别与合成技术 使用Web Speech API实现语音识别与合成技…

自动驾驶系列—面向自动驾驶的模型迭代:工具、平台与最佳实践

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

【Golang】——Gin 框架中的模板渲染详解

Gin 框架支持动态网页开发&#xff0c;能够通过模板渲染结合数据生成动态页面。在这篇文章中&#xff0c;我们将一步步学习如何在 Gin 框架中配置模板、渲染动态数据&#xff0c;并结合静态资源文件创建一个功能完整的动态网站。 文章目录 1. 什么是模板渲染&#xff1f;1.1 概…

网络基础 - NAT 篇

一、全局 IP 地址(公网 IP 地址)和私有 IP 地址 RFC 1918 规定了用于组建局域网的私有 IP 地址&#xff1a; 10.0.0.0 ~ 10.255.255.255172.16.0.0 ~ 172.31.255.255192.168.0.0 ~ 192.168.255.255 包含在以上范围内的 IP 地址都属于私有 IP 地址&#xff0c;而在此之外的 I…

ClickHouse的介绍、安装、数据类型

1、介绍和安装 1.1、简介 ClickHouse是俄罗斯的Yandex于2016年开源的列式存储数据库&#xff08;DBMS&#xff09;&#xff0c;使用C语言编写&#xff0c;主要用于在线分析处理查询&#xff08;OLAP&#xff09;&#xff0c;能够使用SQL查询实时生成分析数据报告。 OLAP&…

基于AOA算术优化的KNN数据聚类算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于AOA算术优化的KNN数据聚类算法matlab仿真。通过AOA优化算法&#xff0c;搜索最优的几个特征数据&#xff0c;进行KNN聚类&#xff0c;同时对比不同个数特征下…

tcp 超时计时器

在 TCP&#xff08;传输控制协议&#xff09;中有以下四种重要的计时器&#xff1a; 重传计时器&#xff08;Retransmission Timer&#xff09; 作用&#xff1a;用于处理数据包丢失的情况。当发送方发送一个数据段后&#xff0c;就会启动重传计时器。如果在计时器超时之前没有…

《Probing the 3D Awareness of Visual Foundation Models》论文解析——多视图一致性

一、论文简介 论文讨论了大规模预训练产生的视觉基础模型在处理任意图像时的强大能力&#xff0c;这些模型不仅能够完成训练任务&#xff0c;其中间表示还对其他视觉任务&#xff08;如检测和分割&#xff09;有用。研究者们提出了一个问题&#xff1a;这些模型是否能够表示物体…

【论文阅读】WaDec: Decompiling WebAssembly Using Large Language Model

论文阅读笔记:WaDec: Decompiling WebAssembly Using Large Language Model 1. 来源出处 论文标题: WaDec: Decompiling WebAssembly Using Large Language Model作者: Xinyu She, Yanjie Zhao, Haoyu Wang会议: 39th IEEE/ACM International Conference on Automated Softwar…

【数字孪生】从Abaqus到Unity有限元应力云图

从abaqus到unity&#xff1a; 目录 1. 数据准备 1.1 abaqus中提取element rpt文件 element rpt文件格式&#xff1a; 1.2 abaqus中提取node rpt文件&#xff1a; node rpt文件格式&#xff1a; 2. python预处理以上数据&#xff1a; 2.1 提取node rpt中的节点坐标及应力…

一次需升级系统的wxpython安装(macOS M1)

WARNING: The scripts libdoc, rebot and robot are installed in /Users/用户名/Library/Python/3.8/bin which is not on PATH. 背景&#xff1a;想在macos安装Robot Framework &#xff0c;显示pip3不是最新&#xff0c;更新pip3后显示不在PATH上 参看博主文章末尾 MAC系统…

MySQL45讲 第二十五讲 高可用性深度剖析:从主备原理到策略选择

文章目录 MySQL45讲 第二十五讲 高可用性深度剖析&#xff1a;从主备原理到策略选择一、MySQL 主备基础原理&#xff08;一&#xff09;主备关系与数据同步&#xff08;二&#xff09;主备切换流程 二、主备延迟分析&#xff08;一&#xff09;主备延迟的定义与计算&#xff08…

跨越网络边界:IPv6与零信任架构的深度融合

2024年&#xff0c;工信部发布了《关于开展“网络去NAT”专项工作 进一步深化IPv6部署应用的通知》&#xff0c;加速了国内网络由IPv4向IPv6的转型步伐。未来&#xff0c;各行各业将逐步去NAT&#xff0c;逐步向IPv6迁移。在此过程中&#xff0c;网络安全解决方案和产品能力将面…

Linux—ln(link files)命令使用方法(How to create links on Linux)

Linux—ln&#xff08;link files&#xff09;命令使用方法 在 Linux 系统中工作时&#xff0c;需要在不同的目录中使用相同的文件时&#xff0c;不必在每个目录下都复制一份文件&#xff0c;这样不仅浪费磁盘空间&#xff0c;还会导致文件管理上的混乱。 ln(link files) 便是…

我要成为算法高手-位运算篇

目录 1. 判断字符是否唯一2. 消失的数字3. 两整数之和4. 只出现一次的数字II5. 消失的两个数字 前情提要&#xff1a;如果对一些常见的二进制位运算不熟悉&#xff0c;请看这篇文章&#xff1a; 常见的位运算 1. 判断字符是否唯一 面试题 01.01. 判定字符是否唯一 - 力扣&…

1Panel 推送 SSL 证书到阿里云、腾讯云

本文首发于 Anyeの小站&#xff0c;点击链接 访问原文体验更佳 前言 都用 CDN 了还在乎那点 1 年证书钱么&#xff1f; 开句玩笑话&#xff0c;按照 Apple 的说法&#xff0c;证书有效期不该超过 45 天。那么证书有效期的缩短意味着要更频繁地更新证书。对于我这样的“裸奔”…

23种设计模式-访问者(Visitor)设计模式

文章目录 一.什么是访问者模式&#xff1f;二.访问者模式的结构三.访问者模式的应用场景四.访问者模式的优缺点五.访问者模式的C实现六.访问者模式的JAVA实现七.代码解释八.总结 类图&#xff1a; 访问者设计模式类图 一.什么是访问者模式&#xff1f; 访问者模式&#xff08;…

JavaScript——DOM编程、JS的对象和JSON

一、DOM编程 DOM(Document Object Model)编程&#xff1a;就是使用document对象的API&#xff0c;完成对网页HTML文档进行动态修改&#xff0c;以实现网页数据&#xff0c;和样式动态变化效果的编程。 (一)DOM获取元素的多种方法 1.查找元素的函数 getElementById("id值…

Pr:音频过渡

Adobe Premiere Pro 自带一组共三个音频过渡 Audio Transitions效果。 对音频剪辑之间应用交叉淡化 Crossfade过渡&#xff0c;操作方式类似于应用视频过渡效果。 对于交叉淡化&#xff0c;要保证前剪辑的出点之后及后剪辑的入点之前有足够的预留内容&#xff08;也称“手柄”&…

大数据-226 离线数仓 - Flume 优化配置 自定义拦截器 拦截原理 拦截器实现 Java

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇开始了&#xff01; 目前开始更新 MyBatis&#xff0c;一起深入浅出&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff0…