哈希:哈希函数 | 哈希概念 | 哈希冲突 | 闭散列 | 开散列

news2024/9/23 7:33:06

在这里插入图片描述

🌈个人主页: 南桥几晴秋
🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git
🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈
本科在读菜鸡一枚,指出问题及时改正

文章目录

  • unordered系列关联式容器
    • unordered_map
      • unordered_map介绍
        • 接口说明
          • unordered_map 的构造
          • unordered_map的容量
          • unordered_map的迭代器
          • unordered_map的元素访问
          • unordered_map的查询
          • unordered_map的修改操作
          • unordered_map的桶操作
    • unordered_set文档介绍
  • 底层结构
    • 哈希概念
    • 哈希冲突
      • 解决哈希冲
        • 闭散列
          • 线性探测
            • 模拟实现
          • 二次探测
        • 开散列
          • 模拟实现

unordered系列关联式容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2N log2N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。

unordered_map

unordered_map介绍

介绍文档

  1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value
  2. unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的valueunordered_map将相同哈希值的键值对放在相同的桶中。
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value
  6. 它的迭代器至少是前向迭代器。
接口说明
unordered_map 的构造
函数声明功能介绍
unordered_map构造不同格式的unordered_map对象

在这里插入图片描述

unordered_map的容量
函数声明功能介绍
bool empty() const检测unordered_map是否为空
size_t size() const获取unordered_map的有效元素个数
unordered_map的迭代器

是一个单向迭代器

函数声明功能介绍
begin返回unordered_map第一个元素的迭代器
end返回unordered_map最后一个元素下一个位置的迭代器
cbegin返回unordered_map第一个元素的const迭代器
cend返回unordered_map最后一个元素下一个位置的const迭代器
unordered_map的元素访问
函数声明功能介绍
operator[]返回与key对应的value,没有一个默认值
unordered_map的查询
函数声明功能介绍
iterator find(const K& key)返回key在哈希桶中的位置
size_t count(const K& key)返回哈希桶中关键码为key的键值对的个数

注意:unordered_map中key是不能重复的,因此count函数的返回值最大为1

unordered_map的修改操作
函数声明功能介绍
insert向容器中插入键值对
erase删除容器中的键值对
void clear()清空容器中有效元素个数
void swap(unordered_map&)交换两个容器中的元素
unordered_map的桶操作
函数声明功能介绍
size_t bucket_count()const返回哈希桶中桶的总个数
size_t bucket_size(size_t n)const返回n号桶中有效元素的总个数
size_t bucket(const K& key))返回元素key所在的桶号

unordered_set文档介绍

setunordered_set使用方法类似
文档介绍

底层结构

unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构

哈希概念

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

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

哈希也叫做散列,是一种映射,把值和值进行一对一或者一对多关联。

哈希表:使用哈希思想实现的数据结构。一般都是将值和存储位置建立映射关系。

当向该结构中:

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

存在的问题:

  • 值很分散时,效率不高
  • 有些值不好映射,比如:string、结构体对象

在这里插入图片描述

除留余数法解决了数据分散问题,但是会导致哈希冲突,不同的值可能会映射到相同位置。例如下面10001和11就映射到一位置。
在这里插入图片描述

哈希冲突

对于两个数据元素的关键字 k i k_i ki k j k_j kj(i != j),有 k i k_i ki != k j k_j kj,但有:Hash( k i k_i ki) ==Hash( k j k_j kj),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。

解决哈希冲

闭散列

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。也就是说,自己位置被占了,去抢别的位置。冲突次数越高,效率越低。

寻找下一个空位置:

线性探测

从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
在这里插入图片描述
插入:
通过哈希函数获取待插入元素在哈希表中的位置
如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。
删除:
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。
扩容:
散列表的载荷因子定义为:α=填入表中的元素个数 / 散列表的长度
负载因子越高,冲突率越高,效率越低;负载因子越小,冲突效率越低,效率就越高,空间利用率就越低。

模拟实现

在这里插入图片描述

扩容机制:
首先扩容,然后原有的值不能直接拷贝,需要重新映射。
在这里插入图片描述

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

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:
	HashTable()
	{
		_tables.resize(10);
	}

	bool Insert(const pair<K, V>& kv)
	{
		if (Find(kv.first)) return false;
		//扩容
		if (_n * 10 / _tables.size() >= 7)
		{
			//size_t newsize = _tables.size() * 2;
			//vector<HashData<K, V>> newtables(newsize);
			//for (size_t i = 0; i < _tables.size(); i++)
			//{}

			size_t newsize = _tables.size() * 2;
			HashTable<K, V> 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);
		}


		size_t hashi = kv.first % _tables.size();

		//线性探测
		while (_tables[hashi]._state == EXIST)  //如果该位置存在继续往后探测
		{
			++hashi;
			hashi %= _tables.size();
		}

		_tables[hashi]._kv = kv;
		_tables[hashi]._state = EXIST;
		++_n;

		return true;
	}

	HashData<K, V>* Find(const K& key)
	{
		size_t hashi = key % _tables.size();
		//线性探测
		while (_tables[hashi]._state != EMPTY)  //如果该位置存在继续往后探测
		{
			if (_tables[hashi]._state == EXIST && _tables[hashi]._kv.first == key)
			{
				return &_tables[hashi];
			}
			++hashi;
			hashi %= _tables.size();
		}
		return nullptr;
	}

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

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

