当前位置: 首页 > 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实现流水灯效果。本文详细解析项目硬件连接、代码实…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

工程地质软件市场:发展现状、趋势与策略建议

一、引言 在工程建设领域&#xff0c;准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具&#xff0c;正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

vulnyx Blogger writeup

信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面&#xff0c;gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress&#xff0c;说明目标所使用的cms是wordpress&#xff0c;访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...