【C++】Map、Set 模拟实现

news/2024/12/29 19:00:45/

文章目录

  • 📕 概念
  • 📕 实现
    • 框架
    • Find()
    • ★ 迭代器 ★
    • 反向迭代器
    • map 的 operator[ ]
  • 📕 源代码
    • rb_tree.h
    • set.h
    • map.h

📕 概念

map、set 是 C++ 中的关联式容器,由于 map 和set所开放的各种操作接口,RB-tree 也都提供了,所以几乎所有的 map 和set的操作行为,都只是转调 红黑树 的操作行为。

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。

📕 实现

框架

map、set 一个很明显的区别就是,set 存储值,map存储键值对

set 的结构如下,只需要传入一个模板参数 K ,也就是其实际存储的值的数据类型。复用红黑树的时候,第二个参数就是 K 类型。

#pragma once
#include"rb_tree.h"namespace SetSimulate
{template<class K>class set{struct KeyOfT{const K& operator()(const K& k){return k;}};public:typedef typename RBTree<K, K, KeyOfT>::const_iterator iterator;typedef typename RBTree<K, K, KeyOfT>::const_iterator const_iterator;private:RBTree<K, K, KeyOfT> _t;};
}

而 Map 就不一样了,要传入两个参数,第一个是 K(键值),第二个是 V (映射值)。实际存储的数据类型是 pair<K,V> 。如下,复用红黑树的时候,将其第二个模板参数设为 pair<K,V> 即可。

#pragma once
#include"rb_tree.h"namespace MapSimulate
{template<class K, class V>class map{struct MapOfKey{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<const K,V>, MapOfKey>::iterator iterator;typedef typename RBTree<K, pair<const K,V>, MapOfKey>::const_iterator const_iterator;typedef typename RBTree<K, pair<const K, V>, MapOfKey>::reverse_iterator reverse_iterator;typedef typename RBTree<K, pair<const K, V>, MapOfKey>::const_reverse_iterator const_reverse_iterator;private:RBTree<K, pair<const K, V>, MapOfKey> _t;};
}

关于 map、set 底层红黑树的第三个模板参数,分析上面的两段代码,实际上是取存储数据的 K 值。即,在 map 里面,由于存储的数据类型是 pair<K,V> ,所以 MapOfKey 的作用是取到键值对里面 K 类型的数据;而 set 里面,由于存储的数据本身就是 K 类型,所以 SetOfKey 直接取到该类型的数据

至于为什么要这样,这是因为,map、set 是对红黑树的封装,如果仅仅实现 set ,可以不用给红黑树传入第三个模板参数,因为 set 里面存储 K 类型的数据,而红黑树插入、查找、删除等等操作里面,数据之间进行比较,本就是比较的 K 类型的数据,所以是一致的。
但是,map 存储的是 pair<K,V> ,由于map、set 是复用同一份红黑树代码,所以无法将代码直接改成适应 pair<K,V> 的情况(这样就不适应 set 了)。所以,需要有一个统一的视角,来得到map、set 的数据里面的 K 值。

当然,上面两端说起来比较抽象,接下来介绍一下 Find 封装,就不难理解了。

Find()

如下是红黑树底层实现 find 的代码。可以看到它在进行数据之间的比较时,是依靠 KeyOfT 实例化出的对象 kt ,取 kt(cur->_data)
很显然,当容器无论为 set 还是 map, 红黑树中 find() 函数都依靠 K 类型的 val 来查找。可是, 红黑树的节点中, _data 存储的数据类型却不一样,map 中存储的是 pair<K,V>,set 中存储的是 K。

所以,这里就依靠红黑树的第三个模板参数 KeyOfT 实例化的对象 kt,其重载的 () 来取到 map、set 的 _data 里面的 K 值。这样就统一了。

	template<class T>struct RBTreeNode{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& val): _left(nullptr), _right(nullptr), _parent(nullptr), _data(val), _col(RED){}};template<class K, class T, class KeyOfT>class RBTree{typedef struct RBTreeNode<T> Node;public:Node* find(const K& val){KeyOfT kt;Node* cur = _root;while (cur){if (val > kt(cur->_data)){cur = cur->_right;}else if (val < kt(cur->_data)){cur = cur->_left;}elsereturn cur;}return nullptr;}private:Node* _root;};