在这里插入图片描述

上述类型是整型,可以直接进行取模运算,但是对于其他类型,例如:string,自定义类型该如何处理?
对于其他类型,可以先转换成整型,然后进行映射。注意这里不是类型转换!key不支持强转整型取模,需要自己提供转换成整型的仿函数。
在书写代码时,需要增加一个仿函数,用于转换类型:

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

enum State
{
	EMPTY,
	EXIST,
	DELETE
};

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

template<class K>
struct HashFunc  // 用于直接转换成整型
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

struct StringHashFunc
{
	size_t operator()(const string& key)
	{
		return key[0];
	}
};

template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
	HashTable()
	{
		_tables.resize(10);
	}

	bool Insert(const pair<K, V>& kv)
	{
		if (Find(kv.first)) return false;
		//扩容
		if (_n * 10 / _tables.size() >= 7)
		{
			//size_t newsize = _tables.size() * 2;
			//vector<HashData<K, V>> newtables(newsize);
			//for (size_t i = 0; i < _tables.size(); i++)
			//{}

			size_t newsize = _tables.size() * 2;
			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);
		}

		Hash hs;
		size_t hashi = hs(kv.first) % _tables.size();

		//线性探测
		while (_tables[hashi]._state == EXIST)  //如果该位置存在继续往后探测
		{
			++hashi;
			hashi %= _tables.size();
		}

		_tables[hashi]._kv = kv;
		_tables[hashi]._state = EXIST;
		++_n;

		return true;
	}

	HashData<K, V>* Find(const K& key)
	{
		Hash hs;
		size_t hashi = hs(key) % _tables.size();
		//线性探测
		while (_tables[hashi]._state != EMPTY)  //如果该位置存在继续往后探测
		{
			if (_tables[hashi]._state == EXIST && _tables[hashi]._kv.first == key)
			{
				return &_tables[hashi];
			}
			++hashi;
			hashi %= _tables.size();
		}
		return nullptr;
	}

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

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

在这里插入图片描述

对于处理string类型时,会出现冲突,因为英文单词中首字母相同的单词有很多,我们可以对一个单词中所有字母的ASII码值进行相加:

struct StringHashFunc
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (auto ch : key)
		{
			hash += ch;
		}
		return hash;
	}
};

但是这种方法仍然有缺陷 ,例如:abcdbcadaadd这几个的ASII码仍然还是有冲突。

字符串哈希算法

二次探测

线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为: 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是表的大小。

开散列

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

在这里插入图片描述
从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

模拟实现

插入时,需要实现头插:先将待插入的元素插入进去,然后使它变成头结点。
在这里插入图片描述

扩容:

  • 方案一:将旧表中的数据通过映射的方式拷贝到新表中,然后再释放掉旧表中的内容,就表中虽然vector数组可以通过析构的方式释放掉,但是对应的接点删除效率不高。
if (_n == _tables.size())
{
	size_t newsize = _tables.size() * 2;
	HashTable<K, V, Hash> newHT;
	newHT._tables.resize(newsize);
	for (size_t i = 0; i < _tables.size(); i++)
	{
		Node* cur = _tables[i];
		while (cur)
		{
			newHT.Insert(cur->_kv);
			cur = cur->_next;
		}
	}

	_tables.swap(newHT._tables);
}
  • 方案二:将节点从旧表中拿出来,通过映射的方式放在新表中
// 扩容:负载因子为1(平均每个桶下面一个)
			if (_n == _tables.size())
			{
				//方案二:
				vector<Node*> newTables(_tables.size() * 2, nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						//头插到新表的位置
						size_t hashi = cur->_kv.first % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}

删除:
再删除的时候需要考虑删除的数据是头结点还是中间的节点,如果是头结点直接删除即可,中间节点之间让前一个节点指向被删除节点的下一个节点。

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

// 特化
template<>
struct HashFunc<string>
{
	// abcd
	// bcad
	// aadd
	// BKDR
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (auto ch : key)
		{
			hash *= 131;
			hash += ch;
		}

		return hash;
	}
};

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;
}

