C++学习进阶:unordered_set和ma的实现

目录

前言 

 1.哈希表的结构

1.1.哈希节点 

1.2.迭代器的结构

1.2.1.普通迭代器

1.2.2.const迭代器的实现 

1.3.哈希表的实现

2.如何封装哈希表实现个性化的容器

2.1.unordered_set的封装

2.2.unordered_map的封装

3.以上内容的代码实现 

3.1.HashTable.h

3.2.unordered_set.h

3.3.unordered_map.h


前言 

我们在上一篇博客 数据结构与算法:哈希表-CSDN博客 中学习了哈希表这个数据结构,也对哈希桶有了一定的了解,学习这个数据结构的本质是为了对STL容器unordered_set和unordered_map进行深入的学习

我们在C知道中搜索“unordered_set和set 区别”,发现了这两个容器对应着不同的场景,为了更好的了解这个两个容器我们开始学习如何实现“unordered_set和map”把!!!

C++学习进阶:map和set的实现-CSDN博客

unordered_set和set是C++标准库中的两个容器,它们都用于存储一组唯一的元素。它们的主要区别在于底层实现和性能特点。

  1. 底层实现:

    • set是基于红黑树实现的有序容器,它可以保持元素的有序性。
    • unordered_set是基于哈希表实现的无序容器,它不会保持元素的有序性。
  2. 查找效率:

    • set的查找效率较高,时间复杂度为O(log n),因为它使用了红黑树作为底层数据结构。
    • unordered_set的查找效率更高,平均情况下时间复杂度为O(1),最坏情况下为O(n),因为它使用了哈希表作为底层数据结构。
  3. 插入和删除操作:

    • set的插入和删除操作相对较慢,时间复杂度为O(log n),因为需要维护红黑树的平衡性。
    • unordered_set的插入和删除操作相对较快,平均情况下时间复杂度为O(1),最坏情况下为O(n),因为需要处理哈希冲突。
  4. 元素顺序:

    • set中的元素按照键值自动排序,因此可以通过迭代器按照顺序访问元素。
    • unordered_set中的元素没有特定的顺序,因此无法通过迭代器按照顺序访问元素。

总结一下: set适用于需要保持元素有序性的场景,而unordered_set适用于对查找操作有较高要求的场景。选择哪个容器取决于具体的需求和性能要求

 1.哈希表的结构

古语云::合抱之木,生于毫末,九层之台,起于累土。对于unordered_set和map的初步模拟是一个很复杂的封装过程,就像是摆在你面前的高楼一般,我们知道很宏伟,但是更加知道这座大厦是有无数个小砖块堆砌而成的,我们实现这个STL容器也是如此,我们需要一步一步,先了解框架,然后一撮一撮土合为一块砖,然后再不断地,最终建成九层之台......

如图:实现这个哈希表我们需要

  1. 定义哈希节点,并且需要实现桶内的节点“增删查”三个函数
  2. 实现一个迭代器,实现const对象和普通对象的调用

1.1.哈希节点 

对于set和map的主要区别是:set传入的是key类型,map传入的是pair类型,所以第一步我们节点存放的数据就需要是一个多参数类型,因而需要引入模版参数。另外我们在哈希桶部分知道,桶的本质是一个链表所以这里我们也需要一个指针变量。这样子一个哈希节点就被抽象出来了!!

template<class T>
struct HashNode
{HashNode<T>* _next;// T可为key 也可为pairT _data;HashNode(const T& data):_data(data), _next(nullptr){}
};

因为增和查需要实现迭代器后才能实现,但是具体的内容我们已经在讲述哈希桶的那篇博客中进行了讲解,这里不在赘述。

1.2.迭代器的结构

  1. 完成一个普通迭代器,实现迭代器的基本功能,操作符重载,还有用什么结构实现
  2. 通过模版参数的设置,实现const迭代器

迭代器的部分我们由浅到深,先实现一个能用的普通迭代器,接着再实现const迭代器

1.2.1.普通迭代器

实现一个迭代器,我们首先要知道什么是迭代器,迭代器是干什么的?

首先,迭代器是一个类似于指针的结构,对于每一个哈希节点都会对应这维护一个迭代器节点,迭代器是用来遍历节点,并且访问节点的一个结构。

