C++:哈希表特性及开散列哈希表的模拟实现

目录

一、unordered_map

1.1 特性

1.2 接口

1.21 构造函数

1.22 iterator find(const K&  key)

1.23 insert

1.24 operator[]

1.25 erase

1.26 find

1.3 哈希概念

1.31闭散列哈希表

1.32开散列哈希表

二、部分功能模拟实现

hashtable.h

unordered_map.h

unordered_set.h


一、unordered_map

1.1 特性

a.可以存储键值对,pair<K,V>类型

b.可以按照任意指定的方式对元素排序

c.通过key访问单个元素比map快

d.重载了operator[],可以通过key直接访问到value

e.是一个前向迭代器,支持++,但不支持--操作,其实也可以支持,不过代价太大了,效率会很低,可以想象成单链表。所以干脆不支持了。

1.2 接口

这里介绍需要注意的接口,其余的接口基本上看一眼文档就能了解

1.21 构造函数

支持迭代器区间构造,

支持给定初始的键值对构造

支持拷贝构造。

用法实例,这里用的是cplusplus.com的示例代码,本节用法示例中都是。

// constructing unordered_maps
#include <iostream>
#include <string>
#include <unordered_map>

typedef std::unordered_map<std::string,std::string> stringmap;

stringmap merge (stringmap a,stringmap b) {
  stringmap temp(a); temp.insert(b.begin(),b.end()); return temp;
}

int main ()
{
  stringmap first;                              // empty
  stringmap second ( {{"apple","red"},{"lemon","yellow"}} );       // init list
  stringmap third ( {{"orange","orange"},{"strawberry","red"}} );  // init list
  stringmap fourth (second);                    // copy
  stringmap fifth (merge(third,fourth));        // move
  stringmap sixth (fifth.begin(),fifth.end());  // range

  std::cout << "sixth contains:";
  for (auto& x: sixth) std::cout << " " << x.first << ":" << x.second;
  std::cout << std::endl;

  return 0;
}

1.22 iterator find(const K&  key)

此处返回的是key在哈希桶的位置,返回的是一个迭代器。

1.23 insert

(1)插入返回的是pair,pair的first是一个迭代器,指向的是插入成功/失败后的位置,成功就是插入成功的值的位置,失败就是end()(基本上是这个)

second是bool值,插入成功是1,失败是0。下面代码在VS编译后也验证了unordered_map是不能插入相同的key值的。第二个ttest2是插入失败的。

1.24 operator[]

方括号重载。

int main ()
{
  std::unordered_map<std::string,std::string> mymap;

  mymap["Bakery"]="Barbara";  // new element  inserted
  mymap["Seafood"]="Lisa";    // new element  inserted
  mymap["Produce"]="John";    // new element  inserted

  std::string name = mymap["Bakery"];   // existing element accessed (read)
  mymap["Seafood"] = name;              // existing element accessed (written)

  mymap["Bakery"] = mymap["Produce"];   // existing elements accessed (read/written)

  name = mymap["Deli"];      // non-existing element: new element "Deli" inserted!

  mymap["Produce"] = mymap["Gifts"];    // new element "Gifts" inserted, "Produce" written

  for (auto& x: mymap) {
    std::cout << x.first << ": " << x.second << std::endl;
  }

  return 0;
}

当哈希表中没有该元素时,使用方括号重载会插入该元素,并返回该元素的second的引用,当哈希表中有该元素时,返回它的second的引用.

下图只是为了方便理解,实际中它哈希表中用的哈希函数(为了避免哈希冲突)哪种排序我不知道。所以单词的顺序可能有差别,但是键值对是这样发生改变的。

1.25 erase

给定要删除的元素的迭代器,给定要删除的元素迭代器区间,给定要删除的元素的key值,都可以删除掉。

