【C++】哈希——哈希的概念,应用以及闭散列和哈希桶的模拟实现

news2024/10/5 18:32:39

前言:

       前面我们一同学习了二叉搜索树,以及特殊版本的平衡二叉搜索树,这些容器让我们查找数据的效率提高到了O(log^2 N)。虽然效率提高了很多,但是有没有一种理想的方法使得我们能提高到O(1)呢?其实在C语言数据结构中,我们接触过哈希表,他可以使效率提高到O(1)。        

      哈希表作为STL中我们所必须学习和了解的容器,是一种一一映射的存储方式,其次它在日常生活中的应用范围也是很广的,例如位图,海量数据筛选中用到的布隆过滤器等等……

      下面我们就来先学习一下STL中的应用哈希表的两个容器,再了解一下底层结构 (两个关联式容器unordered_map和unordered_set,unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构),最后再来模拟实现一下。

目录

(一)STL中底层应用哈希的两个容器

1、unordered_set

2、unordered_map

(二)常见的查找性能对比

(三)哈希表的概念及模拟实现

1、哈希的概念

2、哈希函数

3、哈希冲突

4、闭散列——开放定址法

5、开散列——链地址法(开链法),哈希桶

(四)详细代码


(一)STL中底层应用哈希的两个容器

在STL中对应的容器分别是unordered_map和unordered_set这两个关联式容器。、

我们会用map和set,其实就会用unordered_map和unordered_set这两个容器,但是这两类容器是有区别的!

我们一一分析:

1、unordered_set

文档链接->unordered_set文档

我们使用一下unordered_set的接口函数:


void test_unordered_set1()
{
	unordered_set<int> s1;
	s1.insert(1);
	s1.insert(2);
	s1.insert(9);
	s1.insert(2);
	s1.insert(3);
	s1.insert(3);
	s1.insert(4);
	s1.insert(5);


	unordered_set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	for (auto e : s1)
	{
		cout << e << " ";
	}
}

 

结果是实现了存储+去重,但是是无序的。

由上图和查阅资料得知:

  • map和set: 去重 + 排序
  • unordered_map和unordered_set: 只有去重

这里主要原因是底层实现不同,map和set底层是红黑树,unordered_set和unordered_map底层是红黑树。

其余函数接口和之前所学的容器使用起来大致相同,不再一一赘述。

unordered_map和unordered_set都是单向迭代器:

值得注意的是unordered_map和unordered_set的迭代器都是单向迭代器,而我们之前学的map和set则是双向迭代器(所以迭代器可以++也可以--)。

unordered_set和set的性能对比:


int main()
{
	const size_t N = 100000;
	unordered_set<int> us;
	set<int> s;

	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand());
		//v.push_back(rand()+i);
		//v.push_back(i);

	}


	size_t begin1 = clock();
	for (auto e : v)
	{
		s.insert(e);
	}
	size_t end1 = clock();
	cout << "set insert:" << end1 - begin1 << endl;


	size_t begin2 = clock();
	for (auto e : v)
	{
		us.insert(e);
	}
	size_t end2 = clock();
	cout << "unordered_set insert:" << end2 - begin2 << endl;


	size_t begin3 = clock();
	for (auto e : v)
	{
		s.find(e);
	}
	size_t end3 = clock();
	cout << "set find:" << end3 - begin3 << endl;

	size_t begin4 = clock();
	for (auto e : v)
	{
		us.find(e);
	}
	size_t end4 = clock();
	cout << "unordered_set find:" << end4 - begin4 << endl << endl;

	size_t begin5 = clock();
	for (auto e : v)
	{
		s.erase(e);
	}
	size_t end5 = clock();
	cout << "set erase:" << end5 - begin5 << endl;

	size_t begin6 = clock();
	for (auto e : v)
	{
		us.erase(e);
	}
	size_t end6 = clock();
	cout << "unordered_set erase:" << end6 - begin6 << endl << endl;
	


	return 0;
}

数据随机但有重复:                                  数据随机但重复少

数据连续无重复:

总结:

总的来说unordered_map和unordered_set要比map和set的性能要好的,但是也并不是一定的,当数据量很大的时候,扩容重新哈希是有消耗的。

2、unordered_map

文档链接->unordered_map文档

我们使用unordered1_map的接口函数:

void test_unordered_map()
{
	string arr[] = { "梨","梨","苹果","梨","西瓜","西瓜" };
	unordered_map<string, int> m;
	for (auto& e : arr)
	{
		m[e]++;
	}

	for (auto& kv : m)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
}
int main()
{
	
	test_unordered_map();

	return 0;
}

 

总体来说,unordered_map和map的用法差不多,但是他们的效率有所不同。

(二)常见的查找性能对比

  • 暴力查找: 时间复杂度〇(N)
  • 二分查找: 时间复杂度〇(logN) ,缺点 — 有序、数组结构
  • 搜索二叉树: 时间复杂度〇(N),缺点 — 极端场景退化成单支
  • 平衡二叉搜索树: 时间复杂度〇(logN)
  • AVLTree: 左右子树高度差不超过1
  • 红黑树:最长路径不超过最短路径的2倍
  • 哈希
  • B树系列: 多叉平衡搜索树 — 数据库原理
  • 跳表

ps:

红黑树高度略高一些,但是跟AVL树是同一数量级,对于现代计算机没有差别但是红黑树相对而言近似平衡,旋转少。

(三)哈希表的概念及模拟实现

1、哈希的概念

我们已学过的查找 :

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素
时,必须要经过关键码的多次比较顺序查找时间复杂度为O(N),平衡树中为树的高度,即
O(log_2 N),搜索的效率取决于搜索过程中元素的比较次数。

理想的查找方法:

可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立
一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
  • 该中存储结构可以实现:
    • 插入元素时:
      根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。
    • 查找元素时:
      对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。
该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称
为哈希表(Hash Table)(或者称散列表)

2、哈希函数

我们如何一一将键值转换为对应的关键码值,并映射到对应序号的存储位置呢?

直接建立映射关系问题:

  • 1、数据范围分布很广、不集中(可能存在空间浪费严重的问题)
  • 2、key的数据不是整数,是字符串怎么办?是自定义类型对象怎么办?

此时我们就需要一个函数对特殊非整数类型的数据进行处理,使其返回一个特定的整数,这个函数我们叫做 —— 哈希函数。
 

常见的哈希函数:

1、直接定址法(常用)

  • 取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
  • 优点:简单、均匀
  • 缺点:需要事先知道关键字的分布情况
  • 使用场景:适合查找比较小且连续的情况

2、除留余数法(常用)

设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,
按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址

3、其余常见但不常用的还有 平方取中法、折叠法、随机数法、数学分析法等。

字符串也有自己类型的哈希函数----->参考文献(了解即可)


3、哈希冲突

不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。

按照上述哈希函数计算出键值对应的关键码值,但是算出来的这些码值当中,有很大的可能会出现关键码值相同的情况,这种情况就叫作:哈希冲突。

  •  哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突。
  •  解决哈希冲突两种常见的方法是:闭散列和开散列

4、闭散列——开放定址法

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有
空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢?
这就用到了我们的—— 线性探测:(依次去找空位置)
如下图中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4,
因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。

 线性探测:

从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

插入:

  • 通过哈希函数获取待插入元素在哈希表中的位置
  • 线性探测找到空位置将值插入

查找:

  • 挨个遍历哈希表,直到找到空为止

删除:

  • 过关键码值再用线性探测找到该值直接删除
  • 注意:
  • 删除要是直接删除的话有可能会影响查找的准确性
  • 如图删除了4,要去找44就会找不到。

那怎么办呢?
 

  • 所以我们给每个键值提供一个状态,采取伪删除的方法

即通过对每一个数据加上一个标识状态即可:

线性探测的缺点:

  • 一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。

所以我们引入了二次探测跳跃着找空位置,是相对上面方法的优化,使得数据可能不那么拥堵。


 所以经过上面的介绍,我们想自己实现一个闭散列需要注意以下的几点:

  1. 哈希函数的选择
  2. 哈希冲突的应对
  3. 如何应对“堆积”导致效率低下的情况
  4. 如何扩容表

第一点和第二点我们已经在上文介绍过了,这里我们应用的就是开放定址法和线性探测。

  • 第三点:如何应对“堆积”导致效率低下的情况?

查询资料:

首先根据上文我们知道闭散列哈希表并不能太满:

  • 太满就会导致线性探测时,找不到位置
  • 更不能放满,那样探测就会陷入死循环
  • 所以要控制一下存储的数据
  • 我们引入了一个变量n来记录存储数据的个数

