搞懂哈希散列

news2024/11/16 1:15:58

文章目录

    • 1. 哈希概念
    • 2. 如何解决哈希冲突?
    • 3. 哈希函数概念
    • 4. 闭散列(目前淘汰的方法,了解)
      • **4.1.线性探测**
      • **4.2. 二次探测**
      • 4.3 闭散列查找与删除
    • 5. 开散列
    • 6. 开散列与闭散列比较

1. 哈希概念

搜索树需要多次的关键码比较来搜索到结果,哈希就是能让值与存储位置建立映射关系,可以不经过任何比较,一次直接从表中得到要搜索的元素。

如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立
一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中

插入元素

  • 根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。

搜索元素

  • 对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置
    取元素比较,若关键码相等,则搜索成功。

构造哈希主要有两个元素

  • 哈希表 - - 作为存储结构
  • 哈希函数 - - 作为转换函数

例如:数据集合{1,7,6,4,5,9};
哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。

在这里插入图片描述

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快

问题:按照上述哈希方式,向集合中插入元素44,会出现什么问题?

如下图当我们插入17时,hash(17)与hash(7) 相同 ,导致哈希冲突了。

在这里插入图片描述

2. 如何解决哈希冲突?

哈希冲突概念:

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

引起哈希冲突的原因:

  • 哈希函数设计不够合理。
  • 哈希函数如果能设计合理,能够较大层度减少冲突次数。
  • 解决冲突需要时间消耗。

哈希冲突吃的解决方法:

  • 闭散列。
  • 开散列。

3. 哈希函数概念

哈希函数设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值
    域必须在0到m-1之间。
  • 哈希函数计算出来的地址能均匀分布在整个空间中。
  • 哈希函数应该比较简单。

常见的哈希函数:

1.直接定址法–(常用)

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

例如:表的位置等于数值,A=1,B=0。

2.除留余数法–(我们使用该方法)

设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,

按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。

简单且可以很好的减少冲突。STL库就是采用此方法。

其他哈希函数:
3.平方取中法–(了解)
4.折叠法–(了解)
5.随机数法–(了解)
6.数学分析法–(了解)

4. 闭散列(目前淘汰的方法,了解)

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有
空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。

那如何寻找下一个空位置呢?

寻找下一个空位置的方式:

  • 线性探测法
  • 二次探测法

4.1.线性探测

比如2.1中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4,因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。

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

  • 插入
    • 过哈希函数获取待插入元素在哈希表中的位置
    • 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素

图2.1

在这里插入图片描述

  • 删除
    • 用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索
    • 比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素
// 哈希表每个空间给个标记
// EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
enum State{EMPTY, EXIST, DELETE};

线性探测代码实现:

4.1.1 初始化哈希
步骤:

我们按照2倍的速度增长空间,因为采用除留余数法,因此我们将空间的大小设置为质数。

我们初始一组28位的质数数组,元素两两之间约等于两倍。

注意:为了减少增容,我们可以预先开辟一个大空间,前提是知道实际数据数量。

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

namespace H
{
	// 哈希表每个空间给个标记
	// EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
	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 Hash=HashFunc<K>>
	class HashTable
	{
	public:
		HashTable(size_t capacity = 0)
			: _tables(__stl_next_prime(capacity)), _size(0)
		{
			for (size_t i = 0; i < capacity; ++i)
				_tables[i]._state = EMPTY;
		}
	private:
		// 获取下一个质数
		inline size_t __stl_next_prime(size_t n)
		{
			static const size_t __stl_num_primes = 28;
			static const size_t __stl_prime_list[__stl_num_primes] =
			{
				53, 97, 193, 389, 769,
				1543, 3079, 6151, 12289, 24593,
				49157, 98317, 196613, 393241, 786433,
				1572869, 3145739, 6291469, 12582917, 25165843,
				50331653, 100663319, 201326611, 402653189, 805306457,
				1610612741, 3221225473, 4294967291
			};

			for (size_t i = 0; i < __stl_num_primes; ++i)
			{
				if (__stl_prime_list[i] > n)
				{
					return __stl_prime_list[i];
				}
			}
			return -1;
		}
	private:
		vector<HashData<K,V>> _tables;
		size_t _size=0;
		size_t loadFactor = 7;// 负载因子
	};
}

4.1.2 插入数据