如下,无论是 map 还是 set,都是直接调用红黑树的 find() ,然后封装成迭代器返回。

iterator Find(const K& key){Node* cur = _t.find(key);return iterator(cur);}

★ 迭代器 ★

如下,是迭代器的大体框架,主要依靠红黑树的节点指针来实现。

重载 * 、->、!= 都比较简单,和 list 封装的迭代器类似。但是 ,重载 ++ 和 – 就需要一些独特的思路了!

template<class T,class Ref,class Ptr>struct _RBTreeIterator{typedef RBTreeNode<T> Node;typedef _RBTreeIterator<T, Ref, Ptr> self;_RBTreeIterator(Node* node):_node(node){}// begin() 、 end() 处可见其效果_RBTreeIterator(const _RBTreeIterator<T, T&, T*> &it):_node(it._node){}Ref operator*(){return _node->_data;}bool operator!=(const self& s){return this->_node != s._node;}Ptr operator->(){return &_node->_data;}self& operator++(){// 实现return *this;}self& operator--(){// 实现return *this;}Node* _node;};

operator++()

对红黑树的遍历,和搜索二叉树是一样的,中序遍历
例如,现在某棵红黑树存储了1到10 的数据,使用迭代器遍历的结果必然是 1、2、3、4、5、6、7、8、9、10。
假设现在将存储 5 这个数据的节点,封装成迭代器 it。进行 ++it 操作之后,无疑 it 内部的节点指针,指向的是数据为 6 的节点。再 ++it,it 内部的结点指针就指向数据为 7 的节点。
很明显,++ 操作,只需要考虑右边部分的结果(上面依次++it,实际上节点指针依次指向 6、7、8、9、10 ),不需要考虑 左边的部分(即 1-5 )。

这样就很清晰了,对于一个中序遍历的结果,对于某一个节点 X 的右边部分,分为两种情况:有数据是 X 的子节点(可能是部分,可能是全部);没有数据是 X 的子节点。例如上面 5 这个结果,其右边部分6到10 里面,可能有数据是5的子节点,也可能没有。

根据这两种情况来写代码。

  • 对于第一种情况——右边部分存在 X 的子节点。这就很容易了,由于是中序遍历,当前节点遍历完之后,就需要遍历其右子树。所以 ++ 操作,只要找到其右子树的最小节点即可

  • 对于第二种情况,X 不存在右子树,那么必然是要往上找。如下图,假设一开始是图中左边的状态, 现在有 cur 、p 两个节点指针,而 cur 没有右子树,向上寻找, cur 在 p 的右边,这说明 p 已经遍历过了。
    再继续往上找,到了下图右边的状态,此时 cur 在 p 的左边,根据中序遍历规则,下一个要遍历的节点就是 此时的 p

请添加图片描述

self& operator++(){// 右子树存在,先去右子树的第一个if (_node->_right){Node* subleft=_node->_right;while (subleft->_left){subleft = subleft->_left;}_node = subleft;}else // 右子树不存在,向上找{Node* cur = _node;Node* parent = _node->_parent;while (parent && parent->_right == cur){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}

operator–()

而 – 就是和 ++ 反过来了,遍历顺序是:右子树、根、左子树。所以,拿到某个节点 X,要先去看左子树是否存在,不存在则往上找。

  • 左子树存在,自然是去寻找左子树中最大的节点
  • 左子树不存在,往上找。如下图,假设当前迭代器封装的是 cur 节点,不存在左子树。 – 操作就应该向上找,在下图左边的状态时, cur 节点是 p节点的左孩子,根据 – 的遍历顺序,p 已经遍历过了。
    继续往上,到了下图右边的状态, cur 是 p 的右孩子,根据 - - 的遍历顺序,下一个节点就是 p

请添加图片描述

self& operator--(){if (_node->_left){Node* subright = _node->_left;while (subright->_right){subright = subright->_right;}_node = subright;}else // 当前节点没有左子树,直接向上找,直到 parent 的左子节点是 cur{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left == cur){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}

反向迭代器

反向迭代器主要是对正向迭代器的封装。

template<class T, class Ref, class Ptr>struct _RBTreeReverseIterator{typedef RBTreeNode<T> Node;typedef _RBTreeReverseIterator<T, Ref, Ptr> self;_RBTreeReverseIterator(Node* node){_it._node = node;}_RBTreeReverseIterator(const _RBTreeIterator<T, T&, T*>& it){_it._node = it._node;}_RBTreeReverseIterator(const _RBTreeReverseIterator<T, T&, T*>& it){_it._node = it._it._node;}self& operator++(){--_it;return *this;}self& operator--(){++_it;return *this;}bool operator!=(const self& s){return _it._node != s._it._node;}Ptr operator->(){return &_it._node->_data;}Ref operator*(){return _it._node->_data;}struct _RBTreeIterator<T,Ref,Ptr> _it=nullptr;};

在红黑树中实现了 迭代器,接下来就是封装到 map、set 里面。与此同时,还需要红黑树中提供 begin() 、end() 、rbegin() 、rend() 方法。

如下,是在红黑树中实现这些方法,返回的结果是迭代器。

template<class K, class T, class KeyOfT>class RBTree{typedef struct RBTreeNode<T> Node;public:typedef _RBTreeIterator<T, T&, T*> iterator;typedef _RBTreeIterator<T, const T&, const T*> const_iterator;typedef _RBTreeReverseIterator<T, T&, T*> reverse_iterator;typedef _RBTreeReverseIterator<T, const T&, const T*> const_reverse_iterator;public:iterator begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}const_iterator begin() const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_iterator(cur);}const_iterator end() const{return const_iterator(nullptr);}reverse_iterator rbegin(){Node* cur = _root;while (cur && cur->_right){cur = cur->_right;}return reverse_iterator(cur);}reverse_iterator rend(){return reverse_iterator(nullptr);}const_reverse_iterator rbegin() const{Node* cur = _root;while (cur && cur->_right){cur = cur->_right;}return const_reverse_iterator(cur);}const_reverse_iterator rend() const{return const_reverse_iterator(nullptr);}private:Node* _root;};

如下是在 set 里面实现 begin() 、end() ,完全是对 红黑树方法的复用。

值得注意的是, typedef typename RBTree<K, K, KeyOfT>::const_iterator iterator; 这是为了保证 set 的 K 不可修改。

#pragma once
#include"rb_tree.h"namespace SetSimulate
{template<class K>class set{struct KeyOfT{const K& operator()(const K& k){return k;}};public:typedef typename RBTreeNode<K> Node;typedef typename RBTree<K, K, KeyOfT>::const_iterator iterator;typedef typename RBTree<K, K, KeyOfT>::const_iterator const_iterator;typedef typename RBTree<K, K, KeyOfT>::const_reverse_iterator reverse_iterator;typedef typename RBTree<K, K, KeyOfT>::const_reverse_iterator const_reverse_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();}reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}const_reverse_iterator rbegin()const{return _t.rbegin();}const_reverse_iterator rend()const{return _t.rend();}iterator Find(const K& key){Node* cur = _t.find(key);return iterator(cur);}private:RBTree<K, K, KeyOfT> _t;};
}

