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

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的实…...

第4.1节:使用正则表达式

1 第4.1节&#xff1a;使用正则表达式 将正则表达式用斜杠括起来&#xff0c;就能用作模式。随后&#xff0c;该正则表达式会与每条输入记录的完整文本进行比对。&#xff08;通常情况下&#xff0c;它只需匹配文本的部分内容就能视作匹配成功。&#xff09;例如&#xff0c;以…...

【算法day25】 最长有效括号——给你一个只包含 ‘(‘ 和 ‘)‘ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

32. 最长有效括号 给你一个只包含 ‘(’ 和 ‘)’ 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号子串的长度。 https://leetcode.cn/problems/longest-valid-parentheses/ 2.方法二&#xff1a;栈 class Solution { public:int longestValid…...

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

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

C 语言的未来:在变革中坚守核心价值

一、从 “古老” 到 “长青”&#xff1a;C 语言的不可替代性 诞生于 20 世纪 70 年代的 C 语言&#xff0c;历经半个世纪的技术浪潮&#xff0c;至今仍是编程世界的 “基石语言”。尽管 Python、Java 等高级语言在应用层开发中占据主流&#xff0c;但 C 语言在系统级编程和资…...

一款超级好用且开源免费的数据可视化工具——Superset

认识Superset 数字经济、数字化转型、大数据等等依旧是如今火热的领域&#xff0c;数据工作有一个重要的环节就是数据可视化。 看得见的数据才更有价值&#xff01; 现如今依旧有多数企业号称有多少多少数据&#xff0c;然而如果这些数据只是呆在冷冰冰的数据库或文件内则毫无…...

Vue3组合式API与选项式API的核心区别与适用场景

Vue.js作为现代前端开发的主流框架之一&#xff0c;在Vue3中引入了全新的组合式API(Composition API)&#xff0c;与传统的选项式API(Options API)形成了两种不同的开发范式。在当前开发中的两个项目中分别用到了组合式和选项式&#xff0c;故记录一下。本文将全面剖析这两种AP…...

RedHatLinux(2025.3.22)

1、创建/www目录&#xff0c;在/www目录下新建name和https目录&#xff0c;在name和https目录下分别创建一个index.htm1文件&#xff0c;name下面的index.html 文件中包含当前主机的主机名&#xff0c;https目录下的index.htm1文件中包含当前主机的ip地址。 &#xff08;1&…...

【C++篇】类与对象(上篇):从面向过程到面向对象的跨越

&#x1f4ac; 欢迎讨论&#xff1a;在阅读过程中有任何疑问&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;如果你觉得这篇文章对你有帮助&#xff0c;记得点赞、收藏&#xff0c;并分享给更多对C感兴趣的…...

深搜专题13:分割回文串

描述 给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是回文串。返回 s 所有可能的分割方案数。 例如&#xff1a; 输入&#xff1a;“aab” 输出&#xff1a;2 2种方案数是[“a”,“a”,“b”]和[“aa”,“b”] 输入描述 一个字符串 s&#…...

OGG故障指南:OGG-01163 Bad column length (xxx) specified for column

报错 OGG-01163 Bad column length (xxx) specified for column AAA in table OWNER.TABLE, maximum allowable length is yyy原因 源端修改了字段长度。 虽然源端和目标端的长度已经通过DDL语句修改到一致&#xff0c;在extract进程未重启的情况下&#xff0c;生成的trail文…...

智慧运维平台:赋能未来,开启高效运维新时代

在当今数字化浪潮下&#xff0c;企业IT基础设施、工业设备及智慧城市系统的复杂度与日俱增&#xff0c;传统人工运维方式已难以满足高效、精准、智能的管理需求。停机故障、低效响应、数据孤岛等问题直接影响企业运营效率和成本控制。大型智慧运维平台&#xff08;AIOps, Smart…...

基于大语言模型的智能音乐创作系统——从推荐到生成

一、引言&#xff1a;当AI成为音乐创作伙伴 2023年&#xff0c;一款由大语言模型&#xff08;LLM&#xff09;生成的钢琴曲《量子交响曲》在Spotify冲上热搜&#xff0c;引发音乐界震动。传统音乐创作需要数年专业训练&#xff0c;而现代AI技术正在打破这一壁垒。本文提出一种…...

Reactive编程:什么是Reactive编程?Reactive编程思想

文章目录 **1. Reactive编程概述****1.1 什么是Reactive编程&#xff1f;****1.1.1 Reactive编程的定义****1.1.2 Reactive编程的历史****1.1.3 Reactive编程的应用场景****1.1.4 Reactive编程的优势** **1.2 Reactive编程的核心思想****1.2.1 响应式&#xff08;Reactive&…...

深度剖析:U盘突然无法访问的数据拯救之道

一、引言 在数字化办公与数据存储日益普及的当下&#xff0c;U盘凭借其小巧便携、即插即用的特性&#xff0c;成为了人们工作、学习和生活中不可或缺的数据存储工具。然而&#xff0c;U盘突然无法访问这一棘手问题却时常困扰着广大用户&#xff0c;它不仅可能导致重要数据的丢失…...

23种设计模式中的备忘录模式