插入步骤

  • 检测扩容。
  • 过哈希函数获取待插入元素在哈希表中的位置。
  • 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。
		bool Insert(const pair<K, V>& kv)
		{
			// 检测哈希表底层空间是否充足
			_CheckCapacity();
			Hash HashFunc;
			size_t hashAddr = HashFunc(kv.first);
			size_t startAddr = hashAddr;
			while (_tables[hashAddr]._state != EMPTY)
			{
				//数据重复了
				if (_tables[hashAddr]._state == EXIST && _tables[hashAddr]._kv.first
					== kv.first)
					return false;

				hashAddr++;
				if (hashAddr == _tables.size())
					hashAddr = 0;
				/*
				// 转一圈也没有找到,注意:动态哈希表,该种情况可以不用考虑,哈希表中元
				素个数到达一定的数量,哈希冲突概率会增大,需要扩容来降低哈希冲突,因此哈希表中元素是
				不会存满的
				*/
				//bug 处理极端情况下,线性探测的过程中,探测的节点都是 state==DELETE,会导致死循环
				if (hashAddr == startAddr)
					return false;
			}
			// 插入元素
			_tables[hashAddr]._state = EXIST;
			_tables[hashAddr]._kv = kv;
			_size++;
			return true;
		}

思考:哈希表什么情况下进行扩容?如何扩容?

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

a是散列表装满程度的标志因子。由于表长是定值,a与“填入表中的元素个数”成正比,所以,a越大,表明填入表中的元素占比越多,产生冲突的可能性就越大:反之,a越小,标明填入表中的元素占比越少,产生冲突的可能性就越小

实际上,散列表的平均查找长度是载荷因子a的函数,只是不同处理冲突的方法有不同的函数。

对于开放定址法,荷载因子是特别重要因素,应严格限制在0.7-0.8以下。超过0.8,查表时的CPU缓存不命中(cachemissing)按照指数曲线上升。因此,一些采用开放定址法的hash库,如Java的系统库限制了荷载因子为0.75,超过此值将resize散列表(扩容)。

		void _CheckCapacity()
		{
			if (_tables.size() == 0 || _size * 10 / _tables.size() >= loadFactor)
			{
				//扩容
				size_t newSize = __stl_next_prime(_tables.size());
				HashTable<K, V, Hash> newHT;
				newHT._tables.resize(newSize);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					if (_tables[i]._state == EXIST)
					{
						newHT.Insert(_tables[i]._kv);
					}
				}
			
				_tables.swap(newHT._tables);
			}
		}

哈希函数

  • 我们模拟STL实现一个模板仿函数。
  • STL库里我们也可以自己写一个仿函数传递给hash,让hash使用我们特定的仿函数。
  • STL库默认将我们的key强制转换位size_t
  • 因此std::string经常使用,string不能够强制转化位size_t,因此我们特化一份string类型的仿函数。
  • 如何写string类型的hash仿函数?
  • 将一个string类型转换位hash值其实有多种方法的,该链接详细介绍字符串哈希算法
  • 我们使用第一种方法,简单高效。
template<class K>
struct HashFunc
{
	size_t operator()(const K& k)
	{
		return size_t(k);
	}
};

template<>
struct HashFunc<std::string>
{
	size_t operator()(const std::string& str)
	{
		size_t ret = 0;
		for (auto e : str)
		{
			ret *= 131;
			ret += e;
		}
		return ret;
	}
};

4.2. 二次探测

线性探测优点:实现非常简单,
线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同
关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降
。如何缓解呢?

线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位
置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法
为: H i H_i Hi = ( H 0 H_0 H0 + i 2 i^2 i2 )% m, 或者: H i H_i Hi = ( H 0 H_0 H0 - i 2 i^2 i2 )% m。其中:i =1,2,3…, H 0 H_0 H0是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表
的大小

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

		bool Insert(const pair<K, V>& kv)
		{

			// 检测哈希表底层空间是否充足
			_CheckCapacity();
			Hash HashFunc;
			size_t hashAddr = HashFunc(kv.first)%_tables.size();
			size_t startAddr = hashAddr;
			size_t i = 0;
			while (_tables[hashAddr]._state == EXIST)
			{
				//数据重复了
				if (_tables[hashAddr]._kv.first== kv.first)
					return false;
				i++;
				hashAddr = (hashAddr + i*i) % _tables.size();

			}
			// 插入元素
			_tables[hashAddr]._state = EXIST;
			_tables[hashAddr]._kv = kv;
			_size++;
			return true;
		}