散列表的载荷因子定义为: a = 填入表中的元素个数 / 散列表的长度

所以我们要控制一下负载因子:

  • 第四点:负载因子超了如何扩容?

有人会说直接resize扩容就行了啊。但是,你没注意到一个问题:

我在刚刚那个表中又插入了13,这时按理说应该扩容了防止效率变低。

假如我扩容到20格,我想找13的时候根据哈希函数,13不应该在编号是13的格子中吗?但是我是存储在3中啊,这就矛盾了...

所以扩容时我们不能直接将原来的数据拷贝过去:

  • 因为哈希是映射的关系,关键码值是通过数据和表的大小计算出来的
  • 如果直接拷贝的话全都乱套了
  • 这时我们需要重新映射

 

如图所示,也不是特别麻烦
直接建立一个新表,然后遍历旧表一次映射到新表中
不过扩容时会有不少的消耗

 

补充:

  • 映射的时候取模
  • 应该是对表的size()取模,而不是capacity()
  • 因为对capacity取模的话,可能取到超出size的位置
  • operator[]会对超出size的检查(不过有的也不检查,根据不同版本的库里定)


5、开散列——链地址法(开链法),哈希桶

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地
址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链
接起来,各链表的头结点存储在哈希表中

从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

很显然,哈希桶中每个元素是个地址,所以哈希桶的底层原理就是一个指针数组,每个结点再挂着一个单链表,这样冲突就很容易解决了。

还是老问题,

  1. 如何应对“堆积”导致效率低下的情况
  2. 如何扩容
  • 如何应对“堆积”导致效率低下的情况?

这里我们还是选择适时扩容,那什么情况扩容呢?

桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可
能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希
表进行增容,那该条件怎么确认呢?开散列最好的情况是:每个哈希桶中刚好挂一个节点,
再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可
以给哈希表增容。
  • 如何扩容?
方案一:
方案二:

很显然我们更倾向于方案二:

方案一写法更简单,但是不断递归开销更大。

注:哈希桶结点插入,我们一般采用头插的方法,因为对于每一个链表,如果尾插,需要先找到尾,增加了时间消耗,头插的话消耗更低。

因为开散列是一个指针数组,涉及到空间的开辟,所以析构函数我们要自己完善:

	~HashTable()
		{
			for (auto& cur : _tables)
			{
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}

				cur = nullptr;
			}
		}

(四)详细代码


namespace OpenAddress
{
	enum State
	{
		EMPTY,
		EXIST,
		DELETE
	};

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;
	};

	template<class K, class V>
	class HashTable
	{
	public:
		HashData<K, V>* Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return nullptr;
			}

			size_t hashi = key % _tables.size();

			//线性探测
			size_t i = 1;
			size_t index = hashi;
			while (_tables[index]._state != EMPTY)
			{
				if (_tables[index]._state == EXIST
					&& _tables[index]._kv.first == key)
				{
					return &_tables[index];
				}

				index = hashi + i;
				index %= _tables.size();
				++i;

				if (index == hashi)
				{
					break;
				}
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_state = DELETE;
				--_n;
				return true;
			}
			else
			{
				return false;
			}
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
			{
				return false;
			}

			if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
			{
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				HashTable<K, V> newht;
				newht._tables.resize(newsize);

				for (auto& data : _tables)
				{
					if (data._state == EXIST)
					{
						newht.Insert(data._kv);
					}
				}
				_tables.swap(newht._tables);
			}

			size_t hashi = kv.first % _tables.size();
			//线性探测
			size_t i = 1;
			size_t index = hashi;
			while (_tables[index]._state == EXIST)
			{
				index = hashi + i;
				index %= _tables.size();
				++i;
			}
			_tables[index]._kv = kv;
			_tables[index]._state = EXIST;
			_n++;

			return true;

		}

	private:
		vector<HashData<K, V>> _tables;
		size_t _n = 0;//存储的数据个数
	};


	void TestHashTable2()
	{
		HashTable<int, int> ht;
		int arr[] = { 1,2,2,3,3,3,4,4,4,4,5,9,2,3 };
		for (auto& e : arr)
		{
			ht.Insert(make_pair(e, e));
		}


	}
	void TestHashTable1()
	{
		int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
		HashTable<int, int> ht;
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Insert(make_pair(15, 15));

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

		ht.Erase(13);

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


namespace HashBacket
{
	template<class K,class V>
	struct HashNode
	{
		HashNode<K, V>* _next;
		pair<K, V> _kv;

		HashNode(const pair<K, V>& kv)
			:_next(nullptr)
			, _kv(kv)
		{}
	};
	template<class K,class V>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		~HashTable()
		{
			for (auto& cur : _tables)
			{
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}

				cur = nullptr;
			}
		}


		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return nullptr;
			}
			size_t hashi = key % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}

			return nullptr;
		}


		bool Erase(const K& key)
		{
			size_t hashi = key % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if(prev==nullptr)
					{ 
						_tables[hashi] = cur->_next;
					}
				else
				{
					prev->_next = cur->_next;
				}
				delete cur;

				return true;
				}


				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}


		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
			{
				return false;
			}

			if (_n == _tables.size())
			{

				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*> newtables(newsize, nullptr);
				for (auto& cur : _tables)
				{
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = cur->_kv.first % newtables.size();
						//头插到新表
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;

						cur = next;

					}
				}
				_tables.swap(newtables);
			}
			size_t hashi = kv.first % _tables.size();
			// 头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;

			++_n;
			return true;

		}
	private:
		vector<Node*> _tables;
		size_t _n = 0;
	};


	void TestHashTable1()
	{
		int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
		HashTable<int, int> ht;
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Insert(make_pair(15, 15));
		ht.Insert(make_pair(25, 25));
		ht.Insert(make_pair(35, 35));
		ht.Insert(make_pair(45, 45));
	}

	void TestHashTable2()
	{
		int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
		HashTable<int, int> ht;
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Erase(12);
		ht.Erase(3);
		ht.Erase(33);
	}
}