那么我们如何实现这个迭代器的结构呢?我们在上一篇博客中清楚地知道,遍历哈希表我们需要知道哈希表的size()和它的哈希桶位置Hash_i,所以我们需要传入哈希表来获得size和维护一个Hash_i来定位当前迭代器在哪一个哈希桶中。

template<class K, class T, class KeyOfT, class Hash>
struct _HTIterator
{typedef HashNode<T> HashNode;typedef _HTIterator<K, T,KeyOfT, Hash> Self;typedef HashTable<K, T, KeyOfT, Hash> HashTable;// 结构HashNode* _node;size_t _Hash_i;const HashTable* _pht;// 这里_HTIterator(HashNode* node, HashTable* pht, size_t Hash_i):_node(node), _pht(pht), _Hash_i(Hash_i){ }Self& operator++(){if (_node->_next != nullptr){_node = _node->_next;}else{// 如果为空需要往下一个哈希桶寻找++_Hash_i;// 不断的找非空桶while (_Hash_i < _pht->_table.size()){if (_pht->_table[_Hash_i] != nullptr){_node = _pht->_table[_Hash_i];break;}_Hash_i++;}// 都是空桶,当前节点即为最后一个桶的最后一个节点if (_Hash_i == _pht->_table.size()){_node = nullptr;}}return *this;}T& operator*() { return _node->_data; }bool operator!=(const Self& s) { return _node != s._node; }};

代码讲解:

  1. 首先我们模版参数的设置,设置class KeyOfT和class Hash,两个模版参数均是为了实现仿函数内容,因为对于set和map,我们不知道传入哈希表的参数是key还是pair,对于set我们需要控制传入的参数仅仅为key,map为pair,于是乎我们可以在外部的set.h和map.h中各自实现一个仿函数来进行“各回各家,各找各妈”。Hash我们也是为了映射不同的变量类型,string和int这样子
  2. 对于迭代器++操作就是向后遍历,我们知道哈希桶的结构是存储链表头结点指针数组+哈希节点链表两个一体的,当我们向后遍历时会出现两种情况:1.当前所在节点的下一个节点不为空,也就是仍在原链表向下找。2.当前节点的下一个节点为空,当前节点为某个桶的尾节点,这时候我们需要向下一个桶遍历,走到下一个桶头节点处。这时如果我们更新后的当前桶下标小于哈希表的长度,如果这个桶为空就向后走找不为空的桶,如果桶不为空就维护数据然后break出去。

另外:这里我们访问了哈希表内的private对象_table,这里后面会在哈希表这个类中设置友元函数

到了这里一个普通的迭代器就实现好了,其实也不“难”(真的不难)。

1.2.2.const迭代器的实现 

我们还是从为什么需要const和怎么实现const迭代器来进行学习,对于迭代器来说我们可以修改迭代器对应哈希节点的数据,而在使用者的角度,有时我们只是想要遍历这个哈希桶,而不希望修改数据,所以开发者往往就会开发一份const迭代器来满足只写的需求,普通迭代器实现可读可写。

在哈希表结构中,const迭代器的具体体现,需要实现数据的指针、和引用不能修改

所以我们需要增加模版参数

template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>

