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

【c++篇】:解读Set和Map的封装原理--编程中的数据结构优化秘籍

✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉zmh–CSDN博客
✨ 文章所属专栏:c++篇–CSDN博客

在这里插入图片描述

文章目录

  • 前言
  • 一.`set`和`map`的初步封装
    • 1.树的节点封装修改
    • 2.`Find()`查找函数
    • 3.红黑树的封装修改
    • 4.set和map的初步封装框架
  • 二.红黑树的迭代器封装
    • 1.基本框架
    • 2.常用成员函数
    • 3.前置`++`函数
    • 4.前置`--`函数
    • 5.红黑树封装中的迭代器函数
    • 6.红黑树封装中的插入函数修改
  • 三.set封装完整实现
    • 1.set的迭代器函数
    • 2.set的插入函数
    • 3.测试
  • 四.map封装完整实现
    • 1.map的迭代器函数
    • 2.map的插入函数
    • 3.map的`operator[]`函数
    • 4.测试
  • 五.完整代码文件
    • 1.`RBTree.h`文件
    • 2.`Set.h`文件
    • 3.`Map.h`文件

前言

在之前的文章中,我们知道,setmap是两种常用的关联容器。他们内部通常使用红黑树来实现高效的查找,插入和删除操作,尽管它们提供了不同的接口函数,但它们依然可以通过共享相同的底层数据结构(也就是同一个红黑树)来实现。下面将详细讲解如何通过改造我们之前的红黑树来实现我们自己的setmap容器。(红黑树的实现在我上一篇文章中有详细讲解,不清楚的可以看我之前的文章)

一.setmap的初步封装

1.树的节点封装修改

首先我们来看一下我们之前的红黑树如何实现节点类的封装:

//节点类封装
template<class K,class V>
class RBTreeNode{
public://构造函数RBTreeNode(const pair<K,V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED)    {}//成员变量RBTree<K,V>* _left;RBTree<K,V>* _right;RBTree<K,V>* _parent;pair<K,V> _kv;Colour _col;
};

我们学完set和map应该已经知道,set存储的是一个key值,而map存储的是一个键值对key-value,在上面这段代码中,如果通过这两个模板参数可以实现map,但set却没办法使用,因此,这里节点类的两个模板参数需要使用一个泛型参数来修改,这样就可以实现set和map能够共享一颗树。

修改如下:

//RBTree.h文件template<class T>
class RBTreeNode {
public://构造函数RBTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}//成员变量RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;
};

通过上面的修改为一个模板参数就可以实现共享效果:

set存储的是一个键值key,这里的模板参数T就是键值key的类型

map存储的是一个键值对key-value,这里的模板参数T就是容器pair<key,value> 类型

2.Find()查找函数

在上面对节点封装进行修改后,这里又会产生新的问题,如果我们要通过键值Key来查找对应的节点(也就是Find()函数的参数是键值key,对于set来说直接通过键值key就能找到,但是map中的节点存储的是一个键值对key-value,也就是一个,pair(key,value)对象,直接通过参数key并不能查找,具体可以看下面函数

Node* Find(const K& key){Node* cur=_root;while(cur){//对于set来说,_data就是key//对于map来说,_data是一个piar(key,value)对象if(cur->_data<key){cur=cur->_right;}else if(cur->_data>key){cur=cur->_left;}else{return cur;}}return nullptr;
}

因此为了解决这个问题,这里需要借助一个仿函数来实现两个不同容器的查找

  • set的仿函数:

    struct SetKeyOfT{const K& operator()(const K& key){return key;}
    };
    
  • map的仿函数:

    struct MapKeyOfT{const K& operator()(const pair<K,V>& kv){return kv.first;}
    };
    
  • Find()函数:

    Node* Find(const K& key){Node* cur=_root;//对于set来说,这里的KeyOfT就是SetKeyOfT//对于map来说,这里的KeyOfT就是MapKeyOfTKeyOfT kot;while(cur){if(kot(cur->_data)){cur=cur->_right;}else if(kot(cur->_data)>key){cur=cur->_left;}else{return cur;}}return nullptr;
    }
    

3.红黑树的封装修改

上面了解完如何实现两个不同容器之间的查找之后,这里就需要开始对原本的红黑树进行封装修改,从Find()函数中我们可以看到,需要一个新的模板参数KeyOfT,用来实现不同容器仿函数的查找功能。

修改如下:

//RBTree.h文件//增加一个新的模板参数KeyOfT
template<class K,class V,class KeyOfT>
class RBTree{typedef RBTreeNode<T> Node;
public:   //构造函数RBTree():_root(nullptr){}//Node* Find(const K& key);//...其他成员函数private:Node* _root;
}

4.set和map的初步封装框架

有了前面三个的基础这里就可以开始对set和map进行初步的封装,set和map的底层都是借用同一个红黑树。

  • set的初步封装框架:

    //Set.h文件namesapce MySet{template<class K>class Set{//将仿函数设置为内部类struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://...其他成员函数private://第一个模板参数K是set存储的数据类型RBTree<K,K,SetKeyOfT> _t;};
    };
    
  • map的初步封装框架:

    //Map.h文件namesapce MyMap{template<class K,class V>class Map{//将仿函数设置为内部类struct MapKeyOfT{const K& operator()(const pair<K,V>& kv){return kv.first;}};public://...其他成员函数private://第一个模板参数K是set存储的数据类型RBTree<K,pair<const K,V>,MapKeyOfT> _t;};
    };
    