4.3 闭散列查找与删除

		HashData<K, V>* Find(const K& key)
		{
			size_t hashAddr = HashFunc(key) % _tables.size();
			size_t startAddr = hashAddr;
			size_t i = 0;
			while (_tables[hashAddr]._state != EMPTY)
			{
				if (_tables[hashAddr]._state == EXIST && _tables[hashAddr]._kv.first
					== key)
					return &_tables[hashAddr];
				//hashAddr++;
				i++;
				hashAddr = (hashAddr + i * i) % _tables.size();
				if (hashAddr == startAddr)
					return nullptr;
			}
			return nullptr;
		}

		bool Erase(const K & key)
		{
			HashData<K, V>* indexP = Find(key);
			if (nullptr != indexP)
			{
				indexP->_state = DELETE;
				_size++;
				return true;
			}
			return false;
		}

5. 开散列

5.1 开散列概念

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

注意:如果桶中数据量大,有些库实现会采用把桶换成红黑树,一个线性,一个非线性,数据量大时搜索起来肯定要快很多。我们这里采用线性的方式实现。

如下图插入 1,4,5,6,7,9,44

在这里插入图片描述
在这里插入图片描述

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

5.2 开散列的实现

  • 开散列与闭散列的区别
  • 存储结构不一样
  • 都需要考虑增容问题

5.3 初始化实现

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

		HashNode(const pair<K, V>& kv)
			:_kv(kv)
			, _next(nullptr)
		{}
	};

	// 休息:20
	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:

		HashTable(size_t capacity = 0)
			: _tables(__stl_next_prime(capacity),nullptr), _size(0)
		{
		}

		inline size_t __stl_next_prime(size_t n)
		{
			static const size_t __stl_num_primes = 28;
			static const size_t __stl_prime_list[__stl_num_primes] =
			{
				53, 97, 193, 389, 769,
				1543, 3079, 6151, 12289, 24593,
				49157, 98317, 196613, 393241, 786433,
				1572869, 3145739, 6291469, 12582917, 25165843,
				50331653, 100663319, 201326611, 402653189, 805306457,
				1610612741, 3221225473, 4294967291
			};

			for (size_t i = 0; i < __stl_num_primes; ++i)
			{
				if (__stl_prime_list[i] > n)
				{
					return __stl_prime_list[i];
				}
			}

			return -1;
		}


	private:
		vector<Node*> _tables;
		size_t _size = 0; // 存储有效数据个数
	};


}

5.3 开散列插入

步骤:

  • 检查死否需要增容。
  • 插入元素求出hash值,向对应单链表进行头插。

开散列增容:

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

增容步骤:

  • 开辟一个新的哈希表。
  • 将旧的哈希表的所有节点进行重新计算在新的哈希表里的哈希值。
  • 节点由堆开辟,有对应的地址,我们只需要将节点移动到新哈希表里即可,不需要节点拷贝。
		bool Insert(const pair<K, V>& kv)
		{
			// 去重
			if (Find(kv.first))
			{
				return false;
			}

			Hash hash;
			// 负载因子到1就扩容
			if (_size == _tables.size())
			{
				vector<Node*> newTables;
				newTables.resize(__stl_next_prime(_tables.size()), nullptr);
				// 旧表中节点移动映射新表
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hash(cur->_kv.first) % newTables.size();
						//链表头插入
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}

					_tables[i] = nullptr;
				}

				_tables.swap(newTables);
			}

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

			return true;
		}

5.4 开散列查找

查找步骤:

  • 求出查找元素的hash值。
  • 向对应链表遍历查找。
		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return nullptr;
			}

			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}

				cur = cur->_next;
			}

			return nullptr;
		}

5.5 开散列删除

删除步骤:

  • 求出删除元素的hash值。
  • 向对应链表遍历查找元素,然后删除。
  • 注意:考虑头部元素删除 还是 中间元素删除。原因是删除两者删除的逻辑不一样。
		bool Erase(const K& key)
		{
			if (_tables.size() == 0)
			{
				return nullptr;
			}

			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					// 1、头删
					// 2、中间删
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}

					delete cur;
					--_size;

					return true;
				}

				prev = cur;
				cur = cur->_next;
			}

			return false;
		}

5.6 开散列Size()

		// 获取有效元素大小
		size_t Size()
		{
			return _size;
		}

		// 表的总长度
		size_t TablesSize()
		{
			return _tables.size();
		}

		// 桶的个数
		size_t BucketSize()
		{
			size_t num = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					++num;
				}
			}

			return num;
		}
		// 有效桶的个数
		size_t MaxBucketLenth()
		{
			size_t maxLen = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				size_t len = 0;
				Node* cur = _tables[i];
				while (cur)
				{
					++len;
					cur = cur->_next;
				}

				//if (len > 0)
					//printf("[%d]号桶长度:%d\n", i, len);

				if (len > maxLen)
				{
					maxLen = len;
				}
			}

			return maxLen;
		}

