C++ -- 哈希表封装实现unordered_map 和 unordered_set

news2025/2/28 17:27:39

        本章内容分为源码看框架讲解和结构模拟实现两部分,源码框架是让我们了解容器结构在设计时的思路,模拟实现才是重点。因此如果在看源码结构式感到疑惑,不妨继续往下看,相信一切都会慢慢了解~

源码及框架分析

        在C++98 / SGI-STL30版本的源代码中没有 unordered_map 和 unordered_set(SGI-STL30版本是C++11之前的STL版本),这两个容器是C++11之后才更新的,但是SGI-STL实现了哈希表,容器的名字是hash_map 和 hash_set,作为非标准容器出现(非C++标准规定必须实现),源代码在hash_map / hash_set / stl_map / stl_set / stl_hashtable.h 中。

源代码中 hash_map / hash_set 的实现结构框架核心部分如下:

// stl_hash_set
template <class Value, class HashFcn = hash<Value>,
class EqualKey = equal_to<Value>,
class Alloc = alloc>
class hash_set
{ 
private:
    typedef hashtable<Value, Value, HashFcn, identity<Value>,EqualKey, Alloc> ht;
    ht rep;
public:
    typedef typename ht::key_type key_type;
    typedef typename ht::value_type value_type;
    typedef typename ht::hasher hasher;
    typedef typename ht::key_equal key_equal;
    typedef typename ht::const_iterator iterator;
    typedef typename ht::const_iterator const_iterator;
    hasher hash_funct() const { return rep.hash_funct(); }
    key_equal key_eq() const { return rep.key_eq(); }
};


// stl_hash_map
template <class Key, class T, class HashFcn = hash<Key>,
class EqualKey = equal_to<Key>,class Alloc = alloc>
class hash_map
{ 
private:
    typedef hashtable<pair<const Key, T>, Key, HashFcn,
    select1st<pair<const Key, T> >, EqualKey, Alloc> ht;
    ht rep;
public:
    typedef typename ht::key_type key_type;
    typedef T data_type;
    typedef T mapped_type;
    typedef typename ht::value_type value_type;
    typedef typename ht::hasher hasher;
    typedef typename ht::key_equal key_equal;
    typedef typename ht::iterator iterator;
    typedef typename ht::const_iterator const_iterator;
};


// stl_hashtable.h
template <class Value, class Key, class HashFcn,class ExtractKey, 
class EqualKey,class Alloc>
class hashtable {
public:
    typedef Key key_type;
    typedef Value value_type;
    typedef HashFcn hasher;
    typedef EqualKey key_equal;
private:
    hasher hash;
    key_equal equals;
    ExtractKey get_key;
    typedef __hashtable_node<Value> node;
    vector<node*,Alloc> buckets;
    size_type num_elements;
public:
    typedef __hashtable_iterator<Value, Key, HashFcn, ExtractKey,
EqualKey,Alloc> iterator;
    pair<iterator, bool> insert_unique(const value_type& obj);
    const_iterator find(const key_type& key) const;
};
template <class Value>
struct __hashtable_node
{
    __hashtable_node* next;
    Value val;
};

        通过源码,我们可以看到结构上 hash_map / hash_set 与 map / set 完全类似,复用了同一个hashtable实现 key / key_value 结构,hash_set 传给 hash_table 的是两个 key,hash_map 传给hash_table的是pair<const key, value>

        需要注意的是源码里面跟 map / set 源码类似,命名风格比较乱,这里比 map 和 set 还乱,hash_set 模板参数居然用的 value 命名,hash_map 用的是key 和 T 命名,课件大佬优势写代码也不规范,乱弹琴。下面我们模拟一份结构出来,在命名上我会保持之前模拟的map和set的风格。

        参考源码框架,unordered_map和unordered_set复用之前我们实现的哈希表。

        我们这里相比源码调整一下,key参数就用K,value参数就用V,哈希表中的数据类型,我们使用T。

        其次跟map和set相比而言unordered_map和unordered set的模拟实现类结构更复杂一点,但是大框架和思路是完全类似的。因为HashTable实现了泛型不知道T参数导致是K,还是pair<K,V>,那么insert内部进行插入时要用K对象转换成整形取模和K比较相等,因为pair的value不参与计算取模,且默认支持的是key和value一起比较相等,我们需要时的任何时候只需要比较K对象,所以我们在unordered map和unordered set层分别实现一个MapKeyOfT和SetKeyOfT的仿函数传给HashTable的KeyOfT,然后HashTable中通过KeyOfT仿函数取出T类型对象中的K对象,再转换成整形取模和K比较相等。