二.红黑树的迭代器封装

这里红黑树的迭代器封装其实和容器list比较类似,不能像string和vector一样使用原生指针作为迭代器,只能通过封装结点指针来实现迭代器。

1.基本框架

//迭代器封装
template<class T,class Ptr,class Ref>
class TreeIterator{//重命名定义typedef RBTreeNode<T> Node;typedef TreeIterator<T,Ptr,Ref> self;public://节点指针Node* _node;//构造函数TreeIterator(Node* node):_node(node){}//...成员函数}

2.常用成员函数

  • operator*函数:

    //T& operator*()
    //const T& operator*()
    //用模板参数Ref来实现两个不同返回类型的替换
    Ref opeartor*(){return _node->_data;
    }
    
  • operator->函数:

    //T* operator->()
    //const T* operator->()
    //用模板参数Ptr来实现两个不同返回类型的替换
    Ptr operator->(){return &_node->_data;
    }
    
  • operator!=函数:

    bool operator!=(const self& s)const {return _node!=s._node;
    }
    
  • operator==函数:

    bool operator==(const self& s)const {return _node==s._node;
    }
    

3.前置++函数

在这里插入图片描述

self operator++(){//如果该节点右子节点不为空,则到右子树中找最左节点if(_node->_right){Node* subleft=_node->_right;while(subleft->_left){subleft=subleft->_left;}_node=subleft;}//如果该节点右子节点为空,则找到该节点父节点的左子节点的祖先节点else{Node* cur=_node;Node* parent=cur->_parent;while(parent){//如果cur节点是父节点的右子节点,继续往上if(cur==parent->_right){cur=parent;parent=parent->_parent;}//如果cur节点是父节点的左子节点,停止else{break;}}_node=parent;}return *this;
}

4.前置--函数

在这里插入图片描述

self& operator--(){if(_node->_left){Node* subright=_node->_left;while(subright->_right){subright=subright->_right;}_node=subright;}else{Node* cur=_node;Node* parent=cur->_parent;while(parent){if(cur==parent->_left){cur=parent;parent=parent->_parent;}else{break;}_node=parent;}return *this;}
}

5.红黑树封装中的迭代器函数

对于红黑树来说,有普通对象迭代器和const对象迭代器。

template<class ,class T,class KeyOfT>
class RBTree{//....public://普通对象迭代器typedef TreeIterator<T,T*,T&> iterator;//const对象迭代器typedef TreeIterator<T,const T*,const T&> const_iterator;iterator begin(){Node* cur=_root;while(cur&&cur->_left){cur=cur->_left;}return cur;} iterator end(){return nullptr;}const_iterator begin()const {Node* cur=_root;while(cur&&cur->_left){cur=cur->_left;}return cur;}const_iterator end()const {return nullptr;}
}

6.红黑树封装中的插入函数修改

有了前面红黑树封装的迭代器,这里插入函数就可以进行修改,从原本的bool类型,变为pair<iterator,bool>类型,其中,iterator表示插入位置的迭代器,如果差人成功,返回插入位置的迭代器和true;如果该值已经存在,返回该值位置的迭代器和false。

//这里第三个模板参数KeyOfT就可以用到
pair<iteraotr,bool> insert(const T& data){if(_root==nullptr){_root=new Node(data);_root->_col=BLACK;return make_pair(_root,true);}Node* parent=nullptr;Node* cur=_root;KeyOfT kot;while(cur){if(kot(cur->_data)<kot(data)){parent=cur;cur=cur->_right;}else if(kot(cur->_data)>kot(data)){parent=cur;cur=cur->_left;}else{return make_pair(cur,false);}}cur=new Node(data);cur=_col=RED;if(kot(parent->_data)<kot(data)){parent->_right=cur;}else{parent->_left=cur;}cur->_parent=parent;//更新平衡因子,旋转,变色//...return make_pair(newnode,true);
}

三.set封装完整实现

1.set的迭代器函数

在前面通过对红黑树的迭代器进行封装之后,这里就可以直接实现set的迭代器函数