 增加对数据的指针和引用,接下来我们就是实现一个const迭代器的构造函数

template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
struct _HTIterator
{typedef HashNode<T> HashNode;typedef _HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;typedef HashTable<K, T, KeyOfT, Hash> HashTable;// 结构HashNode* _node;size_t _Hash_i;const HashTable* _pht;// 普通迭代器的构造_HTIterator(HashNode* node, HashTable* pht, size_t Hash_i):_node(node), _pht(pht), _Hash_i(Hash_i){ }// const迭代器的构造_HTIterator(HashNode* node, const HashTable* pht, size_t Hash_i):_node(node), _pht(pht), _Hash_i(Hash_i){ }// 实现Insert的pair类型转换的拷贝构造_HTIterator(const _HTIterator<K, T, T&, T*, KeyOfT, Hash>& s):_node(s._node), _pht(s._pht), _Hash_i(s._Hash_i){ }Self& operator++(){if (_node->_next != nullptr){_node = _node->_next;}else{// 如果为空需要往下一个哈希桶寻找++_Hash_i;while (_Hash_i < _pht->_table.size()){if (_pht->_table[_Hash_i] != nullptr){_node = _pht->_table[_Hash_i];break;}_Hash_i++;}if (_Hash_i == _pht->_table.size()){_node = nullptr;}}return *this;}Ptr operator->() { return &_node->_data; }Ref operator*() { return _node->_data; }bool operator!=(const Self& s) { return _node != s._node; }};

实现const迭代器不是主要,主要的是适配unordered_set和unordered_map这两个容器。

1.3.哈希表的实现

基本的架构“增删查”、构造和析构函数我们在上一个博客也是讲过了的,不再赘述,而迭代器我们维护时,通过它的默认构造来实现。

template<class K, class T, class KeyOfT, class Hash>
class HashTable
{typedef HashNode<T> HashNode;// 定义友元函数 提供HashTable的_tabletemplate<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>friend struct _HTIterator;public:// 普通迭代器和const迭代器typedef _HTIterator<K, T, T&, T*, KeyOfT, Hash> iterator;typedef _HTIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;// 定义默认空间HashTable() { _table.resize(10); }~HashTable(){for (size_t i = 0; i < _table.size(); i++){HashNode* current = _table[i];HashNode* next = nullptr;while (current != nullptr){next = current->_next;delete current;current = next;}_table[i] = nullptr;}}// 迭代器iterator begin(){for (size_t i = 0; i < _table.size(); i++){if (_table[i] != nullptr){return iterator(_table[i], this, i);}}return end();}iterator end(){return iterator(nullptr, this, -1);}const_iterator begin() const{for (size_t i = 0; i < _table.size(); i++){if (_table[i] != nullptr){return const_iterator(_table[i], this, i);}}return end();}const_iterator end() const{return const_iterator(nullptr, this, -1);}// 增pair<iterator, bool> Insert(const T& data){Hash hs_func;KeyOfT kot_func;iterator it = Find(kot_func(data));if (it != end())return make_pair(it, false);// 当负载因子为1时才进行扩容if (_num == _table.size()){vector<HashNode*> newT;newT.resize(_table.size() * 2);for (size_t i = 0; i < _table.size(); i++){HashNode* current = _table[i];HashNode* next = nullptr;// 将旧表的内容插入进新表while (current != nullptr){next = current->_next;// 头插逻辑size_t Hash_i = hs_func(kot_func(current->_data)) % newT.size();current->_next = newT[Hash_i];current = next;}_table[i] = nullptr;}_table.swap(newT);}size_t Hash_i = hs_func(kot_func(data)) % _table.size();HashNode* newNode = new HashNode(data);// 头插(针对哈希桶)// 新节点的下一个节点就是原本的头节点newNode->_next = _table[Hash_i];_table[Hash_i] = newNode;_num++;return make_pair(iterator(newNode, this, Hash_i), true);}// 删bool Erase(const K& key){Hash hs_func;KeyOfT kot_func;size_t Hash_i = hs_func(key) % _table.size();HashNode* current = _table[Hash_i];HashNode* prev = nullptr;while (current != nullptr){if (current->_kv.first == key){if (prev == nullptr){_table[Hash_i] = current->_next;}else{prev->_next = current->_next;}delete current;return true;}prev = current;current = current->_next;}return false;}// 查iterator Find(const K& key){Hash hs_func;KeyOfT kot_func;size_t Hash_i = hs_func(key) % _table.size();HashNode* current = _table[Hash_i];while (current != nullptr){if (kot_func(current->_data) == key)return iterator(current, this, Hash_i);current = current->_next;}return end();}private:// 指针数组存储哈希桶的首元素地址vector<HashNode*> _table;// 存入的key的数目size_t _num = 0;
};

 需要注意的是:我们会发现Hash_i这个值的计算有时候不同,这个是根据实际的场景,来进行判断的

 到了这里哈希表的结构我们就初步完成了,代码会在后续模块统一给出!!!

2.如何封装哈希表实现个性化的容器

我们在前言中讲了我们学习哈希表就是为了实现并适配unordered_set和unordered_map这两个容器,也就是通过一份相同的哈希表代码来实现不同逻辑的unordered_set和unordered_map。废话不多说,我们开始吧

2.1.unordered_set的封装

如图unordered_set的封装所示,接下来我们解析一下原理:

  1. unordered_set的底层是哈希表,所以需要维护一个哈希表对象,这跟我们之前实现set封装需要维护一个红黑树对象一致
  2. 对于unordered_set来说,它维护的数据为key类型,并且这个key不可被修改,所以我们对于迭代器的使用默认就为const迭代器
  3. 对于大部分的函数我们只需要按照unordered_set的需求进行复用哈希表的原生提供的函数即可
  4. 值得注意的是当我们需要使用pair<iterator, bool>类型时,我们需要在迭代器中实现一个拷贝构造函数,让普通迭代器转化为const迭代器,实现权限的放大
  5. 对于仿函数struct SetKeyOfT我们让他只接受key类型的数据,也就是当为key时才进入这个仿函数,并返回内容

2.2.unordered_map的封装

这一部分我们讲一下这两个容器的区别与实现:

  1. 首先unordered_map这个容器的key也是不允许修改的,value允许修改,当处于普通迭代器时,我们通过将pair类型的first设置为const类型,并且将对应的迭代器的pair参数也设置为const类型 
  2. 在这里我们可以设置const迭代器来实现只读操作,防止value值被误修改。
  3. 对于哈希表的维护我们的参数为pair类型和set进行区别
  4. 最重要的一点我们的struct MapKeyOfT面对的是pair类型,这里也是封装精髓的体现,通过泛型编程来实现同一个底层结构实现不同原理的容器
  5. 这里还涉及[ ]重载具体看测试函数

可能看到这里大家会疑惑为什么直接给我看了结构,并没有教学如何实现?这个地方呢,因为主要是通过研究这两个容器的结构进行学习,讲起来需要长篇大论,我们这样子知道整体架构,理解为什么和怎么做即可。

3.以上内容的代码实现 

3.1.HashTable.h

#pragma once
#include<iostream>
#include<vector>
#include<string>
using namespace std;// 整体实现的哈希函数
struct HashFunc
{size_t operator()(const size_t& key){return (size_t)key; }size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash *= 31;hash += e;}return hash;}// 也可以通过函数重载来实现,不过需要设置多种类型
};namespace hash_bucket
{template<class T>struct HashNode{HashNode<T>* _next;// T可为key 也可为pairT _data;// STATUS _status = EMPTY;HashNode(const T& data):_data(data), _next(nullptr){}};// 前置声明一下哈希表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> HashNode;typedef _HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;typedef HashTable<K, T, KeyOfT, Hash> HashTable;// 结构HashNode* _node;size_t _Hash_i;const HashTable* _pht;// const HashTable* _pht_const;// 这里_HTIterator(HashNode* node, HashTable* pht, size_t Hash_i):_node(node),_pht(pht),_Hash_i(Hash_i){ }_HTIterator(HashNode* node, const HashTable* pht, size_t Hash_i):_node(node), _pht(pht), _Hash_i(Hash_i){ }_HTIterator(const _HTIterator<K, T, T&, T*, KeyOfT, Hash>& s):_node(s._node), _pht(s._pht), _Hash_i(s._Hash_i){ }Self& operator++(){if (_node->_next != nullptr){_node = _node->_next;}else{++_Hash_i;while (_Hash_i < _pht->_table.size()){if (_pht->_table[_Hash_i] != nullptr){_node = _pht->_table[_Hash_i];break;}_Hash_i++;}if (_Hash_i == _pht->_table.size()){_node = nullptr;}}return *this;}Ptr operator->() { return &_node->_data; }Ref operator*() { return _node->_data; }bool operator!=(const Self& s) { return _node != s._node; }};template<class K, class T, class KeyOfT, class Hash>class HashTable{typedef HashNode<T> HashNode;// 定义友元函数 提供HashTable的_tabletemplate<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;// 定义默认空间HashTable() { _table.resize(10); }HashTable(const HashTable& ht){_table = new vector<HashNode*>(ht._table);_num = ht._num;}~HashTable(){for (size_t i = 0; i < _table.size(); i++){HashNode* current = _table[i];HashNode* next = nullptr;while (current != nullptr){next = current->_next;delete current;current = next;}_table[i] = nullptr;}}// 迭代器iterator begin(){for (size_t i = 0; i < _table.size(); i++){if (_table[i] != nullptr){return iterator(_table[i], this, i);}}return end();}iterator end(){return iterator(nullptr, this, -1);}const_iterator begin() const{for (size_t i = 0; i < _table.size(); i++){if (_table[i] != nullptr){return const_iterator(_table[i], this, i);}}return end();}const_iterator end() const{return const_iterator(nullptr, this, -1);}// 增pair<iterator, bool> Insert(const T& data){Hash hs_func;KeyOfT kot_func;iterator it = Find(kot_func(data));if (it != end())return make_pair(it, false);// 当负载因子为1时才进行扩容if (_num == _table.size()){vector<HashNode*> newT;newT.resize(_table.size() * 2);for (size_t i = 0; i < _table.size(); i++){HashNode* current = _table[i];HashNode* next = nullptr;// 将旧表的内容插入进新表while (current != nullptr){next = current->_next;// 头插逻辑size_t Hash_i = hs_func(kot_func(current->_data)) % newT.size();current->_next = newT[Hash_i];current = next;}_table[i] = nullptr;}_table.swap(newT);}size_t Hash_i = hs_func(kot_func(data)) % _table.size();HashNode* newNode = new HashNode(data);// 头插(针对哈希桶)// 新节点的下一个节点就是原本的头节点newNode->_next = _table[Hash_i];_table[Hash_i] = newNode;_num++;return make_pair(iterator(newNode, this, Hash_i), true);}// 删bool Erase(const K& key){Hash hs_func;KeyOfT kot_func;size_t Hash_i = hs_func(key) % _table.size();HashNode* current = _table[Hash_i];HashNode* prev = nullptr;while (current != nullptr){if (current->_kv.first == key){if (prev == nullptr){_table[Hash_i] = current->_next;}else{prev->_next = current->_next;}delete current;return true;}prev = current;current = current->_next;}return false;}// 查iterator Find(const K& key){Hash hs_func;KeyOfT kot_func;size_t Hash_i = hs_func(key) % _table.size();HashNode* current = _table[Hash_i];while (current != nullptr){if (kot_func(current->_data) == key)return iterator(current, this, Hash_i);current = current->_next;}return end();}// 对于哈希映射值的分析// 因为插入值时,需要知道 位置 和 类型,1.string还是int进行映射找到位置 2.为key还是kv结构,所以需要两个仿函数// size_t Hash_i = hs_func(kot_func(key)) % _table.size();// 当查找和删除时,我们只需要知道 位置即可,类型我们通过后续判断 // size_t Hash_i = hs_func(key) % _table.size();private:// 指针数组存储哈希桶的首元素地址vector<HashNode*> _table;// 节点数size_t _num = 0;};
}

3.2.unordered_set.h

#pragma once
#include"HashTable.h"
namespace zhong
{template<class K, class Hash = HashFunc>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key) { return key;	}};public:typedef typename hash_bucket::HashTable<K, K, SetKeyOfT, HashFunc>::const_iterator iterator;typedef typename hash_bucket::HashTable<K, K, SetKeyOfT, HashFunc>::const_iterator const_iterator;pair<iterator, bool> insert(const K& key) { return _pht.Insert(key); }bool erase(const K& key) { return _pht.Erase(key); }iterator find(const K& key) { return _pht.Find(key); }// iterator begin() { return _pht.begin(); }// iterator end() { return _pht.end(); }const_iterator begin() const { return _pht.begin(); }const_iterator end() const { return _pht.end(); }private:hash_bucket::HashTable<K, K, SetKeyOfT, HashFunc> _pht;};// 测试函数void test1(){unordered_set<int> us;us.insert(1);us.insert(3);us.insert(5);unordered_set<int>::iterator it = us.begin();while (it != us.end()){// *it = 5;cout << *it << endl;++it;	}cout << endl;}
}

