当前位置: 首页 > news >正文

C++进阶——封装哈希表实现unordered_map/set

与红黑树封装map/set基本相似,只是unordered_map/set是单向迭代器,模板多传一个HashFunc。

目录

1、源码及框架分析

2、模拟实现unordered_map/set

2.1 复用的哈希表框架及Insert

2.2 iterator的实现

2.2.1 iteartor的核心源码

2.2.2 iterator的实现思路

2.3 map支持[ ]

2.4 UnorderedMap/Set的代码实现

2.4.1 UnorderedMap.h

2.4.2 UnorderedSet.h

2.4.3 HashTable.h

2.4.4 Test.cpp


1、源码及框架分析

SGI-STL30版本源代码中没有unordered_mapunordered_setSGI-STL30版本C++11之前STL版本这两个容器C++11之后才更新的但是SGI-STL30实现了哈希表只是容器的名字是hash_maphash_set,它们是作为非标准的容器出现的,非标准是指非C++标准规定必须实现的,源代码在hash_map/hash_set/stl_hash_map/stl_hash_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;
};

 template <class Value, class Key, class HashFcn,
          class ExtractKey, class EqualKey,
          class Alloc>
class hashtable {};

插入Value删除查找KeyExtractKey是一个仿函数Value中的Key值

2、模拟实现unordered_map/set

2.1 复用的哈希表框架及Insert

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

2. 哈希表只需要比较key,那么UnorderedMapUnorderedSet各自传一个仿函数KfromT,UnorderedSet是为了兼容UnorderedMap,所以也要实现。

3. const保证了不能修改keyHash传给哈希表哈希函数

HashTable<K, pair<const K, V>, MapKfromT,Hash> _t;

HashTable<K, const K, SetKfromT,Hash> _t;

template<class K, class T, class KfromT,class Hash>

class HashTable{};

#include "HashTable.h"namespace Lzc
{// class Hash = HashFunc<K>,要通过封装的一层去控制底层的逻辑,template<class K, class V, class Hash = HashFunc<K>>class UnorderedMap{struct MapKfromT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:private:HashTable<K, pair<const K, V>, MapKfromT, Hash> _ht;};
}#include "HashTable.h"namespace Lzc
{template<class K, class Hash = HashFunc<K>>class UnorderedSet{struct SetKfromT{const K& operator()(const K& key){return key;}};public:private:HashTable<K, const K, SetKfromT, Hash> _ht;};
}#include <iostream>
#include <vector>
#include <assert.h>using namespace std;// hash_bucket
namespace Lzc
{inline size_t __stl_next_prime(size_t n) {static const int __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};const size_t* first = __stl_prime_list;const size_t* last = __stl_prime_list + __stl_num_primes;const size_t* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;}template<class K>struct HashFunc{size_t operator()(const K& key){return key;}};template<>struct HashFunc<string>{// 字符串转换成整形,可以把字符ASCII码相加即可// 但是直接相加的话,类似"abcd"和"bcad"这样的字符串计算出是相同的// 这里使用BKDR哈希,用上次的计算结果去乘以一个质数,// 这个质数一般取31, 131等效果会比较好size_t operator()(const string& key){size_t hash = 0;for (auto& ch : key){hash = hash * 131 + ch;}return hash;}};template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};template<class K, class T, class KfromT, class Hash>class HashTable{typedef HashNode<T> Node;public:KfromT KfT;Hash hash;HashTable():_table(__stl_next_prime(0), nullptr), _n(0){}HashTable(const HashTable& ht):_table(ht._table.size(), nullptr), _n(0){for (int i = 0; i < ht._table.size(); ++i){Node* cur = ht._table[i];while (cur){Insert(cur->_data);cur = cur->_next;}}}HashTable& operator=(const HashTable& ht){if (this != &ht){HashTable tmp(ht);swap(_table, tmp._table);swap(_n, tmp._n);}return *this;}~HashTable(){for (size_t i = 0; i < _table.size(); ++i){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}_n = 0;}bool Insert(const T& data){if (Find(KfT(data)))return false;// 负载因子>=1扩容if (_n >= _table.size()){vector<Node*> newtable(__stl_next_prime(_table.size() + 1), nullptr);for (size_t i = 0; i < _table.size(); ++i){Node* cur = _table[i];while (cur){Node* next = cur->_next;size_t hash0 = hash(KfT(cur->_data)) % newtable.size();cur->_next = newtable[hash0]; // 头插newtable[hash0] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newtable);}size_t hashi = hash(KfT(data)) % _table.size();Node* newnode = new Node(data);newnode->_next = _table[hashi];// 头插_table[hashi] = newnode;++_n;return true;}Node* Find(const K& key){size_t hashi = hash(key) % _table.size();Node* cur = _table[hashi];while (cur){if (KfT(cur->_data) == key)return cur;cur = cur->_next;}return nullptr;}bool Erase(const K& key){size_t hashi = hash(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];while (cur){if (KfT(cur->_data) == key){if (prev)prev->_next = cur->_next;else_table[hashi] = cur->_next;delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _table;size_t _n;};
}