iterator的源码实现

template <class Value, class Key, class HashFcn,
	class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator {
	typedef hashtable<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>
		hashtable;
	typedef __hashtable_iterator<Value, Key, HashFcn,
		ExtractKey, EqualKey, Alloc>
		iterator;
	typedef __hashtable_const_iterator<Value, Key, HashFcn,
		ExtractKey, EqualKey, Alloc>
		const_iterator;
	typedef __hashtable_node<Value> node;
	typedef forward_iterator_tag iterator_category;
	typedef Value value_type;
	node* cur;
	hashtable* ht;
	__hashtable_iterator(node* n, hashtable* tab) : cur(n), ht(tab) {}
	__hashtable_iterator() {}
	reference operator*() const { return cur->val; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
	pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
	iterator& operator++();
	iterator operator++(int);
	bool operator==(const iterator& it) const { return cur == it.cur; }
	bool operator!=(const iterator& it) const { return cur != it.cur; }
};
template <class V, class K, class HF, class ExK, class EqK, class A>
__hashtable_iterator<V, K, HF, ExK, EqK, A>&
__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++()
{
	const node* old = cur;
	cur = cur->next;
	if (!cur) {
		size_type bucket = ht->bkt_num(old->val);
		while (!cur && ++bucket < ht->buckets.size())
			cur = ht->buckets[bucket];
	} 
	return* this;
}

         iterator实现的大体框架跟list的iterator思路是一致的,用一个类型封装节点的指针,再通过重载运算符实现,迭代器像指针一样的访问行为,需要注意的是哈希表的迭代器是单向迭代器。

        这里的难点是operator++的实现。iterator中有一个指向节点的指针,如果当前哈希桶下面还有节点,则节点的指针指向下一个节点即可。如果当前哈希桶的下一个节点为nullptr,则需要想办法计算找到下一个桶。这里的难点是反而是结构设计的问题,参考上面的源码,我们看到iterator中除了有节点的指针,还有哈希表对象的指针,这样当前桶走完时,要计算下一个桶就相对容易多了,用key值计算出当前桶位置,依次往后找一个不为空的桶即可。

        begin()返回第一个桶中第一个节点指针构造的迭代器,这里end()返回的迭代器用空表示。

        unordered_set的iterator也不支持修改,我们把unordered_set的第二个模板参数改成const K即可,HashTable<K,const K,SetkeyofT,Hash>_ht;

        unordered_map的iterator不支持修改key但是可以修改value,我们把unordered map的第二
模板参数pair的第一个参数改成constK即可,
HashTable<K,pair<const K,V>,MapKeyofT, Hash> _ht;

模拟实现unordered_map 和 unordered_set 

HashTable.h 

#pragma once
#include<iostream>
#include<vector>
#include<string>
using namespace std;
// 实现步骤:
// 1、实现哈希表
// 2、封装unordered_map和unordered_set的框架 解决KeyOfT
// 3、iterator
// 4、const_iterator
// 5、key不⽀持修改的问题
// 6、operator[]

inline unsigned long __stl_next_prime(unsigned long n)
{
	// Note: assumes long is at least 32 bits.
	static const int __stl_num_primes = 28;
	static const unsigned long __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
	};
	const unsigned long* first = __stl_prime_list;
	const unsigned long* last = __stl_prime_list +
		__stl_num_primes;
	const unsigned long* pos = lower_bound(first, last, n);
	return pos == last ? *(last - 1) : *pos;
}

namespace hash_bucket
{
	enum State
	{
		ESXIT,
		EMPTY,
		DELETE
	};

	template<class T>
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;