3.3.unordered_map.h

#pragma once
#include"HashTable.h"
namespace zhong
{template<class K, class V, class Hash = HashFunc>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv) { return kv.first; }};public:typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, HashFunc>::iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, HashFunc>::const_iterator const_iterator;V& operator[](const K& key){pair<iterator, bool> ret = _pht.Insert(make_pair(key, V()));return ret.first->second;}const V& operator[](const K& key) const {pair<iterator, bool> ret = _pht.Insert(make_pair(key, V()));return ret.first->second;}// 迭代器iterator begin() { return _pht.begin(); }iterator end() { return _pht.end(); }const_iterator begin() const { return _pht.begin(); }const_iterator end() const { return _pht.end(); }// 增删查pair<iterator, bool> insert(const pair<K, V>& kv) { return _pht.Insert(kv); }bool erase(const K& key) { return _pht.Erase(key); }iterator find(const K& key) { return _pht.Find(key); }private:hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, HashFunc> _pht;};// 测试函数void test2(){unordered_map<string, string> mss;mss.insert(make_pair("qq", "11"));mss.insert(make_pair("qa", "22"));mss.insert(make_pair("qw", "33"));unordered_map<string, string>::iterator its = mss.begin();while (its != mss.end()){cout << (*its).first << " " << (*its).second << endl;++its;}cout << endl;unordered_map<int, int> ms;ms.insert(make_pair(1, 1));ms.insert(make_pair(2, 2));ms.insert(make_pair(3, 3));unordered_map<int, int>::const_iterator it = ms.begin();while (it != ms.end()){// (*it).first = 1;// (*it).second = 0;cout << (*it).second << endl;++it;}cout << endl;}void test3(){string arr[] = { "apple", "xiaomi", "xiaomi", "huawei", "huawei" ,"huawei" };unordered_map<string, int> count_map;for (auto& e : arr){count_map[e]++;}for (auto& kv : count_map){cout << kv.first << ":" << kv.second << endl;}cout << endl;}}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