map 的 operator[ ]

首先,map 的 [ ] 是兼具插入和查找功能的。map 里面存储的是 pair<K,V> ,假设有一个map< int,int >对象 m,执行 m[10]=1000; —— 如果存储的 pair<K,V> 里面,有 K 类型的数据是 10,那么就将这个键值对的 V 修改成 100;没有则插入 make_pair(10,100);

要完成上面描述的功能,就必须要让 [ ] 的返回值是 V& (V的引用),这样才可以修改 V 类型的数据。 可以通过迭代器来实现!!

如下是 map 里面实现 operator[ ] 的代码,_t 是红黑树实例化出来的对象,其 Insert() 返回值是 pair<iterator,bool> , 很明显,返回值包含两个信息:

  1. 某个节点构成的迭代器。如果插入成功,那么返回插入节点封装成的迭代器。如果插入失败(已经有了 K 类型对应的数据),那么就相当于查找功能,返回找到的该节点封装成的迭代器
  2. 是否插入成功,插入成功返回 true,否则false。
		V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));return ret.first->second;}

那么就可以对红黑树底层的 Insert() 进行修改,只需要修改函数的返回值类型,以及 return 的时候,构造一个 make_pair<iterator,bool> 类型的数据进行返回即可。

📕 源代码

rb_tree.h