  • 代码实现:
namespace MySet{template<class K>class set{//内部类,用来获取set存储对象中的key值struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://这里的类模板还没有实例化对象,所以要加上关键字typenametypedef typename RBTree<K,K,SetKeyOfT>::const_iterator iterator;typedef typename RBTree<K,K,SetKeyOfT>::const_iterator const_iterator;//set的begin()函数const_iterator begin()const {return _t.begin();}//set的end()函数const_iterator end()const {return _t.end();}private://第一个模板参数k是set存储的数据类型RBTree<K,K,SetKeyOfT> _t;};
};
  • 实现原理:

    • 首先就是要将红黑树原本的迭代器类型进行命名重定义,这里有一个注意点,因为RBTree<K,K,SetKeyOfT>是一个类模板,现在还没有进行实例化,所以直接加上作用域限定符::后面加上迭代器类型会报错,因为编译器并不知道当前模板参数的具体类型,因此要加上关键字typename

    • 其次set还有一个性质就是对于key值,不能进行修改,所以使用迭代器时,如果对于当前迭代器解引用获取key值,要求只能访问,不能修改。因此我们这里可以将set的普通类型迭代器iterator和const类型迭代器const_iterator全都使用红黑树的const类型迭代器typename RBTree<K,K,SetKeyOfT>::const_iterator

2.set的插入函数

set的插入函数返回的是一个pair<iterator,bool>类型

pair<iterator,bool> insert(const K& key){return _t.insert(key);
}

注意:set的返回类型pair<iterator,bool>表面上看起来是普通类型的迭代器,但其实,我们是将红黑树的const_iterator迭代器重命名定义成了iterator,因此pair<iterator,bool>中的其实是const_iterator,但是红黑树的插入函数返回的又是一个iterator,所以这里直接写成上面的代码显然是错误的。

正确的写法是要进行一次类型转换:

pair<iterator,bool> insert(const K& key){pair<typename RBTree<K,K,SetKeyOfT>::iterator ret=_t.insert(key);return pair<iterator,bool>(ret.first,ret.second);
}

为了实现从iterator类型转换为const_iterator,我们要在红黑树的迭代器封装中添加一个构造函数

TreeIterator(Node* node)
:_node(node)
{}

3.测试

测试代码:

#include"Set.h"int main(){MySet::set<int> s;s.insert(2);s.insert(10);s.insert(4);s.insert(7);MySet::set<int>::iterator sit=s.begin();while(sit!=s.end()){//(*sit)++;//这里修改key值就会报错cout<<*sit<<" ";++sit;}cout<<endl;return 0;
}

测试结果:

在这里插入图片描述

四.map封装完整实现

1.map的迭代器函数

这里map的迭代器函数和set的有些不同,因为set要求存储的key值不能被修改,而map只限定了键值对中的key值不能修改,而value值可以修改,所以这里使用红黑树类模板做参数时有些不同,通过pair<const K,V>实现key值不能修改,而value值可以修改。

namespace MyMap{template<class K,class V>class Map{struct MapKeyOfT{const K& operator()(const pair<const K,V>& kv){return kv.first;}};public://map的普通迭代器iteratortypedef typename RBTree<K,pair<const K,V>,MapKeyOfT>::iterator iterator;//map的const类型迭代器const_iteratortypedef typename RBTree<K,pair<const K,V>,MapKeyOfT>::const_iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin()const {return _t.begin();}const_iterator end()const{return _t.end();}//其他成员函数//...private:RBTree<K,pair<const K,V>,MapKeyOfT> _t;};
};

2.map的插入函数

相较于set的插入函数,map的插入函数就比较简单,直接调用函数即可

pair<iterator,bool> insert(const pair<const K,V>& kv){return _t.insert(kv);
}

3.map的operator[]函数

map相比于set还有一个其他的使用方法,就是[][]可以通过key值,返回value值。如果参数key值不存在map中,会将参数key值插入到map中,然后返回value的对应类型的初始化值,当然还可以通过[]修改对应key值的value值。

V& operator[](const K& key){//V()是模板参数V的默认构造pair<iterator,bool> rit=insert(make_pair(key,v()));return rit.first->second;
}

4.测试

测试代码:

#include"Map.h"void test1(){MyMap::Map<int,int> m;m.insert(make_pair(3,3));m.insert(make_pair(2,2));m.insert(make_pair(1,1));MyMap::Map<int,int>::iterator it=m.begin();while(it!=m.end()){//(it->first)++;//这里对key值修改就会报错cout<<it->first<<" "<<it->second<<endl;++it;}cout<<endl;m[3]=1;m[4];m[5]=100;for(auto e : m){cout<<e.first<<" "<<e.second<<endl;}
}int main(){test1();return 0;
}

测试结果:

在这里插入图片描述

五.完整代码文件

1.RBTree.h文件

#include<iostream>
#include<utility>
#include<vector>
#include<time.h>
using namespace std;enum Colour {RED,BLACK
};template<class T>
class RBTreeNode {
public://构造函数RBTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}//成员变量RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;
};//迭代器类封装
template<class T,class Ptr,class Ref>
class TreeIterator{//重命名定义typedef RBTreeNode<T> Node;  typedef TreeIterator<T,Ptr,Ref> self;typedef TreeIterator<T,T*,T&> Iterator;public:Node* _node;TreeIterator(Node* node):_node(node){}TreeIterator(const Iterator& it):_node(it._node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const self& s)const {return _node !=s._node;}bool operator==(const self& s)const {return _node==s._node;}self& operator++(){//如果该节点右子节点不为空,则到右子树中找最左节点if(_node->_right){Node* subleft=_node->_right;while(subleft->_left){subleft=subleft->_left;}_node=subleft;}//如果该节点右子节点为空,则找到该节点父节点的左子节点的祖先节点else{Node* cur=_node;Node* parent=cur->_parent;while(parent){//如果cur节点是父节点的右子节点,继续往上if(cur==parent->_right){       cur=parent;parent=parent->_parent;}//如果cur节点是父节点的左子节点,停止else{break;}}_node=parent;}return *this;}self& operator--(){//如果当前节点左子节点不为空,则到该节点的左子树中的最右节点if(_node->_left){Node* subright=_node->_left;while(subright->_right){subright=subright->_right;}_node=subright;}//如果该节点左子节点为空,就到该节点的祖先节点的右子节点else{Node* cur=_node;Node* parent=cur->_parent;while(parent){if(cur==parent->_left){cur=parent;parent=parent->_parent;}else{break;}}_node=parent;}return *this;}};template<class K , class T, class KeyOfT>
class RBTree {typedef RBTreeNode<T> Node;
public://普通对象迭代器typedef TreeIterator<T ,T* ,T&> iterator;//const对象迭代器typedef TreeIterator<T ,const T* ,const T&> const_iterator;//构造函数RBTree():_root(nullptr){}iterator begin(){Node* cur=_root;while(cur&&cur->_left){cur=cur->_left;}return cur;}iterator end(){return nullptr;}const_iterator begin()const {Node* cur=_root;while(cur&&cur->_left){cur=cur->_left;}return cur;}const_iterator end()const {return nullptr;}Node* Find(const K& key){Node* cur=_root;KeyOfT kot;while(cur){//这里参数key已经是K类型的,所以不用调用仿函数kot()if(kot(cur->_data)<key){                    cur=cur->_right;}else if(kot(cur->_data)>key){cur=cur->_left;}else{return cur;}}return nullptr;}pair<iterator,bool> insert(const T& data) {if(_root==nullptr){_root=new Node(data);_root->_col=BLACK;return make_pair(_root,true);}Node* parent=nullptr;Node* cur=_root;KeyOfT kot;while(cur) {//这里参数data是T类型的,是容器里存储的对象,不是K类型,所以要调用仿函数kot()获取key值if(kot(cur->_data)<kot(data)) {          parent=cur;cur=cur->_right;}else if(kot(cur->_data)>kot(data)) {parent=cur;cur=cur->_left;}else {return make_pair(cur,false);}}cur=new Node(data);cur->_col=RED;if(kot(parent->_data)<kot(data)){parent->_right=cur;}else{parent->_left=cur;}cur->_parent=parent;Node* newnode=cur;while(parent&&parent->_col==RED){Node* grandfather=parent->_parent;//如果parent节点在左子节点if(parent==grandfather->_left){Node* uncle=grandfather->_right;//如果uncle节点存在且节点为红色if(uncle&&uncle->_col==RED){//变色parent->_col=uncle->_col=BLACK;grandfather->_col=RED;//继续往上cur=grandfather;parent=cur->_parent;}//如果uncle节点不存在 或者 节点为黑色else{//如果cur节点在左子节点if(cur==parent->_left){//右单旋RotateR(grandfather);//旋转后变色grandfather->_col=RED;parent->_col=BLACK;}//如果cur节点在右子节点else{//左双旋//先左单旋,再右单旋RotateL(parent);RotateR(grandfather);//旋转后变色cur->_col=BLACK;grandfather->_col=RED;}break;}}//如果parent节点在右子节点else{Node* uncle=grandfather->_left;//如果uncle节点存在且节点为红色if(uncle&&uncle->_col==RED){//变色parent->_col=uncle->_col=BLACK;grandfather->_col=RED;//继续往上cur=grandfather;parent=cur->_parent;}//如果uncle节点不存在 后者 节点为黑色else{//如果cur节点在右子节点if(cur==parent->_right){//左单旋RotateL(grandfather);//变色parent->_col=BLACK;grandfather->_col=RED;}//如果cur节点在左子节点else{//右双旋//先右单旋,再左单旋RotateR(parent);RotateL(grandfather);//旋转后变色cur->_col=BLACK;grandfather->_col=RED;}break;}}}_root->_col=BLACK;return make_pair(newnode,true);}int Height(){return _Height(_root);}bool IsBlance(){return _IsBlance(_root);}private:int _Height(Node* root){if(root==nullptr){return 0;}int leftheight=_Height(root->_left);int rightheight=_Height(root->_right);return leftheight>rightheight ? leftheight+1 : rightheight+1;}bool CheckColour(Node* root,int blacknum,int benchmark){//如果节点是空,判断黑色节点个数是否等于基准值if(root==nullptr){if(blacknum!=benchmark){return false;}return true;}//如果节点是黑色,黑色个数加加if(root->_col==BLACK){blacknum++;}//如果节点是红色,判断父节点是否也是红色,不能出现连续的红色节点if(root->_col==RED&&root->_parent&&root->_parent->_col==RED){cout<<root->_kv.first<<"RED False"<<endl;return false;}return CheckColour(root->_left,blacknum,benchmark)&&CheckColour(root->_right,blacknum,benchmark);}bool _IsBlance(Node* root){if(root==nullptr){return true;}//如果根节点不是黑色,返回错误if(root->_col!=BLACK){return false;}//设置一个基准值int benchmark=0;Node* cur=root;while(cur){if(cur->_col==BLACK){benchmark++;}cur=cur->_left;}return CheckColour(root,0,benchmark);}//左单旋void RotateL(Node* parent){Node* cur=parent->_right;Node* curleft=cur->_left;Node* ppnode=parent->_parent;parent->_right=curleft;if(curleft){curleft->_parent=parent;}cur->_left=parent;parent->_parent=cur;if(ppnode){if(ppnode->_left==parent){ppnode->_left=cur;cur->_parent=ppnode;}else{ppnode->_right=cur;cur->_parent=ppnode;}}else{cur->_parent=nullptr;_root=cur;}}//右单旋void RotateR(Node* parent){Node* cur=parent->_left;Node* curright=cur->_right;Node* ppnode=parent->_parent;parent->_left=curright;if(curright){curright->_parent=parent;}cur->_right=parent;parent->_parent=cur;if(ppnode){if(ppnode->_left==parent){ppnode->_left=cur;cur->_parent=ppnode;}else{ppnode->_right=cur;cur->_parent=ppnode;}}else{cur->_parent=nullptr;_root=cur;}}private:Node* _root;
};

2.Set.h文件

#include"RBTree.h"namespace MySet{template<class K>class set{//内部类,用来获取set存储对象中的key值struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://这里的类模板还没有实例化对象,所以要加上关键字typenametypedef typename RBTree<K,K,SetKeyOfT>::const_iterator iterator;typedef typename RBTree<K,K,SetKeyOfT>::const_iterator const_iterator;const_iterator begin()const {return _t.begin();}const_iterator end()const {return _t.end();}//这里返回的是const_iterator类型的迭代器pair<iterator,bool> insert(const K& key){//插入返回的是iterator类型的迭代器pair<typename RBTree<K,K,SetKeyOfT>::iterator,bool> ret=_t.insert(key);return pair<iterator,bool>(ret.first,ret.second);}private://第一个模板参数k是set存储的数据类型RBTree<K,K,SetKeyOfT> _t;};
};

3.Map.h文件

#include"RBTree.h"namespace MyMap{template<class K,class V>class Map{struct MapKeyOfT{const K& operator()(const pair<const K,V>& kv){return kv.first;}};public:typedef typename RBTree<K,pair<const K,V>,MapKeyOfT>::iterator iterator;typedef typename RBTree<K,pair<const K,V>,MapKeyOfT>::const_iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin()const {return _t.begin();}const_iterator end()const{return _t.end();}//operator[],通过key值,返回value值,具备插入和修改V& operator[](const K& key){pair<iterator,bool> rit=insert(make_pair(key,V()));return rit.first->second;}pair<iterator,bool> insert(const pair<const K,V>& kv){return _t.insert(kv);}private:RBTree<K,pair<const K,V>,MapKeyOfT> _t;};
};