int main ()
{
  std::unordered_map<std::string,std::string> mymap;

  // populating container:
  mymap["U.S."] = "Washington";
  mymap["U.K."] = "London";
  mymap["France"] = "Paris";
  mymap["Russia"] = "Moscow";
  mymap["China"] = "Beijing";
  mymap["Germany"] = "Berlin";
  mymap["Japan"] = "Tokyo";

  // erase examples:
  mymap.erase ( mymap.begin() );      // erasing by iterator
  mymap.erase ("France");             // erasing by key
  mymap.erase ( mymap.find("China"), mymap.end() ); // erasing by range

  // show content:
  for ( auto& x: mymap )
    std::cout << x.first << ": " << x.second << std::endl;

  return 0;
}

示例中,通过迭代器的开始删除了第一个;通过key,删除了key为France的键值对;通过迭代器区间,删除了两个。

1.26 find

查找,可以通过key值查找,返回的是迭代器,const函数就是const迭代器。

1.3 哈希概念

哈希是一种一一映射的概念,有点像数学中的函数。通过建立映射关系,就可以通过这种法则,快速找到映射的另一个。

哈希表运用了哈希概念。它把待存储的数据和哈希表中的下标建立映射关系,提到下标,那么存储的容器当然是顺序表,通过数据的值本身就能飞快访问到数据所存储的位置。这种访问是时间复杂度是O(1)。

对于整型数据,整数1可以存在下标为1的位置,其他整数也以此类推。但数量过大的整数或者分布不太密集的整数可能会造成空间的浪费。因此需要有更加好的法则来建立一一映射的关系。

这种法则就是对数据进行初步处理之后,得到一个相应的下标,再把这个数存在相应的下标中。

这种处理数据的哈希函数有很多。处理后的下标可能是两个相同的数,比如哈希函数是原始数据%10,那么1%10==1,101%10==1,那么这两个数看起来似乎都要存在1,数据就会造成覆盖,这时就会造成哈希冲突。当然也可以把函数设置的复杂一点比如加上^&多种运算来建立映射关系。

再好的哈希函数都无法避免哈希冲突,因为数字是无限多的,而顺序表的容量是有限的。我们只能尽量避免哈希冲突。

1.31闭散列哈希表

线性探测解决。

线性探测的思路:如果1%10==1,1放在下标为1的位置,101%10==1,1的位置已经有值了,那么101就往1的后面没有值的位置找。如果2位置是空的,那么101就存在2这里了。但是2的位置被占领以后,如果有102%10==2,那么102就不能存在2的位置,依然要往后找,这样如果哈希冲突的数据比较多,很多数就被迫放在后面,那么查找的时间就消耗多一点,需要一直往后找。

所以一般情况下,不会让这个哈希表太满,数据/容量=0.7左右。这个值也叫负载因子。

后面又有了开散列来解决哈希冲突。

1.32开散列哈希表

它是一个顺序表,每个节点是一个指针,下面挂着的都是指针。相当于这个哈希表是一个指针数组。

如果遇到哈希冲突,就把这个值进行头插。需要找的时候,就在这个链表上找。这个方法一般设置

负载因子为1,在元素数据等于哈希表容量时,就要扩容。这样每个节点下也不会说挂太多值。在好的哈希函数之下,即使有哈希冲突,节点下面挂的节点并不会太多,查找的效率很高,已经可以看作O(1)的时间复杂度了。

二、部分功能模拟实现

本次主要模拟实现开散列的哈希表。

分析:

1.准备四个文件,一个是哈希表的头文件,然后再装迭代器,再装unordered_map和unordered_set,最后放在test.cpp文件里进行测试。

2.主要实现插入,删除,查找和扩容功能。

3.迭代器要实现前置++和后置++等常用功能。

细节很多,可以多了解一下官方的功能,然后再写。

hashtable.h

#pragma once
#include <iostream>
using namespace std;
#include <vector>
#include <string>
#include <assert.h>
#include <utility>