在不破坏封装的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并允许在对象之外保存和恢复这些状态。 备忘录模式&#xff0c;主要用于捕获并保存一个对象的内部状态&#xff0c;以便将来可以恢复到该状态。 备忘录的模式主要由三个角色来实现&#xff1a;备忘录、发起…...

蓝桥杯-特殊的三角形(dfs/枚举/前缀和)

思路分析 深度优先搜索&#xff08;DFS&#xff09;思路 定义与参数说明 dfs 函数中&#xff0c;last 记录上一条边的长度&#xff0c;用于保证新选边长度大于上一条边&#xff0c;实现三边互不相等 。cnt 记录已选边的数量&#xff0c;当 cnt 达到 3 时&#xff0c;就构成了…...

我的编程之旅:从零到无限可能

一、自我介绍 大家好&#xff0c;我是望云山&#xff0c;一名智能科学与技术专业的大一学生 痴迷于用代码解决现实问题&#xff0c;尤其是自动化工具开发与智能硬件交互方向 2024年偶然用Python写了一个自动整理文件的脚本&#xff0c;第一次感受到“代码即魔法”的震撼 二、…...

一文详解k8s体系架构知识

0.云原生 1.k8s概念 1. k8s集群的两种管理角色 Master&#xff1a;集群控制节点&#xff0c;负责具体命令的执行过程。master节点通常会占用一股独立的服务器&#xff08;高可用部署建议用3台服务器&#xff09;&#xff0c;是整个集群的首脑。 Master节点一组关键进程&#xf…...

wx162基于springboot+vue+uniapp的在线办公小程序

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…...

Baklib内容中台的核心优势是什么?

智能化知识管理引擎 Baklib的智能化知识管理引擎通过多源数据整合与智能分类技术&#xff0c;实现企业知识资产的自动化归集与动态更新。系统内置的语义分析算法可自动识别文档主题&#xff0c;结合自然语言处理技术生成结构化标签体系&#xff0c;大幅降低人工标注成本。针对…...

【C++】C++11介绍列表初始化右值引用和移动语义

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. C11简介2. 统一的列表初始化2.1&#xff5b;&#xff5d;初始化2.2 std::initializer_list 3. 声明3.1 auto3.2 decltype3.3 nullptr 4. 范围for循环4.1 范围for的语法4.2 范围for的使用条件 5. STL中一些变化6. 右…...

搜广推校招面经六十一

美团推荐算法 一、ANN算法了解么&#xff1f;说几种你了解的ANN算法 ANN 近似最近邻搜索&#xff08;Approximate Nearest Neighbor Search&#xff09;算法 1.1. KD-Tree&#xff08;K-Dimensional Tree&#xff0c;K 维树&#xff09; 类型: 空间划分数据结构适用场景: 低…...

人工智能与软件工程结合的发展趋势

AI与软件工程的结合正在深刻改变软件开发的流程、工具和方法&#xff0c;其发展方向涵盖了从代码生成到系统维护的整个生命周期。以下是主要的发展方向和技术趋势&#xff1a; 1. 软件架构体系的重构 从“面向过程”到“面向目标”的架构转型&#xff1a; AI驱动软件设计以目标…...

nacos 外置mysql数据库操作(docker 环境)

目录 一、外置mysql数据库原因&#xff1a; 二、数据库准备工作 三、构建nacos容器 四、效果展示 一、外置mysql数据库原因&#xff1a; 想知道nacos如何外置mysql数据库之前&#xff0c;我们首先要知道为什么要外置mysql数据库&#xff0c;或者说这样做有什么优点和好处&am…...

动力电池热失控:新能源汽车安全的“隐形火山”如何预防?

一、火山爆发前的征兆&#xff1a;热失控的演化逻辑 在锂离子电池内部&#xff0c;正负极材料与电解液的 “亲密接触” 本是能量转换的基石&#xff0c;但当温度突破 180℃临界点&#xff0c;电解液就像被点燃的火药库。以三元锂电池为例&#xff0c;镍钴锰氧化物在 200℃以上…...

【数电】半导体存储电路

组合逻辑电路输入和输出之间是确定关系&#xff0c;与之前的历史记录没有任何关系。时序逻辑电路则有相应的存储元件&#xff0c;要把之前的状态保存起来。 要构成时序逻辑电路&#xff0c;必须要有相应的存储元件&#xff0c;第五章讲述相应的存储元件 一、半导体存储电路概…...

Jenkins插件安装失败如何解决

问题&#xff1a;安装Jenkins时候出现插件无法安装的情况。 测试环境&#xff1a; 操作系统&#xff1a;Windows11 Jenkins&#xff1a;2.479.3 JDK&#xff1a;17.0.14&#xff08;21也可以&#xff09; 解决办法一&#xff1a; 更换当前网络&#xff0c;局域网、移动、联通…...

postman测试文件上传接口详解

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 postman是一个很好的接口测试软件&#xff0c;有时候接口是Get请求方式的&#xff0c;肯定在浏览器都可以测了&#xff0c;不过对于比较规范的RestFul接口&#x…...

什么是贴源库

贴源库的定义与核心概念 贴源库&#xff08;Operational Data Store, ODS&#xff09;是数据架构中的基础层&#xff0c;通常作为数据仓库或数据中台的第一层&#xff0c;负责从业务系统直接抽取、存储原始数据&#xff0c;并保持与源系统的高度一致性。其核心在于“贴近源头”…...