以上就是关于set和map的封装讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
在这里插入图片描述

相关文章:

【c++篇】:解读Set和Map的封装原理--编程中的数据结构优化秘籍

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;c篇–CSDN博客 文章目录 前言一.set和map的初步封装1.树的节点封装修改2.Find()查找函数3.红…...

ollama部署bge-m3,并实现与dify平台对接

概述 这几天为了写技术博客,各种组件可谓是装了卸,卸了装,只想复现一些东西,确保你们看到的东西都是可以复现的。 (看在我这么认真的份上,求个关注啊,拜托各位观众老爷了。) 这不,为了实验在windows上docker里运行pytorch,把docker重装了。 dify也得重装: Dify基…...

在并发情况下,Elasticsearch如果保证读写一致?

大家好&#xff0c;我是锋哥。今天分享关于【在并发情况下&#xff0c;Elasticsearch如果保证读写一致&#xff1f;】面试题。希望对大家有帮助&#xff1b; 在并发情况下&#xff0c;Elasticsearch如果保证读写一致&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java…...

AMD的AI芯片Instinct系列介绍

AMD最强AI芯片发布&#xff01; 在旧金山举行的Advancing AI 2024大会上&#xff0c;AMD推出Instinct MI325X AI加速器&#xff08;以下简称MI325X&#xff09;&#xff0c;直接与英伟达的Blackwell芯片正面交锋。 现场展示的数据显示&#xff0c;与英伟达H200的集成平台H200 …...