template<class K>
class HashFunc//哈希函数,仿函数,传一个val返回val
{
public:
	size_t operator()(const K& val)
	{
		return val;
	}
};

template<>
class HashFunc<string>//特化,如果是字符串,走这个仿函数
{
public:
	size_t operator()(const string& s)
	{
		const char* str = s.c_str();
		unsigned int seed = 131; // 31 131 1313 13131 131313
		unsigned int hash = 0;
		while (*str)
		{
			hash = hash * seed + (*str++);
		}

		return hash;//把字符串经过一些处理转变为unsigned int,达到映射,减少哈希冲突
	}
};
namespace OpenHash
{
	
	template<class K>
	struct HashBucketNode//哈希桶中的节点
	{
		HashBucketNode(const K& data)
			: _pNext(nullptr), _data(data)
		{}
		HashBucketNode<K>* _pNext;
		K _data;
	};

	template<class K, class T, class KeyOfValue, class HF>
	class HashBucket;

	// 注意:因为哈希桶在底层是单链表结构,所以哈希桶的迭代器不需要--操作
	//map和set中迭代器是相同的,所以都放到hashtable.h中来实现了
	template <class K, class T, class KeyOfValue, class HF>
	struct HBIterator
	{
		typedef HashBucket<K, T, KeyOfValue, HF> HashBucket;
		typedef HashBucketNode<T>* PNode;
	public:
		typedef HBIterator<K, T, KeyOfValue, HF> Self;

		HBIterator(PNode pNode = nullptr, HashBucket* pHt = nullptr)//实现一下构造
			:_pNode(pNode)
			,_pHt(pHt)
		{

		}
		Self& operator++()//前置++
		{
			// 当前迭代器所指节点后还有节点时直接取其下一个节点
			if (_pNode->_pNext)
			{
				_pNode = _pNode->_pNext;
			}
			else
			{
				// 找下一个不空的桶,返回该桶中第一个节点
				size_t bucketNo = _pHt->HashFunc(KeyOfValue()(_pNode->_data)) + 1;
				for (; bucketNo < _pHt->BucketCount(); ++bucketNo)
				{
					if (_pNode = _pHt->_table[bucketNo])
						break;
				}
			}

			return *this;
		}
		Self operator++(int)//实现后置++
		{
			Self it = *this;
			if (_pNode->_pNext)
				_pNode = _pNode->_pNext;
			else
			{
				// 找下一个不空的桶,返回该桶中第一个节点
				size_t bucketNo = _pHt->HashFunc(KeyOfValue()(_pNode->_data)) + 1;
				for (; bucketNo < _pHt->BucketCount(); ++bucketNo)
				{
					if (_pNode = _pHt->_table[bucketNo])
						break;
				}
			}

			return it;
		}
		T& operator*()
		{
			return _pNode->_data;
		}
		T* operator->()
		{
			return &_pNode->_data;
		}
		bool operator==(const Self& it) const
		{
			return _pNode == it._pNode;
		}
		bool operator!=(const Self& it) const
		{
			return _pNode != it._pNode;
		}
		PNode _pNode;             // 当前迭代器关联的节点
		HashBucket* _pHt;         // 哈希桶--主要是为了找下一个空桶时候方便
	};
	// 本文所实现的哈希桶中key是唯一的
	template<class K, class T, class KeyOfValue, class HF>
	class HashBucket
	{
		//把迭代器列为友元,这样迭代器就可以使用哈希桶中的成员了
		template <class K, class T, class KeyOfValue, class HF>
		friend struct HBIterator;

		typedef HBIterator<K, T, KeyOfValue, HF> iterator;
		typedef HashBucketNode<T> Node;//把节点重命名为Node
		typedef Node* PNode;
		typedef HashBucket< K, T, KeyOfValue, HF> Self;

	public:

