C++数据结构 —— 哈希表、unordered_map/set封装

目录

1.哈希概念

1.1哈希函数

1.2哈希冲突

2.闭散列实现

3.开散列实现

4.容器的封装

4.1unordered_map

4.2unordered_set

4.3封装过程中遇到的问题

1.哈希概念

顺序结构以及平衡二叉搜索树结构中,在查找一个元素时需要经过比较。顺序查找时间复杂度为O(N),平衡二叉搜索树查找时间复杂度为O(log_2N)。

理想的搜索方法便是不经过任何比较,通过常数次的操作从表中得到搜索的元素。如果构造一种存储结构,如果某种函数使元素的存储位置与它存储的元素之间形成一种映射关系,那么在查找中可以通过该函数很快找到搜索元素。

该方式称为哈希(散列)方法,哈希方法使用的转换函数称为哈希函数, 构造出来的数据结构称为哈希表。

1.1哈希函数

常用的哈希函数有多种,这里介绍一种:除留余数法。即将元素转换成数字再模上容器的大小,得到的数字便是该元素的存储位置。

1.2哈希冲突

哈希冲突是避免不了的。如果我们使用上述哈希函数,存储26、36、46三个元素时,它们的位置将会重合,这样的行为称为哈希冲突/哈希碰撞。解决这种冲突的办法有两个:闭散列和开散列。

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

闭散列又有多种寻找空位的方法。通常使用线性探测法和二次探测法。线性探测法是指在冲突位置上一个一个位置向后寻找空位;二次探测则是先看后一个位置、后两个位置、后四个位置、后八个位置...... 

开散列(链地址法):哈希表中不直接存储元素,而是存放指针。这个指针指向链表的某个节点。如果发生哈希冲突,就把当前元素链到冲突位置的链表当中去。

2.闭散列实现


enum Status
{
	//空   存在   删除
	EMPTY,EXIST,DELETE
};

template <class K,class V>
struct HashData
{
	pair<K,V> _kv;
	Status _st;

};

// 仿函数
template <class K>
struct HashFunc
{
	//如果是数字类型,直接转化为size_t类型即可
	size_t operator()(const K& k)
	{
		return (size_t)k;
	}
};

//如果不是数字类型,例如string,就需要特化单独处理
template<>
struct HashFunc<string>
{
	size_t operator()(const string& k)
	{
		size_t hash = 0;//返回值
		for (auto& e : k)
		{
			hash *= 131;//每次累加之前,先乘等131
			hash += e;
		}
		return hash;
	}
};

template <class K,class V>
class HashTable
{
	typedef HashData<K, V> Data;
	typedef HashFunc<K> Func;
public:
	HashTable()
		:_n(0)//一开始的有效数据个数为0
	{
		_tables.resize(10);//一开始确保表的大小为10
		//通过resize可以确保size == capacity
	}

	Data* find(const K& k)
	{
		Func f;
		size_t hashi = f(k) % _tables.size();
		while (_tables[hashi]._st == EXIST)//只有存在才说明可能有这个数据存在
		{
			//因为我们删除数据不是彻底删除数据
			//而是将其状态改变
			//所以比较的时候需要确保当前位置的数据不能是删除状态
			if (_tables[hashi]._st != DELETE && _tables[hashi]._kv.first == k)
			{
				return &_tables[hashi];
			}
			++hashi;
			hashi %= _tables.size();
		}
		return nullptr;
	}

	bool insert(const pair<K,V>& kv)
	{
		//如果新插入的数据已经存在,就不需要插入了
		if (find(kv.first))
		{
			return false;
		}

		//首先需要确保负载因子不大于0.7才进行插入
		// 否则就要对表进行扩容
		//double lf = _n / _tables.size();
		//if(lf >= 0.7)
		//{ }

		if (_n * 10 / _tables.size() >= 7)
		{
			HashTable<K, V> newHash;//创建一个新的对象
			//这个对象的表的容量是当前表容量的2倍
			newHash._tables.resize(_tables.size() * 2);

			//将旧表的内容拷贝至新表
			for (auto& e : _tables)
			{
				newHash.insert(kv);
			}

			//最后交换这两个表
			//新对象出了此作用域后自动销毁
			swap(_tables, newHash._tables);
		}

		//不管是否已经扩容,这里都进行新数据的插入操作
		//当然,key不可能永远为数字类型
		//size_t hashi = kv.first % _tables.size();

		Func f;
		size_t hashi = f(kv.first) % _tables.size();
		while (_tables[hashi]._st == EXIST)//如果映射位置已经存在数据
		{
			++hashi;//就到下一个位置去
			hashi %= _tables.size();//确保不越界
		}

		// 此时就找到了状态为EMPTY DELETE的位置
		_tables[hashi]._kv = kv;//插入
		_tables[hashi]._st = EXIST;//设置状态

		++_n;//递增有效数据个数
		return true;
	}