【知识科普】设计模式之-责任链模式

这里写自定义目录标题 概述责任链模式的详细描述责任链模式的使用场景 使用场景举例1. 审批流程示例&#xff1a;2. 过滤器链示例&#xff1a;3. 事件处理系统示例&#xff1a;4. 插件系统示例&#xff1a; Java代码示例及注释代码解释 概述 责任链模式的详细描述 责任链模式…...

fiddler安卓雷电模拟器配置踩坑篇

一、fiddler端配置 和网页版fiddler一样&#xff0c;需要首先再本机安装证书&#xff0c;可以参考我之前的fiddler浏览器配置文章&#xff0c;前期操作一致&#xff1a; 此处需要注意的是connections里面需要勾选allow remote这个选项&#xff0c;这个主要是为了后来再安卓模拟…...

机器学习5-多元线性回归

多元线性回归 主要了解多元线性回归的原理以及数学推导。 只有损失函数是凸函数才能确认是最优解&#xff0c;极值不一定是最优解 判定凸函数的方式非常多&#xff0c;其中一个方法是看黑塞矩阵是否是半正定的。 黑塞矩阵&#xff08;hessian matrix&#xff09;是由目标函数在…...

Linux kernel 堆溢出利用方法(三)

前言 本文我们通过我们的老朋友heap_bof来讲解Linux kernel中任意地址申请的其中一种比赛比较常用的利用手法modprobe_path&#xff08;虽然在高版本内核已经不可用了但ctf比赛还是比较常用的&#xff09;。在通过两道道近期比赛的赛题来讲解。 Arbitrary Address Allocation…...