		size_t GetNextPrime(const size_t & prime)
		{
			const int PRIMECOUNT = 28;
			static const size_t primeList[PRIMECOUNT] =
			{
				53ul, 97ul, 193ul, 389ul, 769ul,
				1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
				49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
				1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
				50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
				1610612741ul, 3221225473ul, 4294967291ul
			};

			// 获取比prime大那一个素数
			size_t i = 0;
			for (; i < PRIMECOUNT; ++i)
			{
				if (primeList[i] > prime)
					return primeList[i];
			}

			return primeList[PRIMECOUNT-1];
		}
		HashBucket(size_t capacity)
			: _table(GetNextPrime(capacity))
			, _size(0)
		{}
		HashBucket()
			:_table(GetNextPrime(0))
			,_size(0)
		{}
		//添加了迭代器
		iterator begin()
		{
			for (size_t i = 0; i < _table.size();++i)
			{
				if(_table[i])
				{
					return iterator(_table[i], this);
				}
			}
			return end();
		}
		iterator end()
		{
			return iterator(nullptr, this);
		}

		~HashBucket()
		{
			Clear();
		}

		// 哈希桶中的元素不能重复
		//返回的节点从Node*改为pair<iterator,bool>
		pair<iterator,bool> Insert(const T& data)
		{
			CheckCapacity();//检查一下容量,满了扩容
			KeyOfValue kov;
			size_t index  = HashFunc(kov(data));//取data映射的位置
			PNode cur = _table[index];//这个位置的指针
			
			while (cur)//指针不为空
			{
				if (cur->_data == data)
				{
					return { { cur, this }, false };
				}
				cur = cur->_pNext;
			}

			//出来以后,没有返回,说明这里没有重复值
			//插入
			PNode newnode = new Node(data);
			newnode->_pNext = _table[index];
			_table[index] = newnode;
			++_size;
			return { {newnode, this}, true };
		}

		// 删除哈希桶中为data的元素(data不会重复)
		bool Erase(const K& key)
		{
			//1.如果哈希表中没有元素,删除失败
			if (_table.size() == 0)
			{
				return false;
			}
			
			//2.找data的映射值
			KeyOfValue kov;
			size_t index = HashFunc(key);
			PNode cur = _table[index];
			PNode prev = nullptr;

			while (cur)
			{
				if (kov(cur->_data) != key)
				{
					prev = cur;
					cur = cur->_pNext;
				}
				else
				{
					break;
				}
			}
			//找到了
			if (prev)//cur不是头节点
			{
				prev->_pNext = cur->_pNext;
			}
			else
			{
				_table[index] = cur->_pNext;
			}
			delete cur;
			_size--;

			return true;
		}
		iterator Find(const K& key)
		{
			if (_table.size() == 0)
			{
				return iterator(nullptr,this);
			}
			
			KeyOfValue kov;
			size_t index = HashFunc(key);
			index %= _table.size();
			PNode cur = _table[index];
			while (cur)
			{
				if (kov(cur->_data) == key)
				{
					return iterator(cur,this);
				}
				cur = cur->_pNext;
			}
			return iterator(nullptr,this);
		}

		size_t Size()const
		{
			return _size;
		}

		bool Empty()const
		{
			return 0 == _size;
		}

		void Clear()
		{
			size_t i = 0;
			for (; i < _table.size();++i)
			{
				PNode cur = _table[i];
				while (cur)
				{
					PNode next = cur->_pNext;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
			_size = 0;
		}

		size_t BucketCount()const
		{
			return _table.capacity();
		}

		void Swap(Self& ht)
		{
			_table.swap(ht._table);
			swap(_size, ht._size);
		}
 
		 size_t BucketSize(const K& key)
		{
			 return HashFunc(key);
		}

	private:
		size_t HashFunc(const K& data)
		{
			return HF()(data) % _table.capacity();  
		}

		void CheckCapacity()
		{
			if (_size == _table.size())
			{
				size_t newcapacity = GetNextPrime(_table.capacity());
				Self newtable(newcapacity);
				for (size_t i = 0; i < _table.size();++i)
				{
					PNode cur = _table[i];
					while (cur)
					{
						newtable.Insert(cur->_data);
						cur = cur->_pNext;
					}
				}
				Swap(newtable);
			}
		}

	private:
		vector<Node*> _table;
		size_t _size;      // 哈希表中有效元素的个数
	};
}

unordered_map.h

这里typedef 的时候是pair<const K, V>,不是pair<K , V>,请注意。

#pragma once
#include "hashtable.h"


namespace ting
{