6. 开散列与闭散列比较

闭散列:

  • 为了保证搜索效率,负载因子需要控制在 <=0.7~0.8 之间。
  • 负载因子越大搜索效率越低,空间利用率越高。
  • 负载因子越小搜索效率越高,空间利用率越低
  • 优点:
  • 实现非常简单。
  • 缺点:
  • 线性探测,不同的关键码占用别人的空位置,导致数据粘连,需要多次比较,搜索效率降低。
  • 二次探测,让关键码分散,解决大部门数据粘连。
  • 因此,开散列最大的缺陷是哈希表里存在大量空闲空间

开散列:

  • 闭散列和开散列最坏情况时间复杂度为 O(N);
  • 开散列最好的情况是每个桶都挂一个节点,搜索时间O(1)。
  • 闭散列最后的情况是每个关键码能对应其存放位置,搜索时间O(1)。
  • 增容比较:
  • 两者最好的情况是预先开辟空间,无需增容。
  • 闭散列和开散列增容时时间都是O(N).
  • 但是两者的增容过程是不一样的。
  • 闭散列采用需要拷贝数据。开散列采用移到节点,不需要拷贝。
  • 因此,增容时开散列更优秀。
  • 空间比较:
  • 开散列/链式法 有指针消耗,较少的空闲空间浪费,但是相比闭散列,表项比指针占用更多的空间。
  • 因此,开散列空间利用率比闭散列好。

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

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

相关文章

AutoGPT、AgentGPT、BabyAGI、HuggingGPT、CAMEL:各种基于GPT-4自治系统总结

ChatGPT和LLM技术的出现使得这些最先进的语言模型席卷了世界&#xff0c;不仅是AI的开发人员&#xff0c;爱好者和一些组织也在研究探索集成和构建这些模型的创新方法。各种平台如雨后春笋般涌现&#xff0c;集成并促进新应用程序的开发。 AutoGPT的火爆让我们看到越来越多的自…

Linux 3.14的设备树-实战开发代码

OF提供的函数主要集中在drivers/of/目录下&#xff0c;有address.c,base.c,device.c,fdt.c,irq.c,platform.c等等 1&#xff0c;根据deice_node结构的full_name参数&#xff0c;在全局链表of_allnodes中&#xff0c;查找合适的device_node struct device_node *of_find_node_by…

概率/期望dp

思维方式 概率/期望dp都是分析从当前状态能否去到其他情况&#xff0c;然后进行期望/概率公式的运算&#xff0c;最后消元推导出一般式。 ​​​​​​Problem - 4089 (hdu.edu.cn) 思路&#xff1a;概率dp&#xff0c;正推 分类讨论&#xff0c;设dp[i][j]表示i人的队伍在…

力扣sql中等篇练习(八)

力扣sql中等篇练习(八) 1 确认率 1.1 题目内容 1.1.1 基本题目信息1 1.1.2 基本题目信息2 1.1.3 示例输入输出 a 示例输入 b 示例输出 1.2 示例sql语句 # 分别统计没确认的用户和已经确认了的用户 SELECT s.user_id,ROUND(SUM(IF(c.actionconfirmed,1,0))/count(*),2) co…

GPT模型支持下的Python-GEE遥感云大数据分析、管理与可视化技术及多领域案例应用

如今&#xff0c;Earth Engine凭借其强大的功能正受到越来越多国内外科技工作者的关注&#xff0c;应用范围也在不断扩大。学习致力于帮助科研工作者掌握Earth Engine的实际应用能力&#xff0c;以Python编程语言为基础&#xff0c;结合实例讲解平台搭建、影像数据分析、经典应…

JAVA开发(H5商城嵌套到第三方H5商城进行异业合作方案)

需求背景&#xff1a; 很多电商网站或者说是购物网站都是H5商城的方式&#xff0c;每个公司都有自己的H5商城。当公司和公司之间进行异业合作&#xff0c;商城互相导流甚至商城聚合时就需要H5商城和H5商城的嵌套和对接。假如存在A商城和B商城&#xff0c;现在需要B商城入驻到A商…

CrackMapExec 域渗透工具使用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、CrackMapExec 是什么&#xff1f;二、简单使用1、获取帮助信息2、smb连接执行命令3、使用winrm执行命令&#xff08;躲避杀软&#xff09;4、smb 协议常用枚…

Binder 驱动结构体列表

下面列举 Binder 驱动相关的一些重要结构体 6~9 用于数据传输相关&#xff0c;其中 binder_write_read&#xff0c;binder_transaction_data进程空间和内核空间是通用的。 BWR 核心数据图表 binder_write_read 是整个 Binder IPC 过程&#xff0c;最为核心的数据结构之一 3.…