#pragma once
#include<iostream>
#include<cassert>using namespace std;enum Colour{RED,BLACK,};template<class T>struct RBTreeNode{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& val): _left(nullptr), _right(nullptr), _parent(nullptr), _data(val), _col(RED){}};template<class T,class Ref,class Ptr>struct _RBTreeIterator{typedef RBTreeNode<T> Node;typedef _RBTreeIterator<T, Ref, Ptr> self;_RBTreeIterator(Node* node):_node(node){}_RBTreeIterator(const _RBTreeIterator<T, T&, T*> &it):_node(it._node){}Ref operator*(){return _node->_data;}bool operator!=(const self& s){return this->_node != s._node;}Ptr operator->(){return &_node->_data;}self& operator++(){// 右子树存在,先去右子树的第一个if (_node->_right){Node* subleft=_node->_right;while (subleft->_left){subleft = subleft->_left;}_node = subleft;}else // 右子树不存在,向上找{Node* cur = _node;Node* parent = _node->_parent;while (parent && parent->_right == cur){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}self& operator--(){if (_node->_left){Node* subright = _node->_left;while (subright->_right){subright = subright->_right;}_node = subright;}else // 当前节点没有左子树,直接向上找,直到 parent 的左子节点是 cur{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left == cur){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}Node* _node;};template<class T, class Ref, class Ptr>struct _RBTreeReverseIterator{typedef RBTreeNode<T> Node;typedef _RBTreeReverseIterator<T, Ref, Ptr> self;_RBTreeReverseIterator(Node* node){_it._node = node;}_RBTreeReverseIterator(const _RBTreeIterator<T, T&, T*>& it){_it._node = it._node;}_RBTreeReverseIterator(const _RBTreeReverseIterator<T, T&, T*>& it){_it._node = it._it._node;}self& operator++(){--_it;return *this;}self& operator--(){++_it;return *this;}bool operator!=(const self& s){return _it._node != s._it._node;}Ptr operator->(){return &_it._node->_data;}Ref operator*(){return _it._node->_data;}struct _RBTreeIterator<T,Ref,Ptr> _it=nullptr;};template<class K, class T, class KeyOfT>class RBTree{typedef struct RBTreeNode<T> Node;public:typedef _RBTreeIterator<T, T&, T*> iterator;typedef _RBTreeIterator<T, const T&, const T*> const_iterator;typedef _RBTreeReverseIterator<T, T&, T*> reverse_iterator;typedef _RBTreeReverseIterator<T, const T&, const T*> const_reverse_iterator;public:iterator begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}const_iterator begin() const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_iterator(cur);}const_iterator end() const{return const_iterator(nullptr);}reverse_iterator rbegin(){Node* cur = _root;while (cur && cur->_right){cur = cur->_right;}return reverse_iterator(cur);}reverse_iterator rend(){return reverse_iterator(nullptr);}const_reverse_iterator rbegin() const{Node* cur = _root;while (cur && cur->_right){cur = cur->_right;}return const_reverse_iterator(cur);}const_reverse_iterator rend() const{return const_reverse_iterator(nullptr);}~RBTree(){Delete(_root);_root = nullptr;}Node* find(const K& val){KeyOfT kt;Node* cur = _root;while (cur){if (val > kt(cur->_data)){cur = cur->_right;}else if (val < kt(cur->_data)){cur = cur->_left;}elsereturn cur;}return nullptr;}pair<iterator,bool> Insert(const T& val){if (_root == nullptr){_root = new Node(val);_root->_col = BLACK;return make_pair(iterator(_root),true);}KeyOfT kt;Node* parent = nullptr;Node* cur = _root;while (cur) // 找到要插入的位置{if (kt(cur->_data) > kt(val)){parent = cur;cur = cur->_left;}else if (kt(cur->_data) < kt(val)){parent = cur;cur = cur->_right;}elsereturn make_pair(iterator(cur), false);}// 插入cur = new Node(val);Node* newnode = cur;if (kt(parent->_data) < kt(val)){parent->_right = cur;}else if (kt(parent->_data) > kt(val)){parent->_left = cur;}cur->_parent = parent;// 调整颜色while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED) // uncle 为红色{parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续调整cur = grandfather;parent = cur->_parent;}else // uncle不存在 or 存在且为黑{if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;//cur->_col = RED;}else if (cur == parent->_right){RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;parent->_col = RED;}break;}}else // if (parent == grandfather->_right){Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED) // uncle 为红色{parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续调整cur = grandfather;parent = cur->_parent;}else // uncle不存在 or 存在且为黑{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;//cur->_col = RED;}else // cur == parent->_left{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;parent->_col = RED;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(iterator(newnode), true);}int Height(){return _Height(_root);}bool isBalance(){if (_root->_col == RED){cout << "根节点的颜色是红色" << endl;return false;}int benchmark = 0;Node* tmp = _root;while (tmp){if (tmp->_col == BLACK)benchmark++;tmp = tmp->_left;}return _isBalance(_root, 0, benchmark);}void Inorder(){_Inorder(_root);}private:void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << " ";_Inorder(root->_right);}bool _isBalance(Node* root, int blackNum, int benchmark){if (root == nullptr){if (blackNum == benchmark)return true;else{cout << "某条链路黑色节点数目错误!" << endl;return false;}}if (root->_col == BLACK)++blackNum;else{if (root->_parent && root->_parent->_col == RED){cout << "存在两个连续的红色节点" << endl;return false;}}return _isBalance(root->_left, blackNum, benchmark)&& _isBalance(root->_right, blackNum, benchmark);}int _Height(Node* root){if (root == nullptr) return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}void Delete(Node* root){if (root == nullptr)return;Delete(root->_left);Delete(root->_right);free(root);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* pparent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (pparent == nullptr){_root = subR;_root->_parent = nullptr;}else{if (parent == pparent->_left){pparent->_left = subR;}else{pparent->_right = subR;}subR->_parent = pparent;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;subL->_right = parent;parent->_parent = subL;parent->_left = subLR;if (subLR)subLR->_parent = parent;if (pparent == nullptr){subL->_parent = nullptr;_root = subL;}else{if (parent == pparent->_left){pparent->_left = subL;}else{pparent->_right = subL;}subL->_parent = pparent;}}private:Node* _root;};