	bool erase(const K& k)//通过key值来删除
	{
		Data* del = find(k);
		if (del)
		{
			del->_st = DELETE;
			--_n;
			return true;
		}
		return false;
	}
private:
	vector<Data> _tables;//存储数据的表
	size_t _n;//有效数据个数
};

3.开散列实现

namespace ly
{

	template <class T>
	struct Func
	{
		size_t operator()(const T& t)
		{
			return (size_t)t;
		}
	};

	template<>
	struct Func<string>
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (auto& e : s)
			{
				hash *= 131;
				hash += e;
			}
			return hash;
		}
	};


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


	template<class K, class V>
	class HashBucket
	{
	public:
		typedef Func<K> Func;
		typedef HashNode<K, V> Node;
		HashBucket()
			:_n(0)
		{
			_tables.resize(10);//默认大小为10
		}

		~HashBucket()
		{
			clear();
		}

		void clear()
		{
			for (auto cur : _tables)
			{
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
			}
		}

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

			if (_n == _tables.size())//当负载因子等于1时
			{
				vector<Node*> newTables;
				newTables.resize(_tables.size() * 2, nullptr);
				for (auto cur : _tables)
				{
					while (cur)//把cur看成新节点
					{
						Node* next = cur->_next;
						//size_t hashi = cur->_kv.first % newTables.size();
						size_t hashi = Func()(cur->_kv.first) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;
						cur = next;
					}
				}
				_tables.swap(newTables);
			}

			//不需要扩容时,头插
			//size_t hashi = kv.first % _tables.size();//不能确保每个K都是数字类型
			size_t hashi = Func()(kv.first) % _tables.size();
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;//增加有效数据个数
			return true;
		}

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

		bool erase(const K& k)
		{
			size_t hashi = Func()(k) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (cur->_kv.first == k)
				{
					if (prev != nullptr)
					{
						prev->_next = cur->_next;
						delete cur;
					}
					else
					{
						_tables[hashi] = cur->_next;
						delete cur;
					}
					--_n;
					return true;
				}
			}
			return false;
		}
	private:
		vector<Node*> _tables;
		size_t _n;//有效数据个数
	};
}

4.容器的封装

4.1unordered_map

//HashBucket.h
#include <string>
#include <vector>
using std::string;
using std::vector;
using std::make_pair;
namespace ly
{

	template <class T>
	struct Func
	{
		size_t operator()(const T& t)
		{
			return (size_t)t;
		}
	};