品牌推广:如何让品牌在市场中保持活力并吸引更多的年轻人?

在如今这个竞争激烈的市场环境中&#xff0c;让品牌年轻化已经成为了许多企业追求的目标。随着社会的不断发展和进步&#xff0c;消费者的需求和心理也在不断变化。因此&#xff0c;如果一个品牌想要在市场中保持活力并吸引更多的年轻人&#xff0c;就必须思考如何让品牌年轻化…

【社区图书馆】读书推荐:《PyTorch高级机器学习实战》

读书推荐&#xff1a;《PyTorch高级机器学习实战》 作者&#xff1a;i阿极 作者简介&#xff1a;Python领域新星作者、多项比赛获奖者&#xff1a;博主个人首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可以点赞&#x1f44d;收藏…

【GIT】git push后长时间没反应

方向一 查看是否添加ssh 打开git bash cd ~/.ssh看是否成功&#xff0c;能成功说明之前生成过&#xff0c;看文件夹下是否有id_rsa.pub和id_rsa文件&#xff0c;有的话跳过生成步骤3 输入 ssh-keygen -t rsa -C ‘your_emailexample.com’(注&#xff1a;your_emailexample.c…

人人都能GPT!微软开源DeepSpeed Chat帮用户训练模型

简介 4月12日&#xff0c;微软宣布开源了 DeepSpeed Chat&#xff0c;帮助用户加速训练类似于 ChatGPT 的模型。 DeepSpeed Chat 能够简化 ChatGPT 类型模型的训练过程、强化推理体验。其中的 DeepSpeed-RLHF 系统能够在推理和训练两种模式之间进行切换&#xff0c;使复杂的 …

【经典面试题目:最长递增子序列变形题目 | 动态规划 + 二分】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

StackExchange.Redis.RedisServerException 针对持有错误类型值的密钥的WRONGTYPE操作

Redis 的异常消息&#xff1a;StackExchange.Redis.RedisServerException:“WRONGTYPE Operation against a key holding the wrong kind of value” 最近一个较早的项目新增一些功能&#xff0c;业务中服务端需要在token中自动获取用户相关信息的操作&#xff0c;项目中已经封…

web模块_2(SQL注入,上传文件的权限获取)

1题目描述&#xff1a;你知道index.php的备份文件名吗&#xff1f; index.php的备份文件名为index.php.bak2题目描述 php中&#xff0c;双等号是弱类型比较。判断是否相等&#xff0c;不判断格式。 判断格式是否相等。“0a”0为True&#xff0c;“1235a"不算数字&#xf…

从原理聊JVM(一):染色标记和垃圾回收算法

作者&#xff1a;京东科技 康志兴 1 JVM运行时内存划分 1.1 运行时数据区域 • 方法区 属于共享内存区域&#xff0c;存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池&#xff0c;属于方法区的一部分&#xff0c;用于存放编译期生…

spring的BeanFactory和applicationContext有什么区别?

一、加载bean时机不同 ApplicationContext是一次性立刻加载,比较消耗资源但是后续读取非常快,会将spring中所有的bean进行初始化,全部实例化到spring中!!属于饿汉模式加载。 Beanfactory是一个用来管理bean对象的工厂&#xff0c;加载bean的时候不会立刻一次性加载,使用的是惰性…

【C语言】分支语句和循环语句(下)

【C语言】分支语句和循环语句&#xff08;下&#xff09; 1.for循环1.2 语法1.3 break和continue在for循环中1.4 for语句的循环控制变量1.5 一些for循环的变种1.6一道笔试题 2. do……while&#xff08;&#xff09;循环2.1 do语句的语法2.2 do语句的特点2.3 do while循环中的b…

Python多线程慎用shutil.make_archive打包

Python多线程慎用shutil.make_archive打包 记一下在工作中遇到一个错误&#xff0c;先说一下结论shutil.make_archive是线程不安全的&#xff0c;要慎重使用&#xff01;&#xff01;&#xff01; 参考&#xff1a;https://stackoverflow.com/questions/41625702/is-shutil-mak…

【C++】从C语言入门C++的基础知识

C基础知识 前言1. C关键字2. 命名空间namespace命名空间的创建命名空间的使用命名空间的注意事项 3. C输入&输出4. 缺省参数概念分类全缺省参数半缺省参数 5. 函数重载概念实现C为什么能进行函数重载C和C的相互调用&#xff08;可以不用看&#xff09; 6. 引用概念注意事项…