感谢你的阅读!

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

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

相关文章

【面试题】前端开发中如何高效渲染大数据量?

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 【国庆头像】- 国庆爱国 程序员头像&#xff01;总有一款适合你&#xff01; 在日常工作中&#xff0c;较少的能遇到一次性往页面中插入大量数据的场景…

智能合约漏洞案例,DEI 漏洞复现

智能合约漏洞案例&#xff0c;DEI 漏洞复现 1. 漏洞简介 https://twitter.com/eugenioclrc/status/1654576296507088906 2. 相关地址或交易 https://explorer.phalcon.xyz/tx/arbitrum/0xb1141785b7b94eb37c39c37f0272744c6e79ca1517529fec3f4af59d4c3c37ef 攻击交易 3. …

演讲笔记|《一个ppt者的成长故事》

前言&#xff1a;本文为《说服力&#xff1a;工作型PPT该这样做》作者、秋叶PPT团队成员秦阳于2017年1月15日在北京望界无界空间的演讲内容要点总结。 1. 结构化思考&#xff08;思考能力&#xff09; 体系&#xff1a;挖多个坑&#xff0c;多个视角&#xff08;构建体系 – 获…

Java 锁(synchronized)升级过程

java中的锁是针对对象而言的&#xff0c;它锁住的是一个对象&#xff0c;并且具有可重入的性质。 java中的对象内存结构如图所示 普通对象内存结构: 数组对象内存结构&#xff1a; 其中关于锁状态的记录主要存储在_mark&#xff08;markword&#xff09;中&#xff0c;markwor…

Android窗口层级(Window Type)分析

前言 Android的窗口Window分为三种类型&#xff1a; 应用Window&#xff0c;比如Activity、Dialog&#xff1b;子Window&#xff0c;比如PopupWindow&#xff1b;系统Window&#xff0c;比如Toast、系统状态栏、导航栏等等。 应用Window的Z-Ordered最低&#xff0c;就是在系…