	template<>
	struct Func<string>
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (auto& e : s)
			{
				hash *= 131;
				hash += e;
			}
			return hash;
		}
	};


	template <class KV>
	struct HashNode
	{
		HashNode<KV>* _next;
		KV _data;
		HashNode(const KV& data)
			:_data(data),_next(nullptr)
		{}
	};



	template<class K, class KV, class KOfKV, class HashFunc>
	class HashBucket;//声明

	//template<class KV>
	template<class K, class KV, class KOfKV, class HashFunc>
	struct HashIterator
	{
		typedef HashNode<KV> Node;
		typedef HashIterator<K, KV, KOfKV, HashFunc> Self;
		typedef HashBucket<K, KV, KOfKV, HashFunc> Tables;
		typedef Func<K> Func;
		Node* _node;
		Tables* _ht;
		/*如果const对象,传参是传不进来的
		就算加上const兼容,那么成员的定义是非const,也不能转换
		所以const迭代器不能在一个模板上实现
		必须单独实现*/
		HashIterator(Node* _node,Tables* ht)
			:_node(_node),_ht(ht)
		{}

		KV& operator*()
		{
			return _node->_data;
		}

		KV* operator->()
		{
			return &_node->_data;
		}

		Self& operator++()
		{
			if (_node->_next)//如果下一个节点不为空
			{
				_node = _node->_next;
			}
			else
			{
				//如果下一个节点为空,就要想办法跳到下一个有效位置
				KOfKV kof;
				size_t hashi = Func()(kof(_node->_data)) % _ht->_tables.size();
				++hashi;
				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])
					{
						_node = _ht->_tables[hashi];
						break;
					}
					else
					{
						++hashi;
					}
				}
				if (hashi == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}

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

	template<class K, class KV, class KOfKV, class HashFunc>
	struct HashConstIterator
	{
		typedef HashNode<KV> Node;
		typedef HashConstIterator<K, KV, KOfKV, HashFunc> Self;
		typedef HashBucket<K, KV, KOfKV, HashFunc> Tables;
		typedef HashIterator<K, KV, KOfKV, HashFunc> iterator;
		typedef Func<K> Func;
		const Node* _node;
		const Tables* _ht;

		HashConstIterator(const Node* _node, const Tables* ht)
			:_node(_node), _ht(ht)
		{}

		/*与set同样的问题,支持普通迭代器向const迭代器的转换*/
		HashConstIterator(const iterator& it)
			:_node(it._node), _ht(it._ht)
		{}

		const KV& operator*()
		{
			return _node->_data;
		}

		const KV* operator->()
		{
			return &_node->_data;
		}

		Self& operator++()
		{
			if (_node->_next)//如果下一个节点不为空
			{
				_node = _node->_next;
			}
			else
			{
				//如果下一个节点为空,就要想办法跳到下一个有效位置
				KOfKV kof;
				size_t hashi = Func()(kof(_node->_data)) % _ht->_tables.size();
				++hashi;
				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])
					{
						_node = _ht->_tables[hashi];
						break;
					}
					else
					{
						++hashi;
					}
				}
				if (hashi == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}

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


	template<class K,class KV,class KOfKV,class HashFunc>
	class HashBucket
	{
	public:
		typedef Func<K> Func;
		typedef HashNode<KV> Node;

		template<class K, class KV, class KOfKV, class HashFunc>
		friend struct HashIterator;
		template<class K, class KV, class KOfKV, class HashFunc>
		friend struct HashConstIterator;

		typedef HashIterator<K, KV, KOfKV, HashFunc> iterator;
		typedef HashConstIterator<K, KV, KOfKV, HashFunc> const_iterator;

		iterator begin()
		{
			//begin应该指向第一个有效位置
			for (auto cur : _tables)
			{
				if (cur)
				{
					return iterator(cur, this);
				}
			}
			return iterator(nullptr, this);
		}

		iterator end()
		{
			return iterator(nullptr, this);
		}

		const_iterator begin() const
		{
			//begin应该指向第一个有效位置
			for (auto cur : _tables)
			{
				if (cur)
				{
					return const_iterator(cur, this);
				}
			}
			return const_iterator(nullptr, this);
		}

		const_iterator end() const
		{
			return const_iterator(nullptr, this);
		}


		HashBucket()
			:_n(0)
		{
			_tables.resize(10);//默认大小为10
		}

		~HashBucket()
		{
			clear();
		}

		void clear()
		{
			for (auto cur : _tables)
			{
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
			}
		}

		std::pair<iterator,bool> insert(const KV& data)
		{
			KOfKV kof;
			auto ret = find(kof(data));
			if (ret != end())
			{
				return make_pair(ret, false);
			}
			

			if (_n == _tables.size())//当负载因子等于1时
			{
				vector<Node*> newTables;
				newTables.resize(_tables.size() * 2, nullptr);
				for (auto cur : _tables)
				{
					while (cur)//把cur看成新节点
					{
						Node* next = cur->_next;
						//size_t hashi = cur->_kv.first % newTables.size();
						size_t hashi = Func()(kof(cur->_data)) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;
						cur = next;
					}
				}
				_tables.swap(newTables);
			}

			//不需要扩容时,头插
			//size_t hashi = kv.first % _tables.size();//不能确保每个K都是数字类型
			size_t hashi = Func()(kof(data)) % _tables.size();
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;//增加有效数据个数
			return make_pair(iterator(newnode,this), true);
		}

		iterator find(const K& k)
		{
			KOfKV kof;
			size_t hashi = Func()(k) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kof(cur->_data) == k)
				{
					return iterator(cur,this);
				}
				else
				{
					cur = cur->_next;
				}
			}
			return iterator(nullptr,this);
		}

		bool erase(const K& k)
		{
			size_t hashi = Func()(k) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (cur->_kv.first == k)
				{
					if (prev != nullptr)
					{
						prev->_next = cur->_next;
						delete cur;
					}
					else
					{
						_tables[hashi] = cur->_next;
						delete cur;
					}
					--_n;
					return true;
				}
			}
			return false;
		}

		vector<Node*>& get_tables()
		{
			return _tables;
		}

		size_t& get_n()
		{
			return _n;
		}
	private:
		vector<Node*> _tables;
		size_t _n;//有效数据个数
	};
}
//unordered_map.h
#pragma once