    // unordered_map中存储的是pair<K, V>的键值对,K为key的类型,V为value的类型,HF哈希函数类型
    // unordered_map在实现时,只需将hashbucket中的接口重新封装即可
    template<class K, class V, class HF = HashFunc<V>>
    class unordered_map
    {
        // 通过key获取value的操作
        struct mapKeyOfValue
        {
            const K& operator()(const pair<K, V>& kv) 
            {
                return kv.first;
            }
        };
    public:
        typedef typename OpenHash::HBIterator<K, pair<const K, V>, mapKeyOfValue, HF> iterator;
        typedef typename OpenHash::HashBucket<K, pair<const K, V>, mapKeyOfValue, HF>  HT;
        //typedef OpenHash::HBIterator<K, pair<K,V>, KeyOfValue, HF> iterator;
 
        //typename typedef HT::Iterator iterator;
        unordered_map() : _ht()
        {}

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

        // capacity
        size_t size()const { return _ht.size(); }
        bool empty()const { return _ht.empty(); }

        // Access
        V& operator[](const K& key)
        {
            pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
            return ret.first->second;
        }
        const V& operator[](const K& key)const
        {
            pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
            return ret.first->second;
        }

        // lookup
        iterator find(const K& key) { return _ht.Find(key); }
        size_t count(const K& key) { return _ht.Count(key); }

        // modify
        pair<iterator, bool> insert(const pair<K,V> value)
        {
            return _ht.Insert(value);
        }

        iterator erase(iterator position)
        {
            return _ht.Erase(position);
        }

        // bucket
        size_t bucket_count() { return _ht.BucketCount(); }
        size_t bucket_size(const K& key) { return _ht.BucketSize(key); }
    private:
        HT _ht;
    };
    void maptest()
    {
        unordered_map<string, string> m;
        m.insert(make_pair("left",""));
        m.insert(make_pair("right",""));
        m.insert(make_pair("medium",""));

        for (auto e : m)
        {
            cout << e.first << endl;
        }
    }
}

unordered_set.h

#pragma once
#include "hashtable.h"


namespace ting
{
	 //unordered_set中存储的是K类型,HF哈希函数类型
	// unordered_set在实现时,只需将hashbucket中的接口重新封装即可
	template<class K, class HF = HashFunc<K>>
	class unordered_set
	{
		
		// 通过key获取value的操作
		struct setKeyOfValue
		{
			const K& operator()(const K& data)
			{
				return data;
			}
		};
		
	public:
		typedef typename OpenHash::HBIterator<K, K, setKeyOfValue, HF> iterator;
		typedef typename OpenHash::HashBucket<K, K, setKeyOfValue, HF> HT;
		unordered_set() : _ht() {}

		
		iterator begin() { return _ht.begin(); }
		iterator end() { return _ht.end(); }
		
		// capacity
		size_t size()const { return _ht.size(); }
		bool empty()const { return _ht.empty(); }
		///
		// lookup
		iterator find(const K& key) { return _ht.Find(key); }
		size_t count(const K& key) { return _ht.Count(key); }
		/
		// modify
		pair<iterator, bool> insert(const K& value)
		{
			return _ht.Insert(value);
		}

		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}
		
		// bucket
		size_t bucket_count() { return _ht.BucketCount(); }
		size_t bucket_size(const K& key) { return _ht.BucketSize(key); }
	private:
		HT _ht;
	};