set.h

#pragma once
#include"rb_tree.h"namespace SetSimulate
{template<class K>class set{struct KeyOfT{const K& operator()(const K& k){return k;}};public:typedef typename RBTreeNode<K> Node;typedef typename RBTree<K, K, KeyOfT>::const_iterator iterator;typedef typename RBTree<K, K, KeyOfT>::const_iterator const_iterator;typedef typename RBTree<K, K, KeyOfT>::const_reverse_iterator reverse_iterator;typedef typename RBTree<K, K, KeyOfT>::const_reverse_iterator const_reverse_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();}reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}const_reverse_iterator rbegin()const{return _t.rbegin();}const_reverse_iterator rend()const{return _t.rend();}pair<iterator,bool> insert(const K& k){return _t.Insert(k);}iterator Find(const K& key){Node* cur = _t.find(key);return iterator(cur);}private:RBTree<K, K, KeyOfT> _t;};
}

map.h

#pragma once
#include"rb_tree.h"namespace MapSimulate
{template<class K, class V>class map{struct MapOfKey{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:typedef typename RBTreeNode<pair<const K, V>> Node;typedef typename RBTree<K, pair<const K,V>, MapOfKey>::iterator iterator;typedef typename RBTree<K, pair<const K,V>, MapOfKey>::const_iterator const_iterator;typedef typename RBTree<K, pair<const K, V>, MapOfKey>::reverse_iterator reverse_iterator;typedef typename RBTree<K, pair<const K, V>, MapOfKey>::const_reverse_iterator const_reverse_iterator;pair<iterator,bool> insert(const pair<const K, V>& kv){return _t.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));return ret.first->second;}iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin()const{return _t.begin();}const_iterator end()const{return _t.end();}reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}const_reverse_iterator rbegin()const{return _t.rbegin();}const_reverse_iterator rend()const{return _t.rend();}iterator Find(const K& key){Node* cur = _t.find(key);return iterator(cur);}private:RBTree<K, pair<const K, V>, MapOfKey> _t;};
}