对于GC方面,在使用Elasticsearch时要注意什么?

大家好&#xff0c;我是锋哥。今天分享关于【对于GC方面&#xff0c;在使用Elasticsearch时要注意什么&#xff1f;】面试题。希望对大家有帮助&#xff1b; 对于GC方面&#xff0c;在使用Elasticsearch时要注意什么&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java…...

Xilinx PCIe高速接口入门实战(一)

引言&#xff1a;本文对Xilinx 7 Series Intergrated Block for PCI Express PCIe硬核IP进行简要介绍&#xff0c;主要包括7系列FPGA PCIe硬核资源支持、三IP硬核差异、PCIe硬核资源利用等相关内容。 1. 概述 1.1 7系列FPGA PCIe硬件资源支持 7系列FPGA对PCIe接口最大支持如…...

Flume 监控配置和实践

要解释 Flume 的监控机制&#xff0c;需要了解 Flume 是如何设计其监控架构的&#xff0c;以及如何将性能指标暴露给用户或集成工具。下面我将详细分解 Flume 的监控机制&#xff0c;从基础架构、实现原理到源码解析&#xff0c;并提供非专业人也能理解的通俗解释。 Flume 的监…...

深度学习基础1

目录 1. 深度学习的定义 2.神经网络 2.1. 感知神经网络 2.2 人工神经元 2.2.1 构建人工神经元 2.2.2 组成部分 2.2.3 数学表示 2.2.4 对比生物神经元 2.3 深入神经网络 2.3.1 基本结构 2.3.2 网络构建 2.3.3 全连接神经网络 3.神经网络的参数初始化 3.1 固定值初…...