	void settest()
	{
		unordered_set<int> s1;
		s1.insert(1);
		s1.insert(2);
		s1.insert(3);

		for (auto e : s1)
		{
			cout << e << endl;
		}
		unordered_set<int>::iterator it = s1.find(1);
		it = s1.begin();

		while (it != s1.end())
		{
			cout << *it << endl;
			++it;
		}

	}
}

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

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

相关文章

通用链接系统:亚太地区主权数据链接

通用链接系统 - (ULS) ULS 是一款多功能、模块化且可扩展的多数据链路处理器 (DLP)&#xff0c;可配置为包含所有 NATO、美国标准数据链路以及客户定制链路。除了是极为可靠且久经考验的多数据链路网关引擎外&#xff0c;ULS 还通过生成和传播通用作战图 (COP) 来增强态势感知…

排序算法(3)之冒泡排序

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 排序算法(3)之冒泡排序 收录于专栏【数据结构初阶】 本专栏旨在分享学习数据结构学习的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 …

超详细Python安装教程(包含python解释器和pycharm)

目录 一&#xff0c;安装python解释器 二&#xff0c;安装PyCharm开发工具 一&#xff0c;安装python解释器 下载地址&#xff1a;https://www.python.org/downloads/ 如果是在windows上下载的话&#xff0c;选择Downloads->Windows 我选择了最新版本的64位安装&#xf…

RSTP和STP

RSTP&#xff08;Rapid Spanning Tree Protocol&#xff0c;快速生成树协议&#xff09;是STP&#xff08;Spanning Tree Protocol&#xff0c;生成树协议&#xff09;的一个改进版本&#xff0c;主要区别在于RSTP通过引入新的机制来加快网络的收敛速度。具体来说&#xff1a; …

数据分析案例-2024 年热门动漫数据集可视化分析

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

黑马点评-Postman卡住sending Requst原因解决

不知道为什么&#xff0c;用这个c1e1d5的token就会一直卡死&#xff0c;但是换了一个token就解决了&#xff0c;目前不知道为什么 解决了&#xff0c;原来是这个请求下面的函数发生了死循环&#xff01;&#xff01;太瓜皮了我超&#xff01; 把num写成了count&#xff0c;导…

7月18日Workshop新加坡专场:在Sui上创建您自己的token

您是base在新加坡&#x1f1f8;&#x1f1ec;的开发者吗&#xff1f; 您是否觉得编程课程枯燥乏味&#xff1f;&#x1f914; 那么&#xff0c;Sui和Metaschool将一起来为您的学习增添乐趣&#xff01; 欢迎加入我们特别为Web3初学者、开发者设组织的“在Sui上创建您自己的t…

linux下Jenkins的安装部署

前言&#xff1a; 用docker安装Jenkins非常方便快捷&#xff0c;但是最近国内的docker镜像源都不好用了&#xff0c;这里回顾一下最原始的Jenkins安装方式 安装前准备 安装环境 Jenkins的运行依赖java环境&#xff0c;linux下jdk的安装参考&#xff1a;linux下JDK的安装-CSD…

2024“狮舞齐鲁”南北狮王争霸赛在临沭开锣

锣鼓声声&#xff0c;狮王争霸。5月2日&#xff0c;临沭县红色朱村旅游区内人头攒动&#xff0c;热闹非凡。备受瞩目的“狮舞齐鲁”南北狮王争霸赛在此开赛&#xff0c;吸引了来自山东省的40支队伍、400余名参赛选手齐聚一堂&#xff0c;共同献上一场精彩绝伦的舞狮盛宴。 此次…

检测机构的配方分析是怎样?

检测机构配方分析是指检测机构通过对样品的化学成分、结构和性质进行分析&#xff0c;以确定其组成和配方的一种服务。检测机构通常提供以下服务&#xff1a; 1. 样品采集与前处理&#xff1a;检测机构通常会指导客户采集具有代表性的样品&#xff0c;并进行必要的前处理&#…

Blender练习,凳面以及凳脚的制作

目录 ​ 1.凳面的制作 2.制作坐垫下面的两条杠 3.制作桌腿 要制作的凳子如图&#xff0c;可以看到&#xff0c;它是一个由长方体&#xff0c;圆柱体等多种几何图形合成的一个立体图形 1.凳面的制作 shifta创建一个正方体 ctrltab打开一个弹窗&#xff0c;选择编辑模式。…

Linux离线安装Mysql5.7

Linux之Mysql安装配置 第一种&#xff1a;Linux离线安装Mysql&#xff08;提前手动下载好tar.gz包&#xff09; 第二种&#xff1a;通过yum安装配置Mysql&#xff08;服务器有网络&#xff09; 之前在阿里云上采用yum安装过一次&#xff08;请看这里&#xff09;&#xff0c;…

区块链资料

Quantstamp - Public Security Assessments smart-contract-sanctuary-bsc/contracts/mainnet at master tintinweb/smart-contract-sanctuary-bsc GitHub https://github.com/slowmist/Cryptocurrency-Security-Audit-Guide/blob/main/README_CN.md sFuzz: 高效自适应的智…

SpringBoot结合ip2region实现博客评论显示IP属地

你好呀&#xff0c;我是小邹。 在现代的Web应用中&#xff0c;特别是博客和论坛类网站&#xff0c;为用户提供地理定位服务&#xff08;如显示用户所在地理位置&#xff09;可以极大地增强用户体验。本文将详细探讨如何使用Java和相关技术栈来实现在博客评论中显示用户的地址信…

大雾弥散动态特效404单页源码

源码介绍 大雾弥散动态特效404单页源码,跟随鼠标反向移动,可以做网站错误页或者丢失页面,将下面的代码放到空白的HTML里面,然后上传到服务器里面,设置好重定向即可 效果预览 完整源码 <!DOCTYPE html> <html><head><meta charset="utf-8&quo…

react自定义校验报错问题修复 ProFormText

1、以下是tsx组件 自定义校验告警导致表单无法提交问题修复 修改如下&#xff1a;

Spring使用外部的属性文件

首先&#xff0c; 我们在Spring里配置一个数据源Datasource&#xff0c;看具体代码&#xff1a; 我们说这样配置数据源&#xff0c;是可以实现&#xff0c; 但是我们不建议讲数据源配置在Spring的配置文件里&#xff0c;因为&#xff0c;这里会配置很多bean&#xff0c;而我们的…

W外链创建抖音私信卡片教程,私信卡片跳转微信工具

W外链地址wai.cn 在数字化时代的浪潮中&#xff0c;私域流量的价值愈发凸显&#xff0c;成为企业获取用户、建立品牌忠诚度、提升转化率的关键手段。抖音&#xff0c;作为当下最热门的短视频社交平台之一&#xff0c;其用户基数庞大、互动性强&#xff0c;为企业私域引流提供了…

爬虫(一)——爬取快手无水印视频

前言 最近对爬虫比较感兴趣&#xff0c;于是浅浅学习了一些关于爬虫的知识。爬虫可以实现很多功能&#xff0c;非常有意思&#xff0c;在这里也分享给大家。由于爬虫能实现的功能太多&#xff0c;而且具体的实现方式也有所不同&#xff0c;所以这里开辟了一个新的系列——爬虫…

FY4B卫星L2级产品掌握和python处理

废话不多说&#xff0c;展示二级产品CTT为例&#xff1a; 抱歉没空了解FY4B产品情况了&#xff0c;直接看代码 # CTT色标配置 bounds_CTT [180, 200, 220, 240, 260, 280, 300, 320] # 根据你的数据设定 colors_CTT [(0, 0, 139/255),(10/255, 0, 245/255),(0, 164/255, 2…