完整代码:

namespace gwj_hash_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)
		{}
	};

	template<class K,class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;

	public:
		HashTable()
		{
			_tables.resize(10, nullptr);
			_n = 0;
		}

		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;

					cur = next;
				}

				_tables[i] = nullptr;
			}
		}

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

			// 扩容:负载因子为1(平均每个桶下面一个)
			if (_n == _tables.size())
			{
				//方案一:
				//	size_t newsize = _tables.size() * 2;
				//	HashTable<K, V, Hash> newHT;
				//	newHT._tables.resize(newsize);
				//	for (size_t i = 0; i < _tables.size(); i++)
				//	{
				//		Node* cur = _tables[i];
				//		while (cur)
				//		{
				//			newHT.Insert(cur->_kv);
				//			cur = cur->_next;
				//		}
				//	}

				//	_tables.swap(newHT._tables);

				//方案二:
				vector<Node*> newTables(_tables.size() * 2, nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						//头插到新表的位置
						size_t hashi = hs(cur->_kv.first) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}

			size_t hashi = hs(kv.first) % _tables.size();
			Node* newnode = new Node(kv);
			// 头插
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			return true;
		}

		Node* Find(const K& key)
		{
			Hash hs;
			size_t hashi = hs(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)
		{
			Hash hs;
			size_t hashi = hs(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;
			}
		}

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

	void TestHT1()
	{
		int a[] = { 10001,11,55,24,19,12,31,4,34,44 };
		HashTable<int, int> ht;
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

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

		ht.Erase(31);
		ht.Erase(11);
	}

	void TestHT2()
	{
		HashTable<string, int> ht;
		ht.Insert(make_pair("sort", 1));
		ht.Insert(make_pair("left", 1));
		ht.Insert(make_pair("insert", 1));
	}


}

在这里插入图片描述

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

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

相关文章

在线式环氧乙烷检测仪:现代工业生产中的环氧乙烷安全监测

在现代工业生产的广阔领域中&#xff0c;环氧乙烷&#xff08;C2H4O&#xff09;作为一种不可或缺的化工原料&#xff0c;其应用范围广泛且深远&#xff0c;涵盖了涂料、树脂、塑料、印染、纺织品等多个关键行业。然而&#xff0c;环氧乙烷的化学性质极为活泼&#xff0c;不仅易…

小阿轩yx-云原生存储Rook部署Ceph

小阿轩yx-云原生存储Rook部署Ceph 前言 Rook 一款云原生存储编排服务工具由云原生计算基金会&#xff08;CNCF&#xff09;孵化&#xff0c;且于2020年10月正式进入毕业阶段。并不直接提供数据存储方案&#xff0c;而是集成了各种存储解决方案&#xff0c;并通过一种自管理、…

SprinBoot+Vue图书馆预约与占座微信小程序的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue3.6 uniapp代码 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平…

“Flash闪存”介绍 及 “SD NAND Flash”产品的测试含例程

文章目录 前言一、“FLASH闪存”是什么&#xff1f;1. 简介2. 特点3. 未来发展 二、SD NAND Flash1. 概述2. 特点3. 引脚分配4. 数据传输模式5. SD NAND寄存器6. 通电图7. 参考设计 三、STM32测试例程1. 初始化2. 但数据块测试3. 多数据块测试4. 状态缓冲 前言 本篇除了对flas…

Java JVM 垃圾回收算法详解

Java 虚拟机&#xff08;JVM&#xff09;是运行 Java 应用程序的核心&#xff0c;它的垃圾回收&#xff08;Garbage Collection, GC&#xff09;机制是 JVM 中非常重要的一个部分。垃圾回收的主要任务是自动管理内存&#xff0c;回收那些不再被使用的对象&#xff0c;从而释放内…

VOCs将纳入征税,LDAR系统的排放量计算准确度将要求更加规范,VOCs排放量计算准确度会更加重视,直接影响到税费

笔者见过很多不同公司的LDAR管理系统以及和很多检测公司技术人员沟通&#xff0c;部分技术人员在排放量计算方面尽然不知道中间点等关键要素&#xff0c;有的系统计算排放量不考虑中间点算法、有的计算一年四轮次检测 每轮都是独立计算和上轮检测数据没有任何关系&#xff08;这…

YOLOv8改进实战 | 引入多维协作注意模块MCA,实现暴力涨点

YOLOv8专栏导航&#xff1a;点击此处跳转 前言 YOLOv8 是由 YOLOv5 的发布者 Ultralytics 发布的最新版本的 YOLO。它可用于对象检测、分割、分类任务以及大型数据集的学习&#xff0c;并且可以在包括 CPU 和 GPU 在内的各种硬件上执行。 YOLOv8是一种尖端的、最先进的 (SOTA)…