http://www.ppmy.cn/news/98593.html

相关文章

SpringBoot2-核心技术(一)

SpringBoot2-核心技术&#xff08;一&#xff09; 了解SpringBoot配置文件的使用 文章目录 SpringBoot2-核心技术&#xff08;一&#xff09;了解SpringBoot配置文件的使用一、文件类型1. properties2. yaml 二、yaml的基本使用1. 基本语法2. 数据类型2.1 字面量 2.2 对象2.3 …

HashMap,HashTable,ConcurrentHashMap的区别

a、HashMap是非线程安全的&#xff0c;HashTable是线程安全的。   b、HashMap的键和值都允许有null值存在&#xff0c;而HashTable则不行。   c、因为线程安全的问题&#xff0c;HashMap效率比HashTable的要高。   HashMap&#xff1a;它根据键的hashCode值存储数据&a…

HCIA-MSTP替代技术之设备堆叠

1.什么是堆叠、集群 堆叠&#xff1a;将多个具有堆叠特性的交换设备逻辑上变成一台设备&#xff0c;作为一个整体参与转发 集群&#xff1a;将两个具有集群特性的交换设备组合为逻辑上的一台设备。 集群支持两台设备&#xff08;css&#xff09;&#xff0c;一般框式交换机支…

k8s介绍

目录 1&#xff1a;k8s概念 2&#xff1a;为什么引入k8s和k8s特性 2.1 为什么要引入k8s&#xff1a; 2.2 k8s特性 3 K8S架构 1&#xff1a;k8s概念 k8s官方网站&#xff1a;Kubernetes Kubernetes 是一个可移植、可扩展的开源平台&#xff0c;用于管理容器化的工作负载和…

网页端扫码通过公众号实现微信授权登录

1.参考开发文档&#xff1a; https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#02.先调起微信授权页面&#xff0c;获取code。&#xff08;如果用户同意授权&#xff0c;页面将跳转至 redirect_uri/?codeCODE&stateSTAT…

【五】设计模式~~~创建型模式~~~单例模式(Java)

【学习难度&#xff1a;★☆☆☆☆&#xff0c;使用频率&#xff1a;★★★★☆】 5.1. 模式动机 对于系统中的某些类来说&#xff0c;只有一个实例很重要&#xff0c;例如&#xff0c;一个系统中可以存在多个打印任务&#xff0c;但是只能有一个正在工作的任务&#xff1b;一…

五种IO模型

本文分享的是理解五种IO模型的基本概念, 重点是IO多路转接。 什么是IO 其实我们在读写数据的时候&#xff0c;比如使用了write、read、recv、send等等函数&#xff0c;本质是对数据的拷贝。即在写的时候&#xff0c;将数据拷贝到了TCP协议的发送缓冲区中。在读的时候&#xff…

Linux|奇怪的知识|一次性任务at命令的使用

前言&#xff1a; at命令是Linux的一个专有命令&#xff0c;该命令是旧的计划任务atd服务的客户端命令&#xff08;at命令是c/s形式的软件套件里的client&#xff0c;客户端&#xff09;&#xff0c;主要的用处就是灵活制定一个工作计划&#xff0c;特定时间自动完成你所设定的…