#include "HashBucket.h"
#include <map>
using std::pair;
namespace ly
{
	//                          通常这个函数由上层提供
	template <class K,class V,class HashFunc = Func<K>>
	class unordered_map
	{
	public:
		struct KeyOfMap
		{
			K operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
		typedef typename HashBucket<K, pair<K, V>, KeyOfMap, HashFunc>::iterator iterator;
		typedef typename HashBucket<K, pair<K, V>, KeyOfMap, HashFunc>::const_iterator const_iterator;

		template<class Iterator>
		unordered_map(Iterator begin, Iterator end)
		{
			while (begin != end)
			{
				insert(make_pair(begin->first, begin->second));
				++begin;
			}
		}

		unordered_map(const unordered_map& um)
		{
			unordered_map tmp(um.begin(), um.end());
			swap(tmp);
		}

		unordered_map()
		{}

		void swap(unordered_map& um)
		{
			std::swap(_hb.get_tables(), um._hb.get_tables());
			std::swap(_hb.get_n(), um._hb.get_n());
		}

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

		const_iterator begin() const
		{
			return _hb.begin();
		}
		const_iterator end() const
		{
			return _hb.end();
		}
		
		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _hb.insert(kv);
		}

		V& operator[](const K& k)
		{
			pair<iterator, bool> p = insert(make_pair(k, V()));
			return p.first->second;
		}
	private:
		HashBucket<K, pair<K, V>,KeyOfMap,HashFunc> _hb;
	};
}

4.2unordered_set

#pragma once

#include "HashBucket.h"
#include <map>
using std::pair;
namespace ly
{
	//                          通常这个函数由上层提供
	template <class K,class HashFunc = Func<K>>
	class unordered_set
	{
	public:
		struct KeyOfSet
		{
			K operator()(const K& k)
			{
				return k;
			}
		};
		typedef typename HashBucket<K,K, KeyOfSet, HashFunc>::const_iterator iterator;

		template<class Iterator>
		unordered_set(Iterator begin, Iterator end)
		{
			while (begin != end)
			{
				insert(*begin);
				++begin;
			}
		}

		unordered_set(const unordered_set& um)
		{
			unordered_set tmp(um.begin(), um.end());
			swap(tmp);
		}

		unordered_set()
		{}

		void swap(unordered_set& um)
		{
			std::swap(_hb.get_tables(), um._hb.get_tables());
			std::swap(_hb.get_n(), um._hb.get_n());
		}

		iterator begin() const 
		{
			return _hb.begin();
		}
		iterator end() const
		{
			return _hb.end();
		}

		/*与set同样的问题,需要支持普通迭代器向const迭代器的转换*/
		pair<iterator, bool> insert(const K& k)
		{
			pair<typename HashBucket<K, K, KeyOfSet, HashFunc>::iterator, bool> p = _hb.insert(k);
			return pair<iterator, bool>(p.first, p.second);
		}

	private:
		HashBucket<K, K, KeyOfSet, HashFunc> _hb;
	};
}

4.3封装过程中遇到的问题