《FPGA开发工具》专栏目录

《FPGA开发工具》专栏目录 1.Vivado开发 1.1使用相关 Vivado工程创建、仿真、下载与固化全流程 Vivado工程快速查看软件版本与器件型号 Vivado IP核的快速入门 官方手册和例程 Vivado中对已调用IP核的重命名 Vivado中增加源文件界面中各选项的解释 Vivado IP中Generate…...

李春葆《数据结构》-查找-课后习题代码题

一&#xff1a;设计一个折半查找算法&#xff0c;求查找到关键字为 k 的记录所需关键字的比较次数。假设 k 与 R[i].key 的比较得到 3 种情况&#xff0c;即 kR[i].key&#xff0c;k<R[i].key 或者 k>R[i].key&#xff0c;计为 1 次比较&#xff08;在教材中讨论关键字比…...

【Git】:分支管理

目录 理解分支 创建分支 切换分支 合并分支 删除分支 合并冲突 分支管理策略 快进合并 正常合并 bug 分支 总结 理解分支 在版本控制系统中&#xff0c;分支是一条独立的开发线路。它允许开发者从一个主要的代码基线&#xff08;例如master分支&#xff09;分离出来…...

C、C++ 和 Java的区别

C、C 和 Java 是三种广泛使用的编程语言&#xff0c;它们各有特点&#xff0c;适合不同的应用场景。以下从多个角度对它们的区别进行分析&#xff1a; 基础特性 特性CCJava语言类型过程式编程语言过程式 面向对象编程语言纯面向对象编程语言&#xff08;也支持过程式&#x…...

【Python-Open3D学习笔记】005Mesh相关方法

