【数据结构高阶】B-树

目录

一、常见的搜索结构

二、B树

2.1 B树的概念

2.2 B树插入数据的分析

2.3 B树的性能分析

2.4 模拟实现B树

2.4.1 B树节点的定义

2.4.2 B树数据的查找

2.4.3 B树节点的数据插入

2.4.4 B树的遍历

2.4.5 模拟实现B树实现的完整代码

三、B+树

3.1 B+树的概念

3.2 B+树插入数据的分析

四、B*树

五、B树系列总结

六、B-树的应用

6.1 索引

6.2 MySQL索引简介


一、常见的搜索结构

在正式介绍B树之前的我们先来看一下常用到的搜索结构:

种类数据格式时间复杂度
顺序查找无要求O(N)
二分查找有序O(㏒⑵N)
二叉搜索树无要求O(N)
二叉平衡树(AVL树&红黑树)无要求O(㏒⑵N)
哈希无要求O(1)

以上结构适合用于数据量相对不是很大,能够一次性存放在内存中,进行数据查找的场景。如果 数据量很大,比如有100G数据,无法一次放进内存中,那就只能放在磁盘上了。

如果放在磁盘上,有需要搜索某些数据,那么如果处理呢?那么我们可以考虑将存放关键字及其映射的数据的地址放到一个内存中的搜索树的节点中,那么要访问数据时,先取这个地址去磁盘访问数据。

但是这样的存储方式一旦数据量较大,内存很想要存储全部的数据是很难的;如果我们为了节省空间只存外存地址不存关键字的话,需要我们不断的去访问外存,即便是O(㏒⑵N)的访问次数对于时间的消耗也是巨大的,外存的IO是很慢的:

那使用哈希表呢?哈希表的效率很高是O(1),但是一些极端场景下某个位置冲突很多,导致访问次数剧增,也是难以接受的。

为了解决这种方法我们来引入本期的主角:B树

二、B树

2.1 B树的概念

1970年,R.Bayer和E.mccreight提出了一种适合外查找的树,它是一种平衡的多叉树,称为B树 (后面有一个B的改进版本B+树,然后有些地方的B树写的的是B-树,注意不要误读成"B减树")。

一棵m阶(m>2)的B树,是一棵平衡的m路平衡搜索树,可以是空树或者满足一下性质:

1. 根节点至少有两个孩子

2. 每个分支节点都包含k-1个关键字和k个孩子,其中 ceil(m/2) ≤ k ≤ m,ceil是向上取整函数

3. 每个叶子节点都包含k-1个关键字,其中 ceil(m/2) ≤ k ≤ m

4. 所有的叶子节点都在同一层

5. 每个节点中的关键字从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域划分

6. 每个结点的结构为:(n,A0,K1,A1,K2,A2,… ,Kn,An)其中,Ki(1≤i≤n)为关键 字,且Ki < Ki+1(1 ≤ i ≤ n-1),Ai(0≤i≤n)为指向子树根结点的指针。且Ai所指子树所有结点中的 关键字均小于Ki+1。n为结点中关键字的个数,满足ceil(m/2)-1≤n≤m-1。

上面这些乍一眼看让人不知所云,下面我们结合代码和图来细细分析这些规则:
 

2.2 B树插入数据的分析

为了简单起见,我们先来看看m=3(三叉树)情况下的B树的插入过程:

来分析一下,当m=3时,每个节点中存储最多两个数据(data1,data2),这两个数据可以将该节点的区间分为三部分(即小于data1、大于data1小于data2、大于data2这三个部分):

但是在具体实现时,为了方便数据的插入我们将每个节点存储数据的大小设为初始大小+1:

至于为什么在原初始大小上+1,下面在数据插入的时候来体会:

下面我们插入数据:{40, 126, 62, 36, 132, 23, 188}

我们插入62这个数据后发现节点中数据达到m个,但节点中最多存储m-1个数据,所以下一步我们要进行节点的分裂数据的迁移

从这个过程可以看出,通过节点的分裂和数据的迁移,即便节点的存储数据的大小为m但实际只能存储m-1个有效数据

下面我们接着插入数据:

后面的数据插入不再赘述,我们看看到关键的节点分裂和数据迁移的过程:

最后我们综合之前的规律,插入最后一个数据

一直到现在我们都发现这棵数一直都是平衡的,这是为什么呢?B树怎么是一棵自平衡树呢?

这是因为B树是向右和向上增长的,这种分裂方式让B树始终是一棵平衡树(其他的大部分树都是纵向向下增长的)

2.3 B树的性能分析

实际的B树不会这么平凡的分裂,一般将M设为1024,那么想象一下,当M = 1024是,插入数据时,这个树的高度会如何变化?

第一层:1023个关键字;

第二层:1024个子结点 * 1023个关键字,大约是100W的级别;

第三层:1024 * 1024 * 1023,大约是10亿的级别;

第四层:1024 * 1024 * 1024 * 1023,大约是万亿级别;

但是上面的情况是理想化的满数据和节点的情况,那我们来算一下在最坏情况下:

第一层:1个关键字;

第二层:2个子结点 * 512个关键字,大约是1K的级别;

第三层:2 * 512 * 512,大约是10W的级别;

第四层:2 * 512 * 512 * 512,大约是2.5亿级别;

可以看到这个数据规模也高的惊人

对于一棵节点为N度为M的B-树,查找和插入需要㏒(M-1)N ~ ㏒(M/2)N次比较(每次的比较都使用二分查找),这个很好证明:对于度为M的B-树,每一个节点的子节点个数为M/2 ~(M-1)之间,因此树的高度应该在要㏒(M-1)N ~ ㏒(M/2)N之间,在定位到该节点后,再采用二分查找的方式可以很快的定位到该元素。

那么它的时间复杂度在㏒(M-1)N ~ ㏒(M/2)N之间,也就是说M越大,效率越高,但是M也不是越大越好,因为会有空间的浪费,有因为结点满了要拷走一半,浪费一个结点一半的空间;

最后我们来算一下对于N = 62*1000000000个节点,如果度M为1024,则㏒(M/2)N <= 4,即在620亿个元素中,如果这棵树的度为1024,则需要小于4次即可定位到该节点,然后利用二分查找可以快速定位到该元素,大大减少了读取磁盘的次数。

2.4 模拟实现B树

下面我们来手搓一棵B树,但这只是最基本的B树,想要它作为数据库的引擎还是需要很多优化和改进的:

2.4.1 B树节点的定义

template<class K, size_t M>//K我们要存储的关键字,M控制B树的叉数
struct BTreeNode
{//将节点所能存储的数据数和孩子数+1,方便我们后续的插入K _keys[M];//最多有M-1个数据BTreeNode<K, M>* _subs[M+1];//最多有M个孩子BTreeNode<K, M>* _pather;//记录节点的父亲节点,方便后续插入操作size_t _n;//记录节点中有效数据个数BTreeNode(){for (size_t i = 0; i < M; ++i){_keys[i] = K();_subs[i] = nullptr;}_subs[M] = nullptr;_n = 0;_pather = nullptr;}
};template<class K,size_t M>
class BTree
{typedef BTreeNode<K, M> Node;private:Node* _root = nullptr;
};

2.4.2 B树数据的查找

template<class K,size_t M>
class BTree
{typedef BTreeNode<K, M> Node;public:pair<Node*, int> Find(const K& key)//查找key值所对应的节点{Node* parent = nullptr, * cur = _root;size_t i = 0;while (cur){while (i < cur->_n)//在当前节点查找适合的位置{if (key < cur->_keys[i]){break;}else if (key > cur->_keys[i]){++i;}else{return make_pair(cur, i);}}parent = cur;cur = cur->_subs[i];//当前节点没找到,向其孩子节点再找i = 0;}return make_pair(parent, -1);//B树中没有该值,返回parent节点方便后续数据的插入}private:Node* _root = nullptr;
};

2.4.3 B树节点的数据插入