首先便是迭代器的问题。只用哈希表中的链表指针封装出迭代器是不够的,因为我们要保证链表走到空之后还能够走到下一个哈希表中的下一个有效位置的链表位置。所以还需要还需要指向哈希表本身的指针,用来辅助迭代器的计算。

const迭代器为何不能像其他容器一样使用同一个类模板?因为我们需要照顾迭代器中的两个成员。

 

如箭头所指,cur和this都将是被const修饰的,保护了指向的内容。而要将const成员赋值给非const成员会造成权限放大,是不合理的。 

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

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

相关文章

顺序栈的实现

目录 一、数据结构中的栈 二、接口函数 三、栈的初始化 四、入栈 五、判断栈是否为空 六、出栈 七、栈顶元素及元素总数 八、顺序栈的销毁 一、数据结构中的栈 首先&#xff0c;栈&#xff08;Stack&#xff09;这个词在数据结构和操作系统两个学科中都有出现。 操作系…

图像分割系列(一)

图像分割分类 语义分割 把每个像素都打上标签&#xff08;这个像素点是人&#xff0c;树&#xff0c;背景等&#xff09; &#xff08;语义分割只区分类别&#xff0c;不区分类别中具体单位&#xff09; 实例分割 实例分割不光要区别类别&#xff0c;还要区分类别中每一个…

面向切面编程AOP

1.Spring的AOP简介 1.1什么是AOP AOP为Aspect Oriented Programming的缩写&#xff0c;意思是面向切面编程&#xff0c;是通过预编译和运行期动态代理实现程序功能维护的一种技术 AOP是OOP&#xff08;面向对象&#xff09;的延续&#xff0c;利用AOP可以对业务逻辑的各部分…

5个代码技巧,加速你的Python

5个代码技巧&#xff0c;加速你的Python 人生苦短&#xff0c;快学Python&#xff01; Python作为一种功能强大的编程语言&#xff0c;因其简单易学而受到很多初学者的青睐。它的应用领域又非常广泛&#xff1a;科学计算、游戏开发、爬虫、人工智能、自动化办公、Web应用开发…

蓝桥杯C++组怒刷50道真题(填空题)

&#x1f33c;深夜伤感网抑云 - 南辰Music/御小兮 - 单曲 - 网易云音乐 &#x1f33c;多年后再见你 - 乔洋/周林枫 - 单曲 - 网易云音乐 18~22年真题&#xff0c;50题才停更&#xff0c;课业繁忙&#xff0c;有空就更&#xff0c;2023/3/18/23:01写下 目录 &#x1f44a;填…

【C++】智能指针

文章目录&#x1f4d6; 前言1. 智能指针的引入1.1 内存泄露的危害&#xff1a;1.2 异常安全中的内存泄露&#xff1a;1.3 RAII思想&#xff1a;1.3 拦截异常解决不了的内存泄漏&#xff1a;1.4 智能指针解决&#xff1a;2. 智能指针的拷贝2.1 直接拷贝的问题&#xff1a;2.2 au…

STM32实战项目-触摸按键

前言&#xff1a; 通过触摸按键控制LED灯以及继电器&#xff0c;具体实现功能如下&#xff1a; 1、触摸按键1单击与长按&#xff0c;控制LED1&#xff1b; 2、触摸按键2单击与长按&#xff0c;控制LED2; 3、触摸按键3单击与长按&#xff0c;控制LED3; 4、触摸按键4单击与长…

详解Spring、SpringBoot、SpringCloud三者的联系与区别

一、Spring Spring 是一个轻量级的Java 开发框架&#xff0c;主要依存于SSM 框架&#xff0c;即Spring MVC Spring Mybatis&#xff0c;定位很明确&#xff0c;Spring MVC主要负责view 层的显示&#xff0c;Spring 利用IOC 和AOP 来处理业务&#xff0c;Mybatis则是数据的持…

跨域解决方案

跨域解决方案 1.跨域基本介绍 文档&#xff1a;https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS 跨域问题是什么&#xff1f; 一句话&#xff1a;跨域指的是浏览器不能执行其他网站的脚本&#xff0c;它是由浏览器的同源策略造成的&#xff0c;是浏览器对 javascr…