51单片机之LED点阵屏

目录 1.LED点阵屏简介 2.配置LED点阵屏代码 1.LED点阵屏简介 LED点阵屏真的是遍布我们我们生活的每个角落&#xff0c;从街边的流动显示字的招牌到你的液晶显示屏&#xff0c;都是基于点阵屏的原理研究出来的。还有那个世界上最大的球状建筑物&#xff1a;MSG Sphere&#xff…

低代码ARM计算机在IIoT中的采集控制生产面板

工业4.0的大潮下工业物联网&#xff08;IIoT&#xff09;已成为推动制造业转型升级的重要动力。其中&#xff0c;低代码ARM嵌入式计算机凭借其出色的性能、灵活的配置以及高度集成化的特点&#xff0c;在工业设备远程监控、维护与诊断方面发挥着关键作用。 一、远程监控与维护 …

python爬虫———post请求方式(第十四天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

Maven与Jave web结构

Maven 简介 https://www.liaoxuefeng.com/wiki/1252599548343744/1255945359327200 java web module web目录 –src 应用程序源代码和测试程序代码的根目录 –main –java  应用程序源代码目录     --package1     --class1     --class2 –resources  应用…

Docker内更新Jenkins详细讲解

很多小伙伴在Docker中使用Jenkins时更新遇到困难&#xff0c;本次结合自己的实际经验&#xff0c;详细讲解。根据官网Jenkins了解以下内容&#xff1a; 一、Jenkins 是什么? Jenkins是一款开源 CI&CD 软件&#xff0c;用于自动化各种任务&#xff0c;包括构建、测…

Mysql-数据库集群的搭建以及数据库的维护

一、数据库的维护 1.数据库的备份与恢复 1&#xff09;备份指定数据库 #mysqldump -u root -p zx > ./zx.dump 2&#xff09;备份所有库 #mysqldump -u root -p --all-databases > ./all.dump 3)恢复所有库 #mysql -u root -p < ./all.dump 4)恢复指定数据库 #mysq…

网络基础三——IP协议补充和Mac帧协议

全球网络及网段划分的理解 ​ 根据国家组织地区人口综合评估进行IP地址范围的划分&#xff1b; ​ 假设前8位用来区分不同的国家&#xff0c;国际路由器负责全球数据传输&#xff0c;子网掩码为IP/8&#xff1b;次6位区分不同的省份&#xff0c;国内路由器负责全国数据的传输…

再见 MybatisPlus,阿里推出新 ORM 框架更牛X

最近看到一个 ORM 框架 Fluent Mybatis 挺有意思的&#xff0c;整个设计理念非常符合工程师思维。 我对官方文档的部分内容进行了简单整理&#xff0c;通过这篇文章带你看看这个新晋 ORM 框架。 官方文档&#xff1a;https://gitee.com/fluent-mybatis/fluent-mybatis/wikis 提…

Golang | Leetcode Golang题解之第19题删除链表的倒数第N个结点

题目&#xff1a; 题解&#xff1a; func removeNthFromEnd(head *ListNode, n int) *ListNode {dummy : &ListNode{0, head}first, second : head, dummyfor i : 0; i < n; i {first first.Next}for ; first ! nil; first first.Next {second second.Next}second.N…

Redis缓存设计

文章目录 1 缓存的收益与成本分析1.1 收益1.2 成本 2 缓存更新策略的选择和使用场景2.1 LRU/LFU/FIFO算法剔除2.2 超时剔除2.3 主动更新2.4 缓存更新策略对比 2.5 最佳实践 3 缓存粒度控制方法3.1 缓存全部数据3.2 缓存部分数据3.3 缓存粒度控制方法对比 4 缓存穿透问题优化4.1…

(2022级)成都工业学院软件构造实验三:面向数据的软件构造

写在前面 1、基于2022级软件工程实验指导书 2、代码仅提供参考 3、如果代码不满足你的要求&#xff0c;请寻求其他的途径 运行环境 window11家庭版 IntelliJ IDEA 2023.2.2 jdk17.0.6 实验要求 任务&#xff1a; ‍一、构造任务4&#xff1a;批量产生习题并用文件存储…

IntelliJ IDEA 2024.1安装与激活[破解]

一&#xff1a;IDEA官方下载 ①如题&#xff0c;先到IDEA官方下载&#xff0c;简简单单 ②IDEA官方&#xff1a;IntelliJ IDEA – the Leading Java and Kotlin IDE 二&#xff1a;获取脚本 &#x1f31f;网盘下载&#xff1a;jetbra (密码&#xff1a;lzh7) &#x1f31f;获取…

STC89C52学习笔记(七)

STC89C52学习笔记&#xff08;七&#xff09; 综述&#xff1a;本文介绍了串口以及讲述了串口相关寄存器如何配置并给予相关代码。 一、修改代码注意事项 在修改代码时不要一次性加入一堆代码&#xff0c;不利于定位错误。可以先注释一些代码&#xff0c;待解决完毕问题后再…

物联网农业四情在线监测系统

TH-Q2随着科技的飞速发展和信息化时代的来临&#xff0c;物联网技术在各个领域都取得了显著的应用成果。其中&#xff0c;物联网农业四情在线监测系统作为农业现代化的重要组成部分&#xff0c;正在为农业生产带来革命性的变革。 一、物联网农业四情在线监测系统的概念 物联网…

大模型笔记:Prompt tuning

1 NLP模型的几个阶段 1.1 第一阶段&#xff08;在深度学习出现之前&#xff09; 通常聚焦于特征工程&#xff08;feature engineering&#xff09;利用领域知识从数据中提取好的特征 1.2 第二阶段&#xff08;在深度学习出现之后&#xff09; 特征可以从数据中习得——>…

使用 kaggle api 实现 kaggle 数据快速下载

在下载kaggle数据集时&#xff0c;以猫狗数据集举例子&#xff0c;有两种方法&#xff1a; Dogs vs. Cats | Kaggle 1&#xff1a;直接浏览器下载&#xff0c;较慢&#xff0c;不推荐。 2&#xff1a;使用kaggle API下载&#xff0c;很快。本文重点介绍。详情可以&#xff1…

Web前端-Ajax

Ajax 概念:Asynchronous JavaScript And XML,异步的JavaScript和XML。 作用: 1.数据交换:通过Ajax可以给服务器发送请求,并获取服务器响应的数据。 2.异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用的校验等等…

ARM嵌入式控制器带HDMI为制造业注入智能动力

工业自动化技术的飞速发展&#xff0c;IT与OT的融合已成为推动工业进步的关键力量。在这个背景下&#xff0c;ARM工业计算机凭借其强大的功能和灵活性&#xff0c;成为了边缘自动化领域的一颗新星。今天&#xff0c;我们将深入探讨这款设备如何通过其独特的特性&#xff0c;助力…

Scrapy 爬取m3u8视频

Scrapy 爬取m3u8视频 【一】效果展示 爬取ts文件样式 合成的MP4文件 【二】分析m3u8文件路径 视频地址&#xff1a;[在线播放我独自升级 第03集 - 高清资源](https://www.physkan.com/ph/175552-8-3.html) 【1】找到m3u8文件 这里任务目标很明确 就是找m3u8文件 打开浏览器…

【C语言】“vid”Microsoft Visual Studio安装及应用(检验内存泄露)

文章目录 前言安装包获取配置VLD完成 前言 我们在写代码时往往容易存在内存泄漏的情况&#xff0c;所以存在这样一个名为VLD的工具用来检验内存泄漏&#xff0c;现在我来教大家安装一下 安装包获取 vld下载网址&#xff1a;https://github.com/KindDragon/vld/releases/tag/…