		HashNode(const T& data)
			:_data(data)
			,_next(nullptr)
		{}
	};

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

	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (auto& s1 : s)
			{
				hash += s1;
			}
			return hash;
		}
	};
	//前置声明
	template<class k, class T, class KeyOfT, class Hash>
	class HashTable;

	template<class k, class T, class Ref, class Ptr, class KeyOfT, class Hash>
	struct HTIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<k, T, KeyOfT, Hash> HT;
		typedef HTIterator<k, T, Ref, Ptr, KeyOfT, Hash> Self;

		Node* _node;
		const HT* _ht;
		HTIterator(Node* node, const HT* ht)
			:_node(node)
			,_ht(ht)
		{}
		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
		Self operator++()
		{
			if (_node->_next)
			{
				//下一个节点存在
				_node = _node->_next;
			}
			else
			{
				Hash hash;
				KeyOfT kot;
				size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
				++hashi;
				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])
						break;
					++hashi;
				}
				if (hashi == _ht->_tables.size())
					_node = nullptr;
				else
					_node = _ht->_tables[hashi];
			}
			return *this;
		}
	};


	template<class k, class T, class KeyOfT, class Hash = HashFunc<k>>
	class HashTable
	{
		typedef HashNode<T> Node;
		template<class k, class T, class Ref, class Ptr, class KeyOfT, class Hash>
		friend struct HTIterator;
	public:
		typedef HTIterator<k, T, T&, T*, KeyOfT, Hash> Iterator;
		typedef HTIterator<k, T, const T&, const T*, KeyOfT, Hash> const_Iterator;
		Iterator Begin()
		{
			if (_n == 0)
				return End();
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
					return Iterator(cur, this);
			}
			return End();
		}
		Iterator End()
		{
			return Iterator(nullptr, this);
		}

		const_Iterator Begin() const
		{
			if (_n == 0)
				return End();
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
					return Iterator(cur, this);
			}
			return End();
		}
		const_Iterator End() const
		{
			return Iterator(nullptr, this);
		}



		HashTable()
		{
			_tables.resize(__stl_next_prime(_tables.size()+1), nullptr);
		}

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

		pair<Iterator, bool> Insert(const T& data)
		{
			Hash hash;
			KeyOfT kot;
			Iterator it = Find(kot(data));
			if (it != End())
				return make_pair(it, false);

			size_t hashi = hash(kot(data)) % _tables.size();
			//扩容
			if (_n == _tables.size())
			{
				vector<Node*> newTable(__stl_next_prime(_tables.size()+1));
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						//头插到新表
						size_t hashi = hash(kot(cur->_data)) % newTable.size();
						cur = newTable[hashi];
						newTable[hashi] = cur;

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


			//头插
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;

			return make_pair(Iterator(newnode, this), true);
		}

		Iterator Find(const k& key)
		{
			Hash hash;
			KeyOfT kot;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (hash(kot(cur->_data)) == hash(key))
				{
					return Iterator(cur,this);
				}
				cur = cur->_next;
			}
			return Iterator(nullptr,this);
		}

		bool Erase(const k& key)
		{
			Hash hash;
			KeyOfT kot;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (hash(kot(cur->_data)) == hash(key))
				{
					if (prev)
					{
						prev->_next = cur->_next;
					}
					else
					{
						_tables[hashi] = cur->_next;
					}
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

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

        这段代码实现了一个自定义的哈希表(HashTable)数据结构以及基于该哈希表构建的unordered_setunordered_map容器,模仿了 C++ 标准库中对应容器的部分功能。代码中包含了一些模板类和函数,利用了哈希算法来实现高效的数据存储和查找等操作。 这里需要注意的是:由于迭代器类和核心哈希表类都需要使用双方内部的成员,所以这里将迭代器放在哈希表上方,然后将哈希表模板声明在迭代器上方,将迭代器类声明为哈希表类的友元类模板。有各种各样的细节需要注意,各位如果对某一方面有疑问,可以将问题发在评论区,我会一 一解答。

  • __stl_next_prime函数用于查找大于给定数字n的下一个质数。它通过维护一个静态的质数列表__stl_prime_list,利用lower_bound算法在列表中查找第一个不小于n的质数。如果查找位置到达列表末尾,则返回列表中的最后一个质数,否则返回找到的那个质数。
            该函数在哈希表的实现中,常用来确定哈希表合适的桶数量(一般取质数可以让数据分布更均匀,减少冲突),例如在哈希表初始化或者扩容时,根据当前元素数量来确定合适的桶大小(取质数)。
  • HashNode类是哈希表中链表节点的类模板定义。每个节点存储了实际的数据_data以及指向下一个节点的指针_next,用于解决哈希冲突(当不同的键通过哈希函数映射到同一个桶位置时,采用链表法将冲突的元素链接起来)。
  • HashFunc函数:  普通版本的HashFunc是一个函数对象结构体模板,对于传入的键类型k,简单地将其转换为size_t类型作为哈希值(这种方式可能对于自定义类型不太合适,只是一个简单示例)。而特化版本针对string类型,通过将字符串中每个字符的 ASCII 值相加来计算哈希值,是一种简单的字符串哈希计算方式。
  • HTIterator类是哈希表的迭代器类模板定义。它内部包含指向当前节点的指针_node以及指向所属哈希表的指针_ht,通过重载*->!=++等运算符,实现了像遍历普通容器一样去遍历哈希表中的元素。例如,*运算符返回当前节点存储的数据引用,++运算符实现了迭代器向后移动到下一个有效元素(如果当前节点所在链表还有下一个节点就移动过去,否则去查找下一个非空桶位置的节点)。
  • 哈希表的核心类定义:
    迭代器相关:定义了Iteratorconst_Iterator类型,分别用于可读写和只读遍历哈希表,并实现了BeginEnd函数来返回对应迭代器,方便外部对哈希表元素进行遍历操作。
    构造函数和析构函数:构造函数中会调用__stl_next_prime函数来初始化哈希表的桶数组_tables大小为一个合适的质数(初始化为比 0 大一点的质数对应的大小)。析构函数则负责释放每个桶链表中的所有节点内存,避免内存泄漏。
    插入操作(Insert:首先通过Find函数检查要插入的数据是否已经存在,如果不存在,计算出数据应该插入的桶位置(通过哈希函数取模得到)。如果当前元素数量等于桶数量(意味着可能出现冲突过多等情况,需要扩容),则进行扩容操作,创建新的桶数组,将旧桶中的元素重新哈希到新桶中(采用头插法插入新桶),然后将新数据插入到对应桶位置的链表头部,最后返回插入后的迭代器和表示插入成功的true(如果元素已存在则返回已存在元素的迭代器和false表示插入失败)。
    查找操作(Find:根据给定的键,通过哈希函数计算桶位置,然后在对应桶的链表中依次比较节点元素的键(通过KeyOfT提取键)与给定键是否相等,找到则返回对应节点的迭代器,没找到返回指向空的迭代器。
    删除操作(Erase:同样根据键找到对应的桶位置,在桶链表中查找并删除对应节点,如果找到并删除成功则返回true,没找到则返回false。

unordered_set.h 

#include"HashTable.h"
namespace zy
{
	template<class k, class Hash = hash_bucket::HashFunc<k>>
	class unordered_set
	{
	public:
		struct SetKeyOfT
		{
			const k& operator()(const k& key)
			{
				return key;
			}
		};
		typedef typename hash_bucket::HashTable<k, const k, SetKeyOfT, Hash>::Iterator iterator;
		typedef typename hash_bucket::HashTable<k, const k, SetKeyOfT, Hash>::const_Iterator const_iterator;

		iterator begin()
		{
			return _ht.Begin();
		}
		iterator end()
		{
			return _ht.End();
		}
		const_iterator begin() const
		{
			return _ht.Begin();
		}
		const_iterator end() const
		{
			return _ht.End();
		}

		pair<iterator, bool> insert(const k& key)
		{
			return _ht.Insert(key);
		}
		iterator find(const k& key)
		{
			return _ht.Find(key);
		}
		bool erase(const k& key)
		{
			return _ht.Erase();
		}
	private:
		hash_bucket::HashTable<k, const k, SetKeyOfT, Hash> _ht;
	};
}
  • unordered_set类:基于前面定义的HashTable实现了一个无序集合类。它内部定义了一个用于提取键的函数对象SetKeyOfT(这里简单地返回传入的键本身,因为unordered_set中元素本身就是键),然后通过typedef定义了对应的迭代器类型。其成员函数基本都是调用内部HashTable对象_ht的相应函数来实现,比如insertfinderase等操作,分别对应向集合中插入元素、查找元素和删除元素,beginend函数则用于返回遍历集合的迭代器。

unordered_map.h 

#include"HashTable.h"
namespace zy
{
	template<class k, class v, class Hash = hash_bucket::HashFunc<k>>
	class unordered_map
	{
	public:
		struct MapKeyOfT
		{
			const k& operator()(const pair<k,v>& kv)
			{
				return kv.first;
			}
		};
		typedef typename hash_bucket::HashTable<k, pair<const k, v>, MapKeyOfT, Hash>::Iterator iterator;
		typedef typename hash_bucket::HashTable<k, pair<const k, v>, MapKeyOfT, Hash>::const_Iterator const_iterator;

		iterator begin()
		{
			return _ht.Begin();
		}
		iterator end()
		{
			return _ht.End();
		}
		const_iterator begin() const
		{
			return _ht.Begin();
		}
		const_iterator end() const
		{
			return _ht.End();
		}

		pair<iterator, bool> insert(const pair<k, v>& kv)
		{
			return _ht.Insert(kv);
		}
		v& operator[](const k& key)
		{
			pair<iterator, bool> ret = _ht.Insert(make_pair(key, v()));
			return ret.first->second;
		}
		iterator find(const k& key)
		{
			return _ht.Find(key);
		}
		bool erase(const k& key)
		{
			return _ht.Erase();
		}
	private:
		hash_bucket::HashTable<k, pair<const k, v>, MapKeyOfT, Hash> _ht;
	};
}
  • 这里定义了三个模板参数,k 表示键的类型,v 表示值的类型,Hash 是一个可选的哈希函数类型,默认使用 hash_bucket::HashFunc<k> 来计算键的哈希值(这里 hash_bucket 应该是某个自定义的命名空间,里面定义了 HashFunc 这个哈希函数相关的类或者函数等)。
  • 私有成员变量:声明了一个 HashTable 类型的私有对象 _ht,整个 unordered_map 的各种操作实际上都是基于这个内部的 HashTable 来实现的,对外部隐藏了具体的哈希表实现细节。
  • MapKeyOfT:这个类定义了一个函数调用运算符,它的作用是从 pair<k, v> 类型的元素中提取键(也就是返回 pair 中的第一个元素)。这个结构体在后续和 HashTable 结合使用时,用于告诉 HashTable 如何获取键来进行哈希相关操作。
  • 类型别名(typedef)定义:这里通过 typedef 为 HashTable 中的迭代器类型定义了别名,方便在 unordered_map 类中使用,分别定义了普通迭代器 iterator 和常量迭代器 const_iterator。这样使得 unordered_map 的使用者可以像使用标准库容器的迭代器那样来遍历 unordered_map 中的元素。typename的作用是声明定义为类型而非变量,因为域作用限定符既可以声明类型,又可以声明变量。
  • 迭代器相关函数:这些函数用于获取 unordered_map 的起始和结束迭代器,它们直接调用了内部 HashTable 对象 _ht 的相应 Begin 和 End 函数来返回迭代器,从而可以遍历 unordered_map 中的所有键值对元素。
  • 插入元素函数 insert:该函数接受一个 pair<k, v> 类型的参数(代表要插入的键值对),并将插入操作委托给内部的 HashTable 对象 _ht 的 Insert 函数来执行,最后返回插入操作的结果(以包含迭代器和表示是否插入成功的布尔值的 pair 形式返回,和标准库中 unordered_map 的 insert 函数行为类似)。
  • 下标运算符重载 operator[]:这个运算符重载实现了类似标准库 unordered_map 中通过键获取对应值的功能。如果键不存在,它会先插入一个默认构造的 pair(值部分通过 v() 来默认构造),然后返回对应迭代器指向的 pair 中的值的引用。这样就可以方便地通过 map[key] 的形式来访问或修改值了。
  •  查找元素函数 find:调用内部 HashTable 对象的 Find 函数来查找给定键对应的元素,若找到则返回指向该元素的迭代器,否则返回表示末尾的迭代器(类似标准库容器查找函数的行为)。

  • 删除元素函数 erase:

  • 委托给内部 HashTable 对象的 Erase 函数来执行删除操作,根据键删除对应的元素,并返回删除是否成功的布尔值。

        总之,代码通过模板和对 HashTable 的封装,实现了类似于 C++ 标准库中的 unordered_map 和 unordered_set 的基本功能框架,包括元素的插入、查找、删除以及迭代器遍历等操作。不过,要使其完整可用,还需要确保 HashTable 类正确地实现了如 BeginEndInsertFindErase 等相关函数,并且相关的命名空间、类型等定义(如 hash_bucket 相关内容)都是正确和完整的。 同时,代码中可能还需要考虑一些异常处理、内存管理等更完善的细节方面的内容,目前只是实现了核心的功能逻辑部分。

        我们下期再会~

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

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

相关文章

修改vscode中emmet中jsx和tsx语法中className的扩展符号从单引号到双引号 - HTML代码补全 - 单引号双引号

效果图 实现步骤 文件 > 首选项 > 设置搜索“”在settings.json中修改&#xff0c;增加 "emmet.syntaxProfiles": {"html": {"attr_quotes": "single"},"jsx": {"attr_quotes": "double","…

CSS学习记录12

CSS浮动 CSSfloat属性规定元素如何浮动 CSSclear属性规定哪些元素可以在清除的元素旁边以及在哪一侧浮动。 float属性 float属性用于定位和格式化内容&#xff0c;例如让图像向左浮动到容器的文本那里。 float属性可以设置以下值之一&#xff1a; left - 元素浮动到其容器…

IDEA搭建SpringBoot,MyBatis,Mysql工程项目

目录 一、前言 二、项目结构 三、初始化项目 四、SpringBoot项目集成Mybatis编写接口 五、代码仓库 一、前言 构建一个基于Spring Boot框架的现代化Web应用程序&#xff0c;以满足[公司/组织名称]对于[业务需求描述]的需求。通过利用Spring Boot简化企业级应用开发的优势&…

Redis应用—4.在库存里的应用

大纲 1.库存模块设计 2.库存缓存分片和渐进式同步方案 3.基于缓存分片的下单库存扣减方案 4.商品库存设置流程与异步落库的实现 6.库存入库时"缓存分片写入 渐进式写入 写入失败进行MQ补偿"的实现 7.库存扣减时"基于库存分片依次扣减 合并扣减 扣不了…

25. 深浅拷贝

一、什么是浅拷贝 只对对象的最顶层进行的拷贝称为 浅拷贝。我们可以用 copy 模块中的 copy() 方法实现浅拷贝。 import copya [11, 22, 33] b [44, 55, 66] c [a, b] d copy.copy(c)print(f"c: {c}") print(f"d: {d}") print(f"c d: {c d}&q…

【GCC】2015: draft-alvestrand-rmcat-congestion-03 机器翻译

腾讯云的一个分析,明显是看了这个论文和草案的 : 最新的是应该是这个 A Google Congestion Control Algorithm for Real-Time Communication draft-ietf-rmcat-gcc-02 下面的这个应该过期了: draft-alvestrand-rmcat-congestion-03

学习笔记:Verilog VHDL硬件描述语言简介及在线仿真Verilog环境

RTL Verilog VHDL HLS等概念 RTL Register Transfer Level&#xff0c;寄存器传输级。它是数字电路设计中的一种抽象层次&#xff0c;描述了电路中寄存器之间的数据传输和逻辑操作。RTL代码通常用Verilog或VHDL语言编写&#xff0c;是后续综合、仿真等工作的基础。 VHDL全名Ve…

druid图形化监控 + MyBatis优化器使用

文章目录 1.集成druid图形化监控1.配置application.yml2.测试访问 http://localhost:项目端口/druid 2.MyBatis优化器(显示完整sql)1.目录2.SqlBeautyInterceptor.java&#xff1a;sql拦截器3.MybatisConfiguration.java&#xff1a;将sql拦截器注入容器4.测试5.MyBatis优化器动…

1216作业

思维导图 作业 使用无名信号量实现输出春夏秋冬 #include <myhead.h> sem_t sem1,sem2,sem3,sem4; void *fun1() {while(1){sem_wait(&sem1);sleep(1);printf("春\n");sem_post(&sem2);} }void *fun2() {while(1){sem_wait(&sem2);sleep(1);prin…

学习maven(maven 项目模块化,继承,聚合)

前言 本篇博客的核心&#xff1a;理解maven 项目模块化&#xff0c;继承&#xff0c;聚合 的含义 maven 项目模块化 含义 maven项目模块化&#xff1a;使用maven 构建项目&#xff0c;管理项目的方式&#xff0c;我们可以将maven项目根据内在的关系拆分成很多个小项目【模块】…

【Linux】自定义项目-进度条

更多精彩内容..... &#x1f389;❤️播主の主页✨&#x1f618; Stark、-CSDN博客 准备工作&#xff1a;"\r"与"\n"字符 ①&#xff1a;基本含义 在C语言和Linux环境中&#xff0c;\r是回车符&#xff0c;\n是换行符&#xff0c;用于控制文本格式和输出…

OpenLinkSaas 2025年1月开发计划

先来看看OpenLinkSaas的大目标 在OpenLinkSaas的产品目标中&#xff0c;让开发人员更加方便的使用云资源是目标之一。通过各大云厂商的API&#xff0c;来可视化云上基础设施的数据是远远不够的。我们准备在2025年1月份增加方便管理和运营研发场景下服务器的能力。 这部分的功能…

电工电子技术实验:电压比较器及其应用电路

实验目的 1&#xff0e;了解电压比较器与运算放大器的性能区别&#xff1b; 2&#xff0e;掌握电压比较器的结构及特点&#xff1b; 3&#xff0e;掌握电压比较器电压传输特性的测试方法&#xff1b; 4&#xff0e;学习比较器在电路设计中的应用 实验原理 电压比较器是一…

代理 IP 行业现状与未来趋势分析

随着互联网的飞速发展&#xff0c;代理 IP 行业在近年来逐渐兴起并成为网络技术领域中一个备受关注的细分行业。它在数据采集、网络营销、隐私保护等多个方面发挥着重要作用&#xff0c;其行业现状与未来发展趋势值得深入探讨。 目前&#xff0c;代理 IP 行业呈现出以下几个显著…

旅游系统旅游小程序PHP+Uniapp

旅游门票预订系统&#xff0c;支持景点门票、导游产品便捷预订、美食打卡、景点分享、旅游笔记分享等综合系统 更新日志 V1.3.0 1、修复富文本标签 2、新增景点入驻【高级版本】3、新增门票核销【高级版】4、新增门票端口【高级版】

【日常笔记】Spring boot:编写 Content type = ‘text/plain‘ 接口

一、项目场景&#xff1a; 接口&#xff1a;Context-Type&#xff1a;text/plain 方式&#xff1a;POST 项目场景&#xff1a;硬件回调接口 二、实战 PostMapping(value "/xx/xxx", consumes "text/plain" ) 2.1、接口 /*** return String* time 202…

STM32F407+LAN8720A +LWIP +FreeRTOS UDP通讯

STM32F407+LAN8720A +LWIP +FreeRTOS ping通 上一篇实现了LWIP ping 通 本篇实现UDP通讯 实现如下功能: 串口1空闲中断+DMA接收,收到数据用UDP发送UDP接收,收到数据用串口1发送STM32CUBEIDE配置和代码 1. 配置UARAT1的空闲中断+DMA接收 UART1接收到数据,释放信号量,在任…

KeyFormer:使用注意力分数压缩KV缓存

Keyformer: KV Cache Reduction through Key Tokens Selection for Efficient Generative Inference 202403&#xff0c;发表在Mlsys Introduction 优化KV cache的策略&#xff0c;主要是集中在系统级别的优化上&#xff0c;比如FlashAttention、PagedAttention&#xff0c;它…

3.9 网际控制报文协议ICMP

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 前言1 ICMP报文的封装2 ICMP差错报告报文的类型3 不应发送ICMP差错报告报文的情况4 常用的ICMP询问报文类型5 ICMP的应用 前言 网际控制报文协议&#xff08;ICMP&#xff09…

东北大学《2024年839自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《东北大学839自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2024年真题 Part1&#xff1a;2024年完整版真题 2024年真题