Windows安装anaconda注意事项及jupyter notebook更换目录

anaconda的介绍就不罗嗦了&#xff0c;既然准备安装了&#xff0c;说明你已经有所了解了。直入主题&#xff0c;Anaconda官网下载&#xff0c;实在太慢&#xff0c;可到https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/下载&#xff0c;注意&#xff0c;这是清华镜像站…

Mysql中的锁机制详解

一、概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。 在数据库中&#xff0c;除了传统的计算资源&#xff08;如CPU、RAM、I/O等&#xff09;的争用以外&#xff0c;数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决…

新电脑Win11系统想要降级为Win10怎么操作?

前言 现在的电脑大部分都是Windows 11系统&#xff0c;组装机还好一些&#xff0c;如果想要使用Windows 10&#xff0c;只需要在安装系统的时候选择Windows 10镜像即可。 但是对于新笔记本、厂商的成品机、一体机来说&#xff0c;只要是全新的电脑&#xff0c;基本上都是Wind…

黑马JavaWeb开发笔记14——Tomcat(介绍、安装与卸载、启动与关闭)、入门程序解析(起步依赖、SpringBoot父工程、内嵌Tomcat)

文章目录 前言一、Web服务器-Tomcat1. 简介1.1服务器概述1.2 Web服务器1.3 Tomcat 2. 基本使用2.1 下载2.2 安装与卸载2.3 启动与关闭2.4 常见问题 二、入门程序解析1. 起步依赖2. SpringBoot父工程3. 内嵌Tomcat 总结 前言 本篇文章是2023年最新黑马JavaWeb开发笔记14&#x…

[Java]SpringBoot登录认证流程详解

登录认证 登录接口 1.查看原型 2.查看接口 3.思路分析 登录核心就是根据用户名和密码查询用户信息,存在则登录成功, 不存在则登录失败 4.Controller Slf4j RestController public class LoginController {Autowiredprivate EmpService empService;/*** 登录的方法** param …

【Python】一文详细向您介绍 bisect_left 函数

【Python】一文详细向您介绍 bisect_left 函数 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;985高校的普通本硕&#x…

shell脚本1----编程规范与变量

shell脚本 shell的功能 Shell&#xff08;壳程序&#xff09;是一个特殊的应用程序&#xff0c;它介于操作系统内核与用户之间&#xff0c;充当了一个“命令解释器”的角色&#xff0c;负责接收用户输入的操作指令&#xff08;命令&#xff09;并进行解释&#xff0c;将需要执…

(前端)面试300问之(3)this的指向判断

一、this的相关理解与解读 1、各角度看this。 1&#xff09;ECMAScript规范&#xff1a; this 关键字执行为当前执行环境的 ThisBinding。 2&#xff09;MDN&#xff1a; In most cases, the value of this is determined by how a function is called. 在绝大多数情况下&…

图片损坏,如何修复?

在数字化时代&#xff0c;图片已成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;有时我们可能会遇到图片损坏的情况&#xff0c;无论是珍贵的家庭照片、工作文档中的关键图像&#xff0c;还是社交媒体上的分享内容&#xff0c;图片损坏都可能带来不小的困扰。那么…

网络传输加密及openssl使用样例(客户端服务器)

文章目录 背景常用加密方式SSLOpenSSL主要功能 库结构 交互流程证书生成生成 RSA 私钥私钥的主要组成部分私钥的格式 创建自签名证书: 签发证书服务器端代码客户端代码常见错误版本问题证书问题证书格式 背景 网络传输中为保证数据安全&#xff0c;通常需要加密 常用加密方式…

Open3D 基于曲率大小的特征点提取

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 三、实现效果 3.1原始点云 3.2提取特征点 Open3D点云算法汇总及实战案例汇总的目录地址&#xff1a; Open3D点云算法与点云深度学习案例汇总&#xff08;长期更新&#xff09;-CSDN博客 一、概述 基于曲率…

STM32 外部中断(EXTI)

STM32 外部中断(EXTI) 实验&#xff1a;配置一个引脚的下降沿作为外部中断。 参考&#xff1a;江协科技 相关缩写 RCC(Reset and Clock Control) 复位和时钟控制 GPIO(General Purpose Input/Output) 通用输入/输出 AFIO(Alternate Function Input Output) 复用功能输入输…

6.Lab five —— Lazy Page Allocation

首先先切换到lazy分支 git checkout lazy make clean Xv6应用程序使用sbrk()系统调用向内核请求堆内存。sbrk()分配物理内存并将其映射到进程的虚拟地址空间。内核为一个大请求分配和映射内存可能需要很长时间。为了提高效率&#xff0c;故采用懒分配的策略 Eliminate alloc…