TriangleMesh相关方法 文章目录 TriangleMesh相关方法1. 查看mesh三角形面信息2. 可视化三角形3. 上采样4. 计算mesh形成的面积和体积 1. 查看mesh三角形面信息 def view_hull_triangles(hull: o3d.geometry.TriangleMesh):"""查看mesh三角形面信息&#xff08…...

js原型、原型链和继承

文章目录 一、原型1、prototype2、constructor 二、原型链1、字面量原型链2、字面量继承3、构造函数的原型链4、Object.create5、Object.setPrototypeOf 三、继承1、构造函数继承2、原型链继承3、组合继承 四、常见链条1、Function2、Object.prototype 继承是指将特性从父代传递…...

团队自创【国王的魔镜-2】

国王的魔镜-2 题目描述 国王有一个魔镜&#xff0c;可以把任何接触镜面的东西变成原来的两倍——只是&#xff0c;因为是镜子嘛&#xff0c;增加的那部分是反的。比如一条项链&#xff0c;我们用AB来表示&#xff0c;不同的字母表示不同颜色的珍珠。如果把B端接触镜面的话&am…...

c++编程玩转物联网:使用芯片控制8个LED实现流水灯技术分享

在嵌入式系统中&#xff0c;有限的GPIO引脚往往限制了硬件扩展能力。74HC595N芯片是一种常用的移位寄存器&#xff0c;通过串行输入和并行输出扩展GPIO数量。本项目利用树莓派Pico开发板与74HC595N芯片&#xff0c;驱动8个LED实现流水灯效果。本文详细解析项目硬件连接、代码实…...

【Jenkins】docker 部署 Jenkins 踩坑笔记

文章目录 1. docker pull 超时2. 初始化找不到 initialAdminPassword 1. docker pull 超时 docker pull 命令拉不下来 docker pull jenkins/jenkins:lts-jdk17 Error response from daemon: Get "https://registry-1.docker.io/v2/": 编辑docker配置 sudo mkdir -…...

Unreal Engine使用Groom 打包后报错

Unreal Engine使用Groom打包后报错 版本5.4.4 blender 4.2.1 项目头发用了groom&#xff0c;运行后报错 错误&#xff1a; Assertion failed: Offset BytesToRead < UncompressedFileSize && Offset > 0 [File:E:\UnrealEngine-5.4.4-release\Engine\Source\R…...

嵌入式QT学习第3天:UI设计器的简单使用

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 Qt Creator 里自带的 Qt Designer&#xff0c;使用 Qt Designer 比较方便的构造 UI 界 面。 在 UI 文件添加一个按钮 左边找到 Push Button&#xff0c;然后拖拽到中…...

【连接池】.NET开源 ORM 框架 SqlSugar 系列

.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…...

图论入门编程

卡码网刷题链接&#xff1a;98. 所有可达路径 一、题目简述 二、编程demo 方法①邻接矩阵 from collections import defaultdict #简历邻接矩阵 def build_graph(): n, m map(int,input().split()) graph [[0 for _ in range(n1)] for _ in range(n1)]for _ in range(m): …...

在Java中使用Apache POI导入导出Excel(三)

本文将继续介绍POI的使用&#xff0c;上接在Java中使用Apache POI导入导出Excel&#xff08;二&#xff09; 使用Apache POI组件操作Excel&#xff08;三&#xff09; 24、拆分和冻结窗格 您可以创建两种类型的窗格;冻结窗格和拆分窗格。 冻结窗格按列和行进行拆分。您创建…...

UR开始打中国牌,重磅发布国产化协作机器人UR7e 和 UR12e

近日&#xff0c;优傲&#xff08;UR&#xff09;机器人公司立足中国市场需求&#xff0c;重磅推出UR7e和UR12e 两款本地化协作机器人。它们延续优傲&#xff08;UR&#xff09;一以贯之的高品质与性能特质&#xff0c;着重优化负载自重比&#xff0c;且在价格层面具竞争力&…...

FRU文件

FRU&#xff08;Field Replaceable Unit&#xff09;源文件的格式通常遵循IPMI FRU Information Storage Definition标准。在实际应用中&#xff0c;FRU源文件可以是JSON格式的&#xff0c;这种格式允许用户指定所有的FRU信息字段。以下是FRU源文件的JSON格式的一些关键点&…...

AI需求条目化全面升级!支持多格式需求,打破模板限制!

AI需求条目化全面升级&#xff01;支持多格式需求&#xff0c;打破模板限制&#xff01; 一、多格兼济 标准立成 1、功能揭秘 预览未来 平台需求板块的AI需求条目化功能迎来全面升级。它支持多种需求格式&#xff0c;不再受限于模板文件&#xff0c;能够一键自动快速且灵活地生…...

Java—I/O流

Java的I/O流&#xff08;输入/输出流&#xff09;是用于在程序和外部资源&#xff08;如文件、网络连接等&#xff09;之间进行数据交换的机制。通过I/O流&#xff0c;可以实现从外部资源读取数据&#xff08;输入流&#xff09;或将数据写入外部资源&#xff08;输出流&#x…...

遵义市网站制作/平台推广费用

TinyXML是一个开源的解析XML的解析库&#xff0c;能够用于C&#xff0c;能够在Windows或Linux中编译。这个解析库的模型通过解析XML文件&#xff0c;然后在内存中生成DOM模型&#xff0c;从而让我们很方便的遍历这棵XML树。简单易用且小巧 玲珑&#xff0c;非常适合存储简单数据…...

做外文翻译的网站/情感营销经典案例

二叉树是一种重要的数据结构&#xff0c;初学的我们先要了解如何建立一个二叉树&#xff0c;以及如何去遍历这个二叉树。 ①二叉树的概念和建立 “二叉树”极其类似于变相的链表&#xff0c;只是&#xff0c;其中的每个节点需要存放两个指针&#xff1a;“左支指针left”和“右…...

怎么自己做网站凑钱/关于seo如何优化

自动化部署流程1.获取代码(直接拉取)2.编译(可选)3.配置文件4.打包5.SCP到目标服务器6.将目标服务器移除集群7.解压8.放置到webroot9.SCP差异文件10.重启(可选)11.测试12.加入集群上线回滚流程#正常回滚1.列出回滚版本2.目标服务移除集群3.执行回滚4.重启和测试5.加入集群#紧急…...

去菲律宾做网站/重庆seo快速优化

文档来源为:从网络收集整理.word版本可编辑.欢迎下载支持.1文档来源为:从网络收集整理.word版本可编辑.NICE3000调试说明书V 1.00目录1.调试工具使用说明………………………………………………………41.1调试工具……………………………………………………………………………41…...

wordpress主题使用/网络营销课程实训总结

{dede:sql runphpyes sqlSelect content from dede_arctype where id10}[field:content function"cn_substr(me,180)"/] {/dede:sql}使用cn_substr函数即可实现截串。Html2text()函数是去掉html标签代码。参考代码&#xff1a;转载于:https://blog.51cto.com/okowo/1…...

网站备案需要年检吗/新闻稿发布

写在前面今天带给大家一个突破点选验证码的案例&#xff0c;利用爬虫模拟登陆哔哩哔哩&#xff0c;并且把一些采坑的地方给大家强调一下&#xff01;一、需求分析模拟登陆哔哩哔哩网站链接: https://passport.bilibili.com/login效果图如下&#xff1a;验证成功后会自动跳转B站…...