数据结构 | 栈的中缀表达式求值

目录 什么是栈&#xff1f; 栈的基本操作 入栈操作 出栈操作 取栈顶元素 中缀表达式求值 实现思路 具体代码 什么是栈&#xff1f; 栈是一种线性数据结构&#xff0c;具有“先进后出”&#xff08;Last In First Out, LIFO&#xff09;的特点。它可以看作是一种受限的…

“国产版ChatGPT”文心一言发布会现场Demo硬核复现

文章目录前言实验结果一、文学创作问题1 :《三体》的作者是哪里人&#xff1f;问题2&#xff1a;可以总结下三体的核心内容吗&#xff1f;如果要续写的话&#xff0c;可以从哪些角度出发&#xff1f;问题3&#xff1a;如何从哲学角度来进行续写&#xff1f;问题4&#xff1a;电…

学习28个案例总结

学习前 对于之前遇到的问题没有及时总结&#xff0c;导致做什么事情都是新的一样。没有把之前学习到接触到的内容应用上。通过这次对28个案例的学习。把之前遇到的问题总结成自己的经验&#xff0c;在以后的开发过程中避免踩重复性的坑。多看帮助少走弯路。 学习中 对28个案例…

2023年安徽省中职网络安全跨站脚本攻击

B-4&#xff1a;跨站脚本攻击 任务环境说明&#xff1a; √ 服务器场景&#xff1a;Server2125&#xff08;关闭链接&#xff09; √ 服务器场景操作系统&#xff1a;未知 √ 用户名:未知 密码&#xff1a;未知 1.访问服务器网站目录1&#xff0c;根据页面信息完成条件&am…

Shader基础

参考文章:Unity着色器介绍 Shader基础 Properties 声明格式 [optional: attribute] name(“display text in Inspector”, type name) default value 属性类型 Color&#xff1a;颜色属性&#xff0c;表示 RGBA 颜色值。Range&#xff1a;范围属性&#xff0c;表示一个在…

基于微信小程序的校园二手交易平台小程序

文末联系获取源码 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏览器…

22讲MySQL有哪些“饮鸩止渴”提高性能的方法

短连接风暴 是指数据库有很多链接之后只执行了几个语句就断开的客户端&#xff0c;然后我们知道数据库客户端和数据库每次连接不仅需要tcp的三次握手&#xff0c;而且还有mysql的鉴权操作都要占用很多服务器的资源。话虽如此但是如果连接的不多的话其实这点资源无所谓的。 但是…

Web自动化——前端基础知识(二)

1. Web前端开发三要素 web前端开发三要素 什么是HTMl&#xff1f; Html是超文本标记语言&#xff0c;是用来描述网页的一种标记语言HTML是一种标签规则的形式将内容呈现在浏览器中可以以任意编辑器创建&#xff0c;其文件扩展名为.html或.htm保存即可 什么是CSS&#xff1f;…

ElasticSearch-第五天

目录 es中脑裂问题 脑裂定义 脑裂过程分析 解决方案 数据建模 前言 nested object 父子关系数据建模 父子关系 设置 Mapping 索引父文档 索引子文档 Parent / Child 所支持的查询 使用 has_child 查询 使用 has_parent 查询 使用 parent_id 查询 访问子文档 …

学习 Python 之 Pygame 开发魂斗罗(一)

学习 Python 之 Pygame 开发魂斗罗&#xff08;一&#xff09;Pygame回忆Pygame1. 使用pygame创建窗口2. 设置窗口背景颜色3. 获取窗口中的事件4. 在窗口中展示图片(1). pygame中的直角坐标系(2). 展示图片(3). 给部分区域设置颜色5. 在窗口中显示文字6. 播放音乐7. 图片翻转与…

人脸活体检测系统(Python+YOLOv5深度学习模型+清新界面)

摘要&#xff1a;人脸活体检测系统利用视觉方法检测人脸活体对象&#xff0c;区分常见虚假人脸&#xff0c;以便后续人脸识别&#xff0c;提供系统界面记录活体与虚假人脸检测结果。本文详细介绍基于YOLOv5深度学习技术的人脸活体检测系统&#xff0c;在介绍算法原理的同时&…