2.2 iterator的实现

2.2.1 iteartor的核心源码
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_OPERATORpointer 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;
}
2.2.2 iterator的实现思路

1. 整体思路与listiterator一致封装节点的指针,迭代器类模板多传RefPtr两个参数一份模板实现iteratorconst_iterator

2. iterator是一个单向迭代器只能++(因为是哈希桶,桶中是单链表),如果++,这个桶遍历完了,如何找到下一个桶呢?

源码是传一个HashTable的指针,可是,只用传vector<Node*>继续向后找就行了

所以我们传vector<Node*>。const vector<Node*>,是不允许改变vector中的指针。

3. begin()就是最左非空节点end()nullptr

4. const 对象const迭代器不能修改数据,所以

        typedef HTIterator<T, T&, T*, Node*, KfromT, Hash> Iterator;
        typedef HTIterator<T, const T&, const T*, const Node*, KfromT, Hash> ConstIterator;

template<class T, class Ref, class Ptr, class NodePtr, class KfromT, class Hash>struct HTIterator{typedef HashNode<T> Node;typedef HTIterator<T, Ref, Ptr, NodePtr, KfromT, Hash> Self;typedef HTIterator<T, T&, T*, Node*, KfromT, Hash> Iterator;KfromT KfT;Hash hash;HTIterator(NodePtr node, const vector<NodePtr>& table):_node(node), _table(table){}HTIterator(const Iterator& it):_node(it._node), _table(it._table){}Self& operator++(){if (_node->_next){_node = _node->_next;}else{// 找下一个不为空的桶size_t hashi = hash(KfT(_node->_data)) % _table.size();++hashi;while (hashi < _table.size() && _table[hashi] == nullptr){++hashi;}if (hashi == _table.size())_node = nullptr;else_node = _table[hashi];}return *this;}Ref operator*(){assert(_node);return _node->_data;}Ptr operator->(){assert(_node);return &(_node->_data);}bool operator==(const HTIterator& htit) const{return _node == htit._node;}bool operator!=(const HTIterator& htit) const{return _node != htit._node;}NodePtr _node;// 找下一个桶,普通迭代器可以修改元素值但不能修改容器结构(增删元素)。const vector<NodePtr>& _table;};

2.3 map支持[ ]

UnorderedMap支持[ ]主要需要修改Insert返回值

修改HashTable中的insert返回值为pair<Iterator,bool> Insert(const T& data),

插入失败,就返回相同的keyvalue的引用

插入成功,就返回keyvalue(默认值)的引用

2.4 UnorderedMap/Set的代码实现

2.4.1 UnorderedMap.h
#pragma once#include "HashTable.h"namespace Lzc
{// class Hash = HashFunc<K>,要通过封装的一层去控制底层的逻辑,template<class K, class V, class Hash = HashFunc<K>>class UnorderedMap{struct MapKfromT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:typedef typename HashTable<K, pair<const K, V>, MapKfromT, Hash>::Iterator iterator;typedef typename HashTable<K, pair<const K, V>, MapKfromT, Hash>::ConstIterator const_iterator;pair<iterator, bool> insert(const pair<const K, V>& kv){return _ht.Insert(kv);}V& operator[](const K& key){iterator ret = _ht.Insert({ key, V() }).first;return ret->second;}bool erase(const K& key){return _ht.Erase(key);}iterator find(const K& key){return _ht.Find(key);}const_iterator find(const K& key) const{return _ht.Find(key);}iterator begin(){return _ht.Begin();}iterator end(){return _ht.End();}const_iterator begin() const{return _ht.Begin();}const_iterator end() const{return _ht.End();}private:HashTable<K, pair<const K, V>, MapKfromT, Hash> _ht;};
}
2.4.2 UnorderedSet.h
#pragma once#include "HashTable.h"namespace Lzc
{template<class K, class Hash = HashFunc<K>>class UnorderedSet{struct SetKfromT{const K& operator()(const K& key){return key;}};public:typedef typename HashTable<K, const K, SetKfromT, Hash>::Iterator iterator;typedef typename HashTable<K, const K, SetKfromT, Hash>::ConstIterator const_iterator;pair<iterator, bool> insert(const K& kv){return _ht.Insert(kv);}bool erase(const K& key){return _ht.Erase(key);}iterator find(const K& key){return _ht.Find(key);}const_iterator find(const K& key) const{return _ht.Find(key);}iterator begin(){return _ht.Begin();}iterator end(){return _ht.End();}const_iterator begin() const{return _ht.Begin();}const_iterator end() const{return _ht.End();}private:HashTable<K, const K, SetKfromT, Hash> _ht;};
}
2.4.3 HashTable.h
#pragma once#include <iostream>
#include <vector>
#include <assert.h>using namespace std;// hash_bucket
namespace Lzc
{inline size_t __stl_next_prime(size_t n) {static const int __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};const size_t* first = __stl_prime_list;const size_t* last = __stl_prime_list + __stl_num_primes;const size_t* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;}template<class K>struct HashFunc{size_t operator()(const K& key){return key;}};template<>struct HashFunc<string>{// 字符串转换成整形,可以把字符ASCII码相加即可// 但是直接相加的话,类似"abcd"和"bcad"这样的字符串计算出是相同的// 这里使用BKDR哈希,用上次的计算结果去乘以一个质数,// 这个质数一般取31, 131等效果会比较好size_t operator()(const string& key){size_t hash = 0;for (auto& ch : key){hash = hash * 131 + ch;}return hash;}};template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};template<class T, class Ref, class Ptr, class NodePtr, class KfromT, class Hash>struct HTIterator{typedef HashNode<T> Node;typedef HTIterator<T, Ref, Ptr, NodePtr, KfromT, Hash> Self;typedef HTIterator<T, T&, T*, Node*, KfromT, Hash> Iterator;KfromT KfT;Hash hash;HTIterator(NodePtr node, const vector<NodePtr>& table):_node(node), _table(table){}HTIterator(const Iterator& it):_node(it._node), _table(it._table){}Self& operator++(){if (_node->_next){_node = _node->_next;}else{// 找下一个不为空的桶size_t hashi = hash(KfT(_node->_data)) % _table.size();++hashi;while (hashi < _table.size() && _table[hashi] == nullptr){++hashi;}if (hashi == _table.size())_node = nullptr;else_node = _table[hashi];}return *this;}Ref operator*(){assert(_node);return _node->_data;}Ptr operator->(){assert(_node);return &(_node->_data);}bool operator==(const HTIterator& htit) const{return _node == htit._node;}bool operator!=(const HTIterator& htit) const{return _node != htit._node;}NodePtr _node;// 找下一个桶,普通迭代器可以修改元素值但不能修改容器结构(增删元素)。const vector<NodePtr>& _table;};template<class K, class T, class KfromT, class Hash>class HashTable{typedef HashNode<T> Node;public:typedef HTIterator<T, T&, T*, Node*, KfromT, Hash> Iterator;typedef HTIterator<T, const T&, const T*, const Node*, KfromT, Hash> ConstIterator;KfromT KfT;Hash hash;HashTable():_table(__stl_next_prime(0), nullptr), _n(0){}HashTable(const HashTable& ht):_table(ht._table.size(), nullptr), _n(0){for (int i = 0; i < ht._table.size(); ++i){Node* cur = ht._table[i];while (cur){Insert(cur->_data);cur = cur->_next;}}}HashTable& operator=(const HashTable& ht){if (this != &ht){HashTable tmp(ht);swap(_table, tmp._table);swap(_n, tmp._n);}return *this;}~HashTable(){for (size_t i = 0; i < _table.size(); ++i){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}_n = 0;}Iterator Begin(){for (int i = 0; i < _table.size(); ++i){Node* cur = _table[i];if (cur)return Iterator(cur, _table);}return Iterator(nullptr, _table);}Iterator End(){return Iterator(nullptr, _table);}ConstIterator Begin() const{for (int i = 0; i < _table.size(); ++i){const Node* cur = _table[i];if (cur)return ConstIterator(cur, _table);}return ConstIterator(nullptr, _table);}ConstIterator End() const{return ConstIterator(nullptr, _table);}pair<Iterator, bool> Insert(const T& data){Iterator it = Find(KfT(data));if (it != End())return { it,false };// 负载因子>=1扩容if (_n >= _table.size()){vector<Node*> newtable(__stl_next_prime(_table.size() + 1), nullptr);for (size_t i = 0; i < _table.size(); ++i){Node* cur = _table[i];while (cur){Node* next = cur->_next;size_t hash0 = hash(KfT(cur->_data)) % newtable.size();cur->_next = newtable[hash0]; // 头插newtable[hash0] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newtable);}size_t hashi = hash(KfT(data)) % _table.size();Node* newnode = new Node(data);newnode->_next = _table[hashi];// 头插_table[hashi] = newnode;++_n;return { Iterator(newnode,_table),true };}Iterator Find(const K& key){size_t hashi = hash(key) % _table.size();Node* cur = _table[hashi];while (cur){if (KfT(cur->_data) == key)return Iterator(cur, _table);cur = cur->_next;}return Iterator(nullptr, _table);}ConstIterator Find(const K& key) const{size_t hashi = hash(key) % _table.size();const Node* cur = _table[hashi];while (cur){if (KfT(cur->_data) == key)return ConstIterator(cur, _table);cur = cur->_next;}return ConstIterator(nullptr, _table);}bool Erase(const K& key){size_t hashi = hash(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];while (cur){if (KfT(cur->_data) == key){if (prev)prev->_next = cur->_next;else_table[hashi] = cur->_next;delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _table;size_t _n;};
}
2.4.4 Test.cpp
//#include "HashTable.h"
#include "UnorderedMap.h"
#include "UnorderedSet.h"struct SetKfromT
{const int& operator()(const int& key){return key;}
};void TestHashTableBasic() {Lzc::HashTable<int, int, SetKfromT, Lzc::HashFunc<int>> ht;// 插入测试assert(ht.Insert(1).second == true);  // 首次插入成功assert(ht.Insert(1).second == false); // 重复插入失败// 查找测试auto it = ht.Find(1);assert(it != ht.End() && *it == 1);   // 能找到已插入的值assert(ht.Find(2) == ht.End());       // 找不到未插入的值
}void TestHashTableRehash() {Lzc::HashTable<int, int, SetKfromT, Lzc::HashFunc<int>> ht;// 插入足够多的数据触发扩容for (int i = 0; i < 100; ++i) {ht.Insert(i);}// 验证所有数据仍可找到for (int i = 0; i < 100; ++i) {assert(ht.Find(i) != ht.End());}
}void TestHashTableErase() {Lzc::HashTable<int, int, SetKfromT, Lzc::HashFunc<int>> ht;ht.Insert(1);ht.Insert(2);assert(ht.Erase(1) == true);   // 删除存在的键assert(ht.Find(1) == ht.End()); // 确认删除成功assert(ht.Erase(3) == false);  // 删除不存在的键
}void TestUnorderedMapInsert() {Lzc::UnorderedMap<int, string> map;// 插入测试auto it1 = map.insert({ 1, "one" });assert(it1.second == true && (it1.first)->second == "one");auto it2 = map.insert({ 1, "uno" });assert(it2.second == false && (it2.first)->second == "one"); // 重复插入失败// operator[] 测试map[2] = "two";assert(map[2] == "two");       // 修改值assert(map[3] == "");          // 自动插入默认值
}void TestUnorderedMapIteration() {Lzc::UnorderedMap<int, string> map;map.insert({ 1, "one" });map.insert({ 2, "two" });// 范围for遍历for (const auto& e : map) {assert((e.first == 1 && e.second == "one") || (e.first == 2 && e.second == "two"));}
}void TestUnorderedMapCopy() {Lzc::UnorderedMap<int, string> map1;map1.insert({ 1, "one" });// 拷贝构造auto map2 = map1;assert(map2.find(1)->second == "one");// 赋值操作Lzc::UnorderedMap<int, string> map3;map3 = map1;assert(map3.find(1) != map3.end());
}void TestUnorderedSetInsert() {Lzc::UnorderedSet<int> set;assert(set.insert(1).second == true);  // 首次插入成功assert(set.insert(1).second == false); // 重复插入失败assert(set.find(1) != set.end());
}void TestUnorderedSetErase() {Lzc::UnorderedSet<int> set;set.insert(1);set.insert(2);assert(set.erase(1) == true);   // 删除存在的键assert(set.find(1) == set.end());assert(set.erase(3) == false);  // 删除不存在的键
}void TestUnorderedSetEdgeCases() {Lzc::UnorderedSet<int> empty_set;// 空表测试assert(empty_set.find(1) == empty_set.end());assert(empty_set.erase(1) == false);// 插入大量数据for (int i = 0; i < 1000; ++i) {empty_set.insert(i);}assert(empty_set.find(999) != empty_set.end());
}int main() {TestHashTableBasic();TestHashTableRehash();TestHashTableErase();TestUnorderedMapInsert();TestUnorderedMapIteration();TestUnorderedMapCopy();TestUnorderedSetInsert();TestUnorderedSetErase();TestUnorderedSetEdgeCases();cout << "All tests passed!" << endl;return 0;
}

相关文章:

C++进阶——封装哈希表实现unordered_map/set

与红黑树封装map/set基本相似&#xff0c;只是unordered_map/set是单向迭代器&#xff0c;模板多传一个HashFunc。 目录 1、源码及框架分析 2、模拟实现unordered_map/set 2.1 复用的哈希表框架及Insert 2.2 iterator的实现 2.2.1 iteartor的核心源码 2.2.2 iterator的实…...

AI Agent 实战:搭建个人在线旅游助手

AI Agent 实战&#xff1a;搭建个人在线旅游助手 本次实验中&#xff0c;我们将继续探索 Agent 的提示词&#xff0c;学习更加规范的提示词撰写方法。 本实验中你将掌握的知识点 使用 Dify 构建 Agent 的方法结构化的提示词撰写技巧变量的使用方法 1. 准备 在新建 Agent 之…...

CSS中的overflow属性

在 CSS 中&#xff0c;overflow 属性用于控制当一个元素的内容溢出其指定的区域时&#xff0c;应该如何处理溢出的部分。通常用于盒模型&#xff08;如 div&#xff09;中&#xff0c;指定内容超出容器时的显示方式。 overflow 属性的常用值&#xff1a; 1. visible&#xff08…...

【Unity】处理文字显示不全的问题

1.选中字体文件&#xff0c;检查 MultiAtlasTeextures 是否勾选&#xff0c;未勾选的话&#xff0c;先勾选保存后查看是否显示正常 2.勾选后未正常显示&#xff0c;则在搜索框中输入未显示的文本&#xff0c;确认字体图集是否包含该文本&#xff0c;然后点击Update Atlas Textu…...

蓝桥备赛指南(11):递归简介

递归的介绍 概念&#xff1a;递归是指函数直接或间接调用自身的过程。 解释递归的两个关键要素&#xff1a; 基本情况&#xff08;递归终止条件&#xff09;&#xff1a;递归函数中的一个条件&#xff0c;当满足该条件时&#xff0c;递归终止&#xff0c;避免无限递归。可以…...

Python 图片水印处理工具

自定义水印文本自定义水印位置支持图片裁剪支持各种图片格式 from PIL import Image, ImageDraw, ImageFont import osclass ImageWatermarker:def __init__(self, font_pathNone, font_size40):"""初始化水印处理器font_path: 字体文件路径&#xff0c;默认使…...

从零开始:如何打造一套完整的UI设计系统?

1. 建立色彩系统 色彩系统是设计系统的基础之一&#xff0c;它不仅影响界面的整体美感&#xff0c;还对用户体验有着深远的影响。首先&#xff0c;设计师需要定义主色调、辅助色和强调色&#xff0c;并确保这些颜色在不同场景下的应用保持一致。使用工具如Adobe Color或Coolor…...

Jenkins + CICD流程一键自动部署Vue前端项目(保姆级)

git仓库地址&#xff1a;参考以下代码完成,或者采用自己的代码。 南泽/cicd-test 拉取项目代码到本地 使用云服务器或虚拟机采用docker部署jenkins 安装docker过程省略 采用docker部署jenkins&#xff0c;注意这里的命令&#xff0c;一定要映射docker路径&#xff0c;否则无…...

c# 虚函数、接口、抽象区别和应用场景

文章目录 定义和语法实现要求继承和使用场景总结访问修饰符设计目的性能扩展性在 C# 里,虚函数、接口和抽象函数都能助力实现多态性,不过它们的定义、使用场景和特点存在差异,下面为你详细剖析: 定义和语法 虚函数:虚函数在基类里定义,使用 virtual 关键字,且有默认的实…...

数据治理的主题库是做什么的

数据治理的主题库详解 一、定义与核心概念 主题库是数据治理体系中的核心组件&#xff0c;指围绕某一业务主题或实体对象&#xff0c;通过数据清洗、整合、标准化等手段形成的逻辑化、高质量数据集。其核心特征包括&#xff1a; 主题导向&#xff1a;以业务领域&#xff08;…...

pytorch模型的进阶训练和性能优化

综合案例 将MNIST数据集保存成本地图片读取本地图片进行训练读取自己的数据集进行训练用自己的模型进行训练获得更多评价指标提升模型性能的方法 MNIST转本地图片 import os import torchvision import torchvision.transforms as transforms# 下载MNIST数据集 transform t…...

i18next在vue3中的应用,可参考写法或直接复用

i18next是一个国际化相关的的依赖&#xff0c;适配多种框&#xff0c;比如vue2/3&#xff0c;react&#xff0c;next.js等等&#xff0c;是一个非常实用的依赖。在一次项目中接触过i18n相关内容&#xff0c;因此今天就整理一下这个通用的插件。 官网&#xff1a;Introduction …...

DM数据迁移工具

DM数据迁移工具 一、概述二、迁移准备三、启动迁移工具1.Windows 环境启动 DM 数据迁移工具2.Linux 环境启动 DM 数据迁移工具2.1启用图形化安装界面前需要通过如下命令将图形界面权限放开&#xff1a;2.2进入数据库安装路径 /tool 目录下&#xff0c;运行 ./dts 即可启动 DM 数…...

Python入门(4):函数

目录 1 基本概念 1.1 函数的定义与调用 2 函数的参数 2.1 位置参数&#xff08;Positional Arguments&#xff09; 2.2 默认参数&#xff08;Default Arguments&#xff09; 2.3 关键字参数&#xff08;Keyword Arguments&#xff09; **2.4 可变参数&#xff08;*…...

Java基础-25-继承-方法重写-子类构造器的特点-构造器this的调用

在面向对象编程中&#xff0c;继承是实现代码复用和扩展的重要机制。通过继承&#xff0c;子类可以继承父类的属性和方法&#xff0c;并且可以通过方法重写来改变或扩展父类的行为。此外&#xff0c;构造器在对象初始化过程中扮演了重要角色&#xff0c;尤其是在子类构造器中如…...

Mysql之事务(上)

&#x1f3dd;️专栏&#xff1a;Mysql_猫咪-9527的博客-CSDN博客 &#x1f305;主页&#xff1a;猫咪-9527-CSDN博客 “欲穷千里目&#xff0c;更上一层楼。会当凌绝顶&#xff0c;一览众山小。” 目录 1.什么需要为事务&#xff1f; 2.事务的四个特性 1. 原子性&#xff0…...

2025华为软件精英挑战赛2600w思路分享

这里写自定义目录标题 得分展示对象定义请求价值计算时间同步删除操作完整思路 得分展示 对象定义 // 将一个磁盘划分为多个基于标签聚合的区块 class Block{ public:int tag 0; // 区块标签int start_pos;int end_pos;int id;int use_size 0;int v;// 为区块确定范围Bloc…...

LSTM网络是什么?

环境&#xff1a; LSTM网络 问题描述&#xff1a; LSTM网络是什么&#xff1f; 解决方案&#xff1a; LSTM 网络解释 LSTM&#xff08;Long Short-Term Memory&#xff09;网络 是一种特殊的递归神经网络&#xff08;RNN&#xff09;&#xff0c;能够学习长期依赖关系。L…...

bert自然语言处理框架

自然语言处理框架 目录 自然语言处理框架bert自然语言处理框架概念核心特点应用场景 框架和数据集结构编码-解码框架Self-Attention 机制multi-headed机制位置编码Add与Normalize整体框架outputs训练数据集 bert自然语言处理框架 概念 BERT&#xff08;Bidirectional Encoder …...

UE5学习笔记 FPS游戏制作33 游戏保存

文章目录 核心思想创建数据对象创建UIUI参数和方法打开UI存档文件的位置可以保存的数据类型 核心思想 UE自己有保存游戏的功能&#xff0c;核心节点&#xff0c;类似于json操作&#xff0c;需要一个数据类的对象来进行保存和读取 创建存档 加载存档 保存存档 创建数据对象…...

【超详细】一文解决更新小米澎湃2.0后LSPose失效问题

【超详细】一文解决更新澎湃2.0后LSPose失效问题 问题分析&#xff1a; 出现这个问题大多是因为本次为大版本更新A14->A15,因此原来的LSPose无法支持新系统特性导致的&#xff0c;因此我们从此出发解决这个问题。 方案一&#xff08;magisk&#xff09;&#xff1a; 直接…...

Python爬虫教程007:scrapy结合实际案例的简单使用

文章目录 3.1 scrapy安装3.2 scrapy的基本使用3.2.1 scrapy项目的创建和运行3.3 58同城案例3.3.1 创建案例3.3.2 项目结构说明3.4 汽车之家案例3.1 scrapy安装 什么是scrapy: Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。可以应用在包括数据挖掘、信息处…...

【可能性:如何从已有条件中分析一件事情是否会发生? 关键字摘抄】

是否可以直接从前提条件给出的信息中&#xff0c;推理出一件事情是否会发生呢&#xff1f;还真的可以&#xff0c;这一讲&#xff0c;我们就来说说&#xff0c;什么是逻辑上的必然性&#xff0c;可能性和排他性。 白马非马&#xff1f; 春秋战国百家争鸣时期&#xff0c;名家…...

WPS JS宏编程教程(从基础到进阶)-- 第四部分:函数与自定义功能开发

第四部分:函数与自定义功能开发 1. 函数的创建与调用**基础概念****1.1 命名函数与匿名函数****命名函数示例:计算矩形面积****匿名函数示例:动态赋值****1.2 箭头函数****特点**:简化语法,自动继承外层 `this`。2. 自定义函数实战**2.1 身份证信息提取函数****功能**:从…...

Pytorch 张量操作

在深度学习中&#xff0c;数据的表示和处理是至关重要的。PyTorch 作为一个强大的深度学习框架&#xff0c;其核心数据结构是张量&#xff08;Tensor&#xff09;。张量是一个多维数组&#xff0c;类似于 NumPy 的数组&#xff0c;但具有更强大的功能&#xff0c;尤其是在 GPU …...

constant(safe-area-inset-bottom)和env(safe-area-inset-bottom)在uniapp中的使用方法解析

在微信小程序中&#xff0c;padding-bottom: constant(safe-area-inset-bottom); 和 padding-bottom: env(safe-area-inset-bottom); 这两个 CSS 属性用于处理 iPhone X 及更高版本设备的安全区域&#xff08;safe area&#xff09;。这些设备的底部有一个“Home Indicator”&a…...

ROS相关学习笔记

以下是创建并初始化一个新的 catkin 工作空间的具体步骤 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src catkin_init_workspace #这会在 src 目录下创建一个 CMakeLists.txt 文件 构建工作空间 进入工作空间根目录并构建 cd ~/catkin_ws catkin_make 或者&#xff0c;…...

大模型专题10 —LangGraph高级教程:构建支持网页搜索+人工干预的可追溯对话系统

在本教程中,我们将使用 LangGraph 构建一个支持聊天机器人,该机器人能够: ✅ 通过搜索网络回答常见问题 ✅ 在多次调用之间保持对话状态 ✅ 将复杂查询路由给人工进行审核 ✅ 使用自定义状态来控制其行为 ✅ 进行回溯并探索替代的对话路径 我们将从一个基础的聊天机器人开…...

rbpf虚拟机-汇编和反汇编器

文章目录 一、概述二、主要功能三、关键函数解析3.1 汇编器3.1.1 parse -转换为Instruction列表3.1.2 assemble_internal-转换为Insn 3.2 反汇编器3.2.1 to_insn_vec-转换为机器指令 四、总结 Welcome to Code Blocks blog 本篇文章主要介绍了 [rbpf虚拟机-汇编和反汇编器] ❤…...

压测数据说话:如何用科学方法选择最优高防套餐?

一、压测数据到高防参数的映射规则 1. 带宽需求计算 所需防护带宽 压测崩溃带宽 安全系数&#xff08;建议1.5倍&#xff09; 示例&#xff1a;测试崩溃值50Gbps → 选择75G套餐&#xff08;群联资费表“100G套餐&#xffe5;8,500/月”&#xff09; 2. 连接数容量评估 …...

【初阶数据结构】队列

文章目录 目录 一、概念与结构 二、队列的实现 队列的定义 1.初始化 2.入队列 3.判断队列是否为空 4.出队列 5.取队头数据 6.取队尾数据 7.队列有效个数 8.销毁队列 三.完整源码 总结 一、概念与结构 概念&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除…...

ai说js的instanceof是什么怎么用

instanceof 是一个用于检测对象是否是某个构造函数的实例的操作符 &#xff08;1&#xff09;检测内置类型 对于 JavaScript 的内置类型&#xff0c;instanceof 可以用来检测对象是否是某种内置类型的实例。 let arr [1, 2, 3]; console.log(arr instanceof Array); // tru…...

PyTorch中知识蒸馏浅讲

知识蒸馏 在 PyTorch 中,使用 teacher_model.eval() 和冻结教师模型参数是知识蒸馏(Knowledge Distillation)中的关键步骤。 ​1. teacher_model.eval() 的作用 目的: 将教师模型切换到评估模式,影响某些特定层(如 Dropout、BatchNorm)的行为。 ​具体影响: ​Dropo…...

服务器自动备份到本地,服务器自动备份到本地的方法有哪些?

服务器自动备份到本地是确保数据安全和系统恢复能力的关键步骤。以下是几种常见的服务器自动备份到本地的方法&#xff1a; 一、使用系统自带的备份工具 Windows Server Windows Server Backup 简介&#xff1a;Windows Server Backup是Windows Server操作系统内置的备份和…...

Vue+Elementui首页看板

源码 <template><!-- 查询条件--><div class="optimize-norm" v-loading="selectDataLoading"><el-form :model="queryParams" ref="queryRef" style="padding-bottom:8px" :inline="true"…...

力扣HOT100之链表:141. 环形链表

这道题都已经刷烂了&#xff0c;没啥好说的&#xff0c;就是定义快慢指针&#xff0c;慢指针每次移动一步&#xff0c;快指针每次移动两步&#xff0c;如果链表中有环&#xff0c;那么快指针一定会追上慢指针&#xff0c;追上时直接返回true&#xff0c;否则快指针会直接到达链…...

vue实现俄罗斯方块

说明&#xff1a; vue实现俄罗斯方块 效果图&#xff1a; step1:C:\Users\wangrusheng\PycharmProjects\untitled3\src\views\Game.vue <script setup> import { ref, reactive, computed, onMounted, onUnmounted } from vueconst SHAPES [[[1, 1, 1, 1]], // I[[1, …...

Web3.0隐私计算与云手机的结合

Web3.0隐私计算与云手机的结合 Web3.0隐私计算与云手机的结合&#xff0c;标志着从“数据垄断”向“数据自主”的范式转变。通过技术互补&#xff0c;两者能够构建更安全、高效且用户主导的数字生态。尽管面临技术整合和成本挑战&#xff0c;但随着区块链、AI和分布式存储的成…...

git | 版本切换的相关指令

常见指令 git log --oneline #查看历史提交 git tag latest-backup # 对当前的提交进行标记&#xff0c;标记名为latest-backup git checkout -b old-version 55b16aa # 切换到[55b16aa]的提交中&#xff0c;并标记为[old-version]的分支 git checkout master …...

基于 Fluent-Bit 和 Fluentd 的分布式日志采集与处理方案

#作者&#xff1a;任少近 文章目录 需求描述系统目标系统组件Fluent BitFluentdKafka 数据流与处理流程日志采集日志转发到 Fluentd日志处理与转发到 KafkaKafka 作为消息队列 具体配置Fluent-Bit的CM配置Fluent-Bit的DS配置Fluentd的CM配置Fluentd的DS配置Kafka查询结果 需求…...

【渗透测试】Vulnhub靶机-FSoft Challenges VM: 1-详细通关教程

下载地址&#xff1a;https://www.vulnhub.com/entry/fsoft-challenges-vm-1,402/ 目录 前言 信息收集 目录扫描 wpscan扫描 修改密码 反弹shell 提权 思路总结 前言 开始前注意靶机简介&#xff0c;当第一次开机时会报apache错误&#xff0c;所以要等一分钟后重启才…...

c语言strcat和strlen的注意事项

1 .strlen C库函数size_t strlen(const char* str)计算字符串str的长度&#xff0c;直到空字符&#xff0c;不包括空字符。在C语言中&#xff0c;字符串实际上是使用空字符\0结尾的一维字符数组。空字符&#xff08;Null character&#xff09;又称结束符&#xff0c;缩写NUL&…...

本地RAG知识库,如何进行数据结构化和清洗?

环境&#xff1a; 数据结构化和清洗 问题描述&#xff1a; 本地RAG知识库&#xff0c;如何进行数据结构化和清洗&#xff1f; 解决方案&#xff1a; 1. 数据结构化的重要性 RAG技术需求&#xff1a;在检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xf…...

开源测试用例管理平台

不可错过的10个开源测试用例管理平台&#xff1a; PingCode、TestLink、Kiwi TCMS、Squash TM、FitNesse、Tuleap、Robot Framework、SpecFlow、TestMaster、Nitrate。 开源测试用例管理工具提供了一种透明、灵活的解决方案&#xff0c;使团队能够在不受限的情况下适应具体的测…...

OpenAI最近放出大新闻,准备在接下来的几个月内推出一款“开放”的语言模型

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

锁策略--

文章目录 乐观锁和悲观锁轻量锁和重量锁自旋锁和挂起等待锁读写锁和互斥锁可重入锁和不可重入锁公平锁和非公平锁 乐观锁和悲观锁 乐观锁在执行任务前预期竞争不激烈&#xff0c;就先不添加锁等到了发生了真实的锁竞争再进行锁竞争 乐观锁适用于锁竞争不激烈的情况下 悲观锁在…...

IO多路复用

BIO&#xff08;同步阻塞&#xff09; 当客户端请求连接到服务端请求过程中其实是通过socket连接&#xff0c;socket的意思其实就是插座&#xff0c;可以理解成手机需要充电&#xff0c;这里的电要从服务端获取&#xff0c;手机的充电口和服务端的插座都是socket&#xff0c;假…...

AF3 OpenFoldSingleDataset类解读

AlphaFold3 data_modules 模块的 OpenFoldSingleDataset类 是 OpenFold 中的一个数据集类,继承自 torch.utils.data.Dataset,用于加载和处理蛋白质结构数据,以支持 AlphaFold3 相关的深度学习任务。OpenFoldSingleDataset 读取的多序列比对(MSA)数据、模板(template)特征…...

高级java每日一道面试题-2025年3月21日-微服务篇[Nacos篇]-什么是Nacos?

如果有遗漏,评论区告诉我进行补充 面试官: 什么是Nacos&#xff1f; 我回答: Nacos综合解析 一、Nacos的定义与功能 Nacos是阿里巴巴开源的一个专注于动态服务发现、配置管理和服务管理平台&#xff0c;其名称来源于Dynamic Naming and Configuration Service&#xff08;…...

C++练习3

练习 终端上输出 3.14 要求实现 myDouble的 operator operator- operator* &#xff08;不考虑进位&#xff09; class myOut { 没有私有成员 只有公开函数 } myOut out; out << 1 终端输出1 out << 3.14 终端输出3.14 out << "hello" 终…...