C++之结构体智能指针shared_ptr实例(一百九十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

每日一博 - CRUD system VS Event sourcing design

文章目录 概念Arch Overview小结 概念 CRUD 系统和事件溯源设计是两种不同的软件架构方法&#xff0c;用于处理数据和应用程序的状态。以下是它们的区别以及各自适用的场景&#xff1a; CRUD 系统&#xff1a; CRUD 代表 Create&#xff08;创建&#xff09;、Read&#xff08…

JWT 使用教程 授权 认证

JWT 1.什么是JWT JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally s…

Android Studio实机同WIFI调试

1.点击Pair using Wi-Fi 2.手机扫描跳出来的二维码 小米手机可搜索无线调试进行adb 调试

【AI】数学基础——最优化

从本质上讲&#xff0c;人工智能的目标就是最优化——在复杂环境中与多体交互中做出最优决策 几乎所有的人工智能问题都会归结为一个优化问题 在线性搜索中&#xff0c;确定寻找最小值时的搜索方向需要使用目标函数的一阶导数和二阶导数置信域的思想是先确定搜索步长&#xff0…

【源码】JavaWeb+Mysql招聘管理系统 课设

简介 用idea和eclipse都可以&#xff0c;数据库是mysql&#xff0c;这是一个Java和mysql做的web系统&#xff0c;用于期末课设作业 cout<<"如果需要的小伙伴可以http://www.codeying.top";可定做课设 线上招聘平台整合了各种就业指导资源&#xff0c;通过了…

Pytorch Advanced(三) Neural Style Transfer

神经风格迁移在之前的博客中已经用keras实现过了&#xff0c;比较复杂&#xff0c;keras版本。 这里用pytorch重新实现一次&#xff0c;原理图如下&#xff1a; from __future__ import division from torchvision import models from torchvision import transforms from PIL…

金蝶云星空和四化智造MES(WEB)单据接口对接

金蝶云星空和四化智造MES&#xff08;WEB&#xff09;单据接口对接 接入系统&#xff1a;四化智造MES&#xff08;WEB&#xff09; MES建立统一平台上通过物料防错防错、流程防错、生产统计、异常处理、信息采集和全流程追溯等精益生产和精细化管理&#xff0c;帮助企业合理安排…

Linux中安装MySQL_图解_2023新

1.卸载 为了避免不必要的错误发生,先将原有的文件包进行查询并卸载 // 查询 rpm -qa | grep mysql rpm -qa | grep mari// 卸载 rpm -e 文件名 --nodeps2.将安装包上传到指定文件夹中 这里采用的是Xftp 3.将安装包进行解压 tar -zxvf 文件名 -C 解压路径4.获取解压的全路…

春秋云镜 CVE-2015-9331

春秋云镜 CVE-2015-9331 wordpress插件 WordPress WP All Import plugin v3.2.3 任意文件上传 靶标介绍 wordpress插件 WordPress WP All Import plugin v3.2.3 存在任意文件上传&#xff0c;可以上传shell。 启动场景 漏洞利用 exp #/usr/local/bin/python3 # -*-coding:…

基础设施SIG月度动态:「龙蜥大讲堂」基础设施系列专题分享完美收官,容器镜像构建 2.0 版本上线

基础设施 SIG&#xff08;OpenAnolis Infra SIG&#xff09;目标&#xff1a;负责 OpenAnolis 社区基础设施工程平台的建设&#xff0c;包括官网、Bugzilla、Maillist、ABS、ANAS、CI 门禁以及社区 DevOps 相关的研发工程系统。 01 SIG 整体进展 1. 龙蜥大讲堂 - 基础设施系…

mac 本地运行 http-proxy-middleware ,请求超时

const http require(http)"/customer": {target: "http://10.10.111.192:8080/",// target: "http://user.jinfu.baohan.com/",changeOrigin: true, // 是否启用跨域// 解决mac 代理超时问题headers: {Connection: "keep-alive"},// …

机器学习(10)---特征选择

文章目录 一、概述二、Filter过滤法2.1 过滤法说明2.2 方差过滤2.3 方差过滤对模型影响 三、相关性过滤3.1 卡方过滤3.2 F检验3.3 互信息法3.4 过滤法总结 四、Embedded嵌入法4.1 嵌入法说明4.2 以随机森林为例的嵌入法 五、Wrapper包装法5.1 包装法说明5.2 以随机森林为例的包…

事件处理机制

前面介绍了如何放置各种组件&#xff0c;从而得到了丰富多彩的图形界面&#xff0c;但这些界面还不能响应用户的任何操作。比如单击前面所有窗口右上角的“X”按钮&#xff0c;但窗口依然不会关闭。因为在 AWT 编程中 &#xff0c;所有用户的操作&#xff0c;都必须都需要经过一…

025-从零搭建微服务-文件服务(一)

写在最前 如果这个项目让你有所收获&#xff0c;记得 Star 关注哦&#xff0c;这对我是非常不错的鼓励与支持。 源码地址&#xff08;后端&#xff09;&#xff1a;https://gitee.com/csps/mingyue 源码地址&#xff08;前端&#xff09;&#xff1a;https://gitee.com/csps…