template<class K,size_t M>
class BTree
{typedef BTreeNode<K, M> Node;bool InsertKey(Node* node, Node* child, const K& key)//在node节点中插入新值及其孩子节点{int end = node->_n - 1;while (end >= 0)//直接插入排序{if (node->_keys[end] > key){node->_keys[end + 1] = node->_keys[end];node->_subs[end + 2] = node->_subs[end + 1];//移动的数据节点所对应的右孩子节点也需要向后移动--end;}else{break;}}node->_keys[end + 1] = key;node->_subs[end + 2] = child;node->_n++;if (child)//插入的孩子节点不为空要连接上其父亲节点{child->_parent = node;}return true;}public:pair<Node*, int> Find(const K& key)//查找key值所对应的节点{.....}bool Insert(const K& key){if (_root == nullptr)//第一次插入{_root = new Node;_root->_keys[0] = key;_root->_n++;return true;}pair<Node*, int>ret = Find(key);//查找要插入的值是否在B树中存在if (ret.second >= 0){return false;//存在就直接返回}//不存在在该节点中进行插入Node* parent = ret.first;K newKey = key;Node* child = nullptr;while (1){InsertKey(parent, child, newKey);//将数据插入到节点中//判断该节点是否需要进行分裂if (parent->_n < M){return true;}else//进行节点的分裂{Node* brother = new Node;//创建兄弟节点//将一半的数据转移到兄弟节点size_t mid = M / 2;size_t j = 0;for (size_t i = mid + 1; i < M; ++i){//转移节点数据brother->_keys[j] = parent->_keys[i];parent->_keys[i] = K();//转移掉的数据恢复初始值//转移左节点孩子brother->_subs[j++]= parent->_subs[i];if (parent->_subs[i])//转移走的孩子节点不为空要转换其父亲节点{parent->_subs[i]->_parent = brother;}parent->_subs[i] = nullptr;//转移掉的孩子置空}//转移最后的右节点孩子brother->_subs[j] = parent->_subs[M];if (parent->_subs[M])//转移走的孩子节点不为空要转换其父亲节点{parent->_subs[M]->_parent = brother;}parent->_subs[M] = nullptr;//转移掉的孩子置空brother->_n = j;//将该节点的中间值拿出来作为newKey,继续插入到该节点的父亲节点中parent->_n -= (brother->_n + 1);newKey = parent->_keys[mid];parent->_keys[mid] = K();//转移掉的数据恢复初始值if (parent->_parent == nullptr)//分裂的节点是根节点{_root = new Node;_root->_keys[0] = newKey;_root->_subs[0] = parent;_root->_subs[1] = brother;_root->_n = 1;parent->_parent = _root;brother->_parent = _root;break;//分裂完毕后直接返回}else{//向上跳一层接着进行插入parent = parent->_parent;child = brother;}}}return true;}private:Node* _root = nullptr;
};

2.4.4 B树的遍历

由于B树的有序性,我们选择中序对其遍历(与二叉搜索树大同小异),在我们遍历其节点时要先走完其数据的所有的左子树,最后再走右子树:

template<class K,size_t M>
class BTree
{typedef BTreeNode<K, M> Node;void _InOrder(Node* cur){if (cur == nullptr){return;}size_t i = 0;for (; i < cur->_n; ++i){_InOrder(cur->_subs[i]);//遍历左子树cout << cur->_keys[i] << " ";//遍历根}_InOrder(cur->_subs[i]);//遍历右子树}public:void InOrder(){_InOrder(_root);cout << endl;}private:Node* _root = nullptr;
};

2.4.5 模拟实现B树实现的完整代码

#include<utility>
using namespace std;template<class K, size_t M>//K我们要存储的关键字,M控制B树的叉数
struct BTreeNode
{//将节点所能存储的数据数和孩子数+1,方便我们后续的插入K _keys[M];//最多有M-1个数据BTreeNode<K, M>* _subs[M+1];//最多有M个孩子BTreeNode<K, M>* _parent;//记录节点的父亲节点,方便后续插入size_t _n;//记录节点中有效数据个数BTreeNode(){for (size_t i = 0; i < M; ++i){_keys[i] = K();_subs[i] = nullptr;}_subs[M] = nullptr;_n = 0;_parent = nullptr;}
};template<class K,size_t M>
class BTree
{typedef BTreeNode<K, M> Node;bool InsertKey(Node* node, Node* child, const K& key)//在node节点中插入新值及其孩子节点{int end = node->_n - 1;while (end >= 0)//直接插入排序{if (node->_keys[end] > key){node->_keys[end + 1] = node->_keys[end];node->_subs[end + 2] = node->_subs[end + 1];//移动的数据节点所对应的右孩子节点也需要向后移动--end;}else{break;}}node->_keys[end + 1] = key;node->_subs[end + 2] = child;node->_n++;if (child)//插入的孩子节点不为空要连接上其父亲节点{child->_parent = node;}return true;}void _InOrder(Node* cur){if (cur == nullptr){return;}size_t i = 0;for (; i < cur->_n; ++i){_InOrder(cur->_subs[i]);//遍历左子树cout << cur->_keys[i] << " ";//遍历根}_InOrder(cur->_subs[i]);//遍历右子树}public:pair<Node*, int> Find(const K& key)//查找key值所对应的节点{Node* parent = nullptr, * cur = _root;size_t i = 0;while (cur){while (i < cur->_n)//在当前节点查找适合的位置{if (key < cur->_keys[i]){break;}else if (key > cur->_keys[i]){++i;}else{return make_pair(cur, i);}}parent = cur;cur = cur->_subs[i];//当前节点没找到,向其孩子节点再找i = 0;}return make_pair(parent, -1);//B树中没有该值,返回parent节点方便后续数据的插入}bool Insert(const K& key){if (_root == nullptr)//第一次插入{_root = new Node;_root->_keys[0] = key;_root->_n++;return true;}pair<Node*, int>ret = Find(key);//查找要插入的值是否在B树中存在if (ret.second >= 0){return false;//存在就直接返回}//不存在在该节点中进行插入Node* parent = ret.first;K newKey = key;Node* child = nullptr;while (1){InsertKey(parent, child, newKey);//将数据插入到节点中//判断该节点是否需要进行分裂if (parent->_n < M){return true;}else//进行节点的分裂{Node* brother = new Node;//创建兄弟节点//将一半的数据转移到兄弟节点size_t mid = M / 2;size_t j = 0;for (size_t i = mid + 1; i < M; ++i){//转移节点数据brother->_keys[j] = parent->_keys[i];parent->_keys[i] = K();//转移掉的数据恢复初始值//转移左节点孩子brother->_subs[j++]= parent->_subs[i];if (parent->_subs[i])//转移走的孩子节点不为空要转换其父亲节点{parent->_subs[i]->_parent = brother;}parent->_subs[i] = nullptr;//转移掉的孩子置空}//转移最后的右节点孩子brother->_subs[j] = parent->_subs[M];if (parent->_subs[M])//转移走的孩子节点不为空要转换其父亲节点{parent->_subs[M]->_parent = brother;}parent->_subs[M] = nullptr;//转移掉的孩子置空brother->_n = j;//将该节点的中间值拿出来作为newKey,继续插入到该节点的父亲节点中parent->_n -= (brother->_n + 1);newKey = parent->_keys[mid];parent->_keys[mid] = K();//转移掉的数据恢复初始值if (parent->_parent == nullptr)//分裂的节点是根节点{_root = new Node;_root->_keys[0] = newKey;_root->_subs[0] = parent;_root->_subs[1] = brother;_root->_n = 1;parent->_parent = _root;brother->_parent = _root;break;//分裂完毕后直接返回}else{//向上跳一层接着进行插入parent = parent->_parent;child = brother;}}}return true;}void InOrder(){_InOrder(_root);cout << endl;}private:Node* _root = nullptr;
};void TestBTree()
{int a[] = { 40, 126, 62, 36, 132, 23, 188 };BTree<int, 3> tree;for (auto& x : a){tree.Insert(x);}tree.InOrder();
}

 

三、B+树

3.1 B+树的概念

B+树是B树的变形,是在B树基础上优化的多路平衡搜索树,B+树的规则跟B树基本类似,但是又在B树的基础上做了以下几点改进优化:

1. 分支节点的子树指针与关键字个数相同

2. 分支节点的子树指针p[i]指向关键字值大小在[k[i],k[i+1])区间之间

3. 所有叶子节点增加一个链接指针链接在一起

4. 所有关键字及其映射数据都在叶子节点出现

我们来分析一下B+树和B树的区别:

由第一点规则我们可以发现B+树是将B树每个节点的最左边的子树去除了,使得数据个数与孩子树相等;

第二点规则可以看到B+树的分支节点都是叶子节点的值的最小值,以此来让父节点存储最小值来做索引方便查找;

第三点规则极大程度上方便了B+树的遍历,我们只需要遍历叶子节点就可以得到全部的数据,为范围查找提供了基础

第四点规则就可以让k-v结构的B+树的分支节点只存储key值,最后在叶子节点中也可以找到对应的value值,减少了空间上的花费

 

3.2 B+树插入数据的分析

为了简单起见,我们先来看看m=3(三叉树)情况下的B+树的插入过程:

来分析一下,当m=3时,每个节点中存储最多两个数据(data1,data2,data3),这三个数据可以将该节点的区间分为三部分(即大于等于data1小于data2、大于等于data2小于data3、大于等于data3这三个部分):

和B树一样,在具体实现时,为了方便数据的插入我们将每个节点存储数据的大小设为初始大小+1:

下面我们插入数据:{40, 126, 62, 36, 132, 23, 188}

我们可以发现B+树一开始就是有两层的结构的,第一层作为索引,第二层才真正的存储数据,每当新插入的数据值小于索引值时,索引是要进行更新的(例如36的插入)

插入36这个数据后发现节点中数据达到m个,但节点中最多存储m-1个数据,所以下一步我们要进行节点的分裂数据的迁移

下面我们接着插入数据:

最后我们再插入两个数据,来看看没有父亲节点的节点的分裂:

这时这课B+树的根节点满了,我们来看看其分裂:

 

四、B*树

B*树是B+树的变形,在B+树的非根和非叶子节点再增加指向兄弟节点的指针:

但是B*树的节点数据满了并不进行分裂,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);但如果兄弟节点也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点(这样子原节点,兄弟节点和新节点的空间都使用了2/3),最后在父结点增加新结点的指针。

所以,B*树分配新结点的概率比B+树要低,空间使用率更高

但是由于B树系列常常在外存中使用,对于存储空间丰富的磁盘来说,B*树对空间利用率的提升效果并不明显,所以在很多使用场景中常常使用结构更为简单的B+树

五、B树系列总结

通过上述的介绍分析,大致将B树,B+树,B*树总结如下:

B树:有序数组+平衡多叉树

B+树:有序数组链表+平衡多叉树

B*树:一棵更丰满的,空间利用率更高的B+树

接下来我们来谈谈,B树系列在内存和外存中使用与哈希和二叉平衡搜索树的对比:

在内存中:

单论树高度,搜索效率而言,B树确实不错

但是B树系列有一些隐形的坏处:

1、空间利用率低,消耗高。
2、插入删除数据,分裂和合并节点时,必然会挪动数据,效率低。
3、虽然高度更低,但是在内存中而言,跟哈希和平衡搜索树还是一个量级的(因为内存的空间并不大,在N数量级较小时log以2为底的和以M(M≈1024)为底的结果差距并不大;而且这些微小的差距在极快的内存处理效率面前体现极小)

结论:实质上B树系列在内存中体现不出优势。

在外存中:

在外存容量是内存好几个数量级的场景里,B树系列多消耗的空间几乎微乎其微

接下来我们来算一下1亿以2为底的对数约为30,以1024为底的对数为3;假设外存完成一次搜索需要1s,那这两者之间就相差了29s,这在时间上的体现是极大的

结论:B树系列在外存中的优势极大。

六、B-树的应用

6.1 索引

B-树最常见的应用就是用来做索引。索引通俗的说就是为了方便用户快速找到所寻之物,比如: 书籍目录可以让读者快速找到相关信息,hao123网页导航网站,为了让用户能够快速的找到有价值的分类网站,本质上就是互联网页面中的索引结构。

MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构,简单来说:索引就是数据结构。

当数据量很大时,为了能够方便管理数据,提高数据查询的效率,一般都会选择将数据保存到数据库,因此数据库不仅仅是帮助用户管理数据,而且数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用数据,这样就可以在这些数据结构上实现高级查找算法, 该树据结构就是索引。

6.2 MySQL索引简介

对于此有兴趣的同学们可以看到这里:MySQL索引详解:概念、类型与优化


本期博客到这里就结束啦,让大家久等了,后期会保持连续更新~

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

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

相关文章

《CPython Internals》阅读笔记:p97-p117

《CPython Internals》学习第 7 天&#xff0c;p97-p117 总结&#xff0c;总计 21 页。 一、技术总结 1.词法分析(lexical analysis) 根据《Compilers-Principles, Techniques, and Tools》(《编译原理》第2版)第 5 页&#xff1a;The first phase of a compiler is called …

2.两数相加--力扣

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 …

口碑很好的国产LDO芯片,有哪些?

在几乎任何一个电路设计中&#xff0c;都可能会使用LDO&#xff08;低压差线性稳压器&#xff09;这个器件。 虽然LDO不是什么高性能的IC&#xff0c;但LDO芯片市场竞争异常激烈。最近几年&#xff0c;诞生了越来越多的精品国产LDO&#xff0c;让人看得眼花缭乱。 业内人士曾经…

搜索引擎的设计与实现【源码+文档+部署讲解】

目 录 目 录 1 绪论 1.1 项目背景 1.2 国内外发展现状及分类 1.3 本论文组织结构介绍 2 相关技术介绍 2.1什么是搜索引擎 2.2 sqlserver数据库 2.3 Tomcat服务器 3 搜索引擎的基本原理 3.1搜索引擎的基本组成及其功能 3.2搜索引擎的详细工作流程 4 系统分析与…

计算机系统组成(计算机组成原理 基础)

文章目录&#xff1a; 一&#xff1a;体系结构 1.系统组成 1.1 硬件系统 1.2 软件系统 2.工作原理 2.1 冯诺依曼体系 2.2 指令和指令系统 3.性能指标 二&#xff1a;硬件系统 1.主机 1.1 CPU 1.2 内存 2.外设 2.1 外存 2.2 输入设备 2.3 输出设备 2.4 适配器 …

一些计算机零碎知识随写(25年1月)-1

我原以为世界上有技术的那批人不会那么闲&#xff0c;我错了&#xff0c;被脚本真实了。 今天正隔着画画呢&#xff0c;手机突然弹出几条安全告警通知。 急忙打开服务器&#xff0c;发现问题不简单&#xff0c;直接关服务器重装系统..... 首先&#xff0c;不要认为小网站&…

Go Ebiten小游戏开发:贪吃蛇

贪吃蛇是一款经典的小游戏&#xff0c;玩法简单却充满乐趣。本文将介绍如何使用 Go 语言和 Ebiten 游戏引擎开发一个简单的贪吃蛇游戏。通过这个项目&#xff0c;你可以学习到游戏开发的基本流程、Ebiten 的使用方法以及如何用 Go 实现游戏逻辑。 项目简介 贪吃蛇的核心玩法是…

FCPX插件:100组二维卡通动漫流体线条MG动画元素包 MotionVfx – mzap

mZap 是一款由 motionVFX 公司出品的 Final Cut Pro X 模板&#xff0c;提供 100 种卡通动漫流体 MG 动画元素和标题效果。这套模板专为视频制作者设计&#xff0c;添加流畅且生动的动画效果&#xff0c;提升视频的创意表现力。 丰富预设&#xff1a;提供 100 种卡通动漫流体 M…

linux下实现U盘和sd卡的自动挂载

linux下实现U盘和sd卡的自动挂载 Chapter0 linux下实现U盘和sd卡的自动挂载 Chapter0 linux下实现U盘和sd卡的自动挂载 原文链接&#xff1a;https://blog.csdn.net/EmSoftEn/article/details/45099699 目的&#xff1a;使U盘和SD卡在Linux系统中进行插入和拔除时能自动挂载和…

Taro+react 开发第一节创建 带有redux状态管理的项目

Taro 项目基于 node&#xff0c;请确保已具备较新的 node 环境&#xff08;>16.20.0&#xff09;&#xff0c;推荐使用 node 版本管理工具 nvm 来管理 node&#xff0c;这样不仅可以很方便地切换 node 版本&#xff0c;而且全局安装时候也不用加 sudo 了。 1.安装 npm inf…

ZooKeeper Java API操作

&#xff08;1&#xff09;添加依赖&#xff0c;在pom.xml文件中添加zookeeper依赖&#xff1a; &#xff08;2&#xff09;连接zookeeper服务&#xff0c;创建cn.itcast.zookeeper包&#xff0c;在该包中创建ZooKeeperDemo类&#xff0c;该类用于实现创建会话和操作ZooKeeper&…

什么是顶级思维?

在现代社会&#xff0c;我们常常听到“顶级思维”这个概念&#xff0c;但究竟什么才是顶级思维&#xff1f;它又是如何影响一个人的成功和幸福呢&#xff1f;今天&#xff0c;我们就来探讨一下顶级思维的几个关键要素&#xff0c;并分享一些实用的生活哲学。 1. 身体不适&…

【源码解析】Java NIO 包中的 ByteBuffer

文章目录 1. 前言2. ByteBuffer 概述3. 属性4. 构造器5. 方法5.1 allocate 分配 Buffer5.2 wrap 映射数组5.3 slice 获取子 ByteBuffer5.4 duplicate 复刻 ByteBuffer5.5 asReadOnlyBuffer 创建只读的 ByteBuffer5.6 get 方法获取字节5.7 put 方法往 ByteBuffer 里面加入字节5.…

大模型(LLM)面试全解:主流架构、训练目标、涌现能力全面解析

系列文章目录 大模型&#xff08;LLMs&#xff09;基础面 01-大模型&#xff08;LLM&#xff09;面试全解&#xff1a;主流架构、训练目标、涌现能力全面解析 大模型&#xff08;LLMs&#xff09;进阶面 文章目录 系列文章目录大模型&#xff08;LLMs&#xff09;基础面一、目…

若依框架--数据字典设计使用和前后端代码分析

RY的数据字典管理: 字典管理是用来维护数据类型的数据&#xff0c;如下拉框、单选按钮、复选框、树选择的数据&#xff0c;方便系统管理员维护。减少对后端的访问&#xff0c;原来的下拉菜单点击一下就需要对后端进行访问&#xff0c;现在通过数据字典减少了对后端的访问。 如…

Unity打包+摄像机组件

转换场景 使用程序集&#xff1a;using UnityEngine.SceneManagement; 切换场景相关代码&#xff1a;SceneManager.LoadScene(1);//括号内可放入场景名称&#xff0c;场景索引等 //Application.LoadLevel(""); 老版本Unity加载场景方法 打包相关 Bundle Identi…

蓝桥与力扣刷题(66 加一)

题目&#xff1a; 给定一个由 整数 组成的 非空 数组所表示的非负整数&#xff0c;在该数的基础上加一。 最高位数字存放在数组的首位&#xff0c; 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外&#xff0c;这个整数不会以零开头。 示例 1&#xff1a; 输入…

stable diffusion 量化学习笔记

文章目录 一、一些tensorRT背景及使用介绍1&#xff09;深度学习介绍2&#xff09;TensorRT优化策略介绍3&#xff09;TensorRT基础使用流程4&#xff09;dynamic shape 模式5&#xff09;TensorRT模型转换 二、实操1&#xff09;编译tensorRT开源代码运行SampleMNIST 一、一些…

省森林防火应急指挥系统

森林防火形势严峻 我国森林防火形势十分严峻&#xff0c;森林火灾具有季节性强、发现难、成灾迅速等特点&#xff0c;且扑救难度大、影响范围广、造成的损失重。因此&#xff0c;构建森林防火应急指挥系统显得尤为重要。 系统建设模式与架构 森林防火应急指挥系统采用大智慧…

drawDB docker部属

docker pull xinsodev/drawdb docker run --name some-drawdb -p 3000:80 -d xinsodev/drawdb浏览器访问&#xff1a;http://192.168.31.135:3000/