【数据结构高阶】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.rhkb.cn/news/336.html

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

相关文章

java项目之房屋租赁系统源码(springboot+mysql+vue)

项目简介 房屋租赁系统实现了以下功能&#xff1a; 房屋租赁系统的主要使用者分为&#xff1a; 系统管理&#xff1a;个人中心、房屋信息管理、预约看房管理、合同信息管理、房屋报修管理、维修处理管理、房屋评价管理等模块的查看及相应操作&#xff1b; 房屋信息管理&#…

maven的简单介绍

目录 1、maven简介2、maven 的主要特点3、maven的下载与安装4、修改配置文件5、私服(拓展) 1、maven简介 Maven 是一个广泛使用的项目管理和构建工具&#xff0c;主要应用于 Java 项目。Maven 由 Apache 软件基金会开发和维护&#xff0c;它提供了一种简洁且一致的方法来构建、…

搭建prometheus+grafana监控系统抓取Linux主机系统资源数据

Prometheus 和 Grafana 是两个非常流行的开源工具&#xff0c;通常结合使用来实现监控、可视化和告警功能。它们在现代 DevOps 和云原生环境中被广泛使用。 1. Prometheus 定义&#xff1a;Prometheus 是一个开源的系统监控和告警工具包&#xff0c;最初由 SoundCloud 开发&am…

【YOLOv5】源码(train.py)

train.py是YOLOv5中用于模型训练的脚本文件&#xff0c;其主要功能是读取配置文件、设置训练参数、构建模型结构、加载数据、训练/验证模型、保存模型权重文件、输出日志等 参考笔记&#xff1a; 【YOLOv3】源码&#xff08;train.py&#xff09;_yolo原始代码-CSDN博客 【y…

MySQL存储引擎、索引、索引失效

MySQL Docker 安装 MySQL8.0&#xff0c;安装见docker-compose.yaml 操作类型 SQL 程序语言有四种类型&#xff0c;对数据库的基本操作都属于这四种类&#xff0c;分为 DDL、DML、DQL、DCL DDL(Dara Definition Language 数据定义语言)&#xff0c;是负责数据结构定义与数据…

如何看待Akamai 退出中国市场进行转型?

Akamai宣布退出中国市场并进行战略转型&#xff0c;这一举措引发了广泛的关注和讨论。从多个角度来看&#xff0c;这一决策既反映了Akamai自身的业务调整需求&#xff0c;也与中国市场环境的变化密切相关。 Akamai的退出是其全球战略调整的一部分。Akamai近年来一直在推进业务…

【Linux】网络层

目录 IP协议 协议头格式 网段划分 2中网段划分的方式 为什么要进行网段划分 特殊的IP地址 IP地址的数量限制 私有IP地址和公有IP地址 路由 IP协议 在通信时&#xff0c;主机B要把数据要给主机C&#xff0c;一定要经过一条路径选择&#xff0c;为什么经过路由器G后&…

多线程面试相关

线程基础知识 线程与进程的区别 并行和并发的区别 创建线程的方式 Runnable和Callable有什么区别 run()方法和start()方法的区别 小结 线程包含哪些状态&#xff0c;各个状态之间如何变化 线程按顺序执行 notify()和notifyAll()的区别 Java中的wait方法和sleep方法的不同 如何…

Unity + Firebase + GoogleSignIn 导入问题

我目前使用 Unity版本&#xff1a;2021.3.33f1 JDK版本为&#xff1a;1.8 Gradle 版本为&#xff1a;6.1.1 Firebase 版本: 9.6.0 Google Sign In 版本为&#xff1a; 1.0.1 问题1 &#xff1a;手机点击登录报错 apk转化成zip&#xff0c;解压&#xff0c;看到/lib/armeabi-v…

【Rust自学】11.10. 集成测试

喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 11.10.1. 什么是集成测试 在Rust里&#xff0c;集成测试完全位于被测试库的外部。集成测试…

【Word_笔记】Word的修订模式内容改为颜色标记

需求如下&#xff1a;请把修改后的部分直接在原文标出来&#xff0c;不要采用修订模式 步骤1&#xff1a;打开需要转换的word后&#xff0c;同时按住alt和F11 进入&#xff08;Microsoft Visual Basic for Appliations&#xff09; 步骤2&#xff1a;插入 ---- 模块 步骤3&…

深入Android架构(从线程到AIDL)_21 IPC的Proxy-Stub设计模式03

目录 3、包裝IBinder接口 -- 使用Proxy-Stub设计模式 EIT造型的双层组合 4、 谁来写Proxy及Stub类呢? -- 地头蛇(App开发者)自己写 范例 定义一个新接口&#xff1a; IPlayer 撰写一个Stub类&#xff1a; PlayerStub 撰写mp3Binder类 撰写mp3RemoteService类 3、…

数据在内存的存储

数据类型介绍 前面我们已经学习了基本的内置类型&#xff1a; char //字符数据类型 1字节 打印%c short //短整型 2字节 打印%hd int //整形 4字节 打印%d long long int //长整型 4/8字节 打印%ld l…

springboot 默认的 mysql 驱动版本

本案例以 springboot 3.1.12 版本为例 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.12</version><relativePath/> </parent> 点击 spring-…

直流无刷电机控制(FOC):电流模式

目录 概述 1 系统框架结构 1.1 硬件模块介绍 1.2 硬件实物图 1.3 引脚接口定义 2 代码实现 2.1 软件架构 2.2 电流检测函数 3 电流环功能实现 3.1 代码实现 3.2 测试代码实现 4 测试 概述 本文主要介绍基于DengFOC的库函数&#xff0c;实现直流无刷电机控制&#x…

在 Flownex 网络中创建传热元件

本文探讨了在 Flownex 仿真中集成新传热元件的步骤&#xff0c;以增强热系统管理能力。 了解 Flownex 中的传热元件 Flownex 中的传热元件复制传导、对流和辐射&#xff0c;从而深入了解系统的热流体行为。准确建模复杂系统需要了解这些单元的特性和功能。Flownex 的高级功能…

《OpenCV计算机视觉实战项目》——银行卡号识别

文章目录 项目任务及要求项目实现思路项目实现及代码导入模块设置参数对模版图像中数字的定位处理银行卡的图像处理读取输入图像&#xff0c;预处理找到数字边框使用模版匹配&#xff0c;计算匹配得分 画出并打印结果 项目任务及要求 任务书&#xff1a; 要为某家银行设计一套…

PyCharm文档管理

背景&#xff1a;使用PyCharmgit做文档管理 需求&#xff1a;需要PyCharm自动识别docx/xslx/vsdx等文件类型&#xff0c;并在PyCharm内点击文档时唤起系统内关联应用(如word、excel、visio) 设置步骤&#xff1a; 1、file -》 settings -》file types 2、在Files opened i…

景联文科技提供高质量多模态数据处理服务,驱动AI新时代

在当今快速发展的AI时代&#xff0c;多模态数据标注成为推动人工智能技术进步的关键环节。景联文科技作为行业领先的AI数据服务提供商&#xff0c;专注于为客户提供高质量、高精度的多模态数据标注服务&#xff0c;涵盖图像、语音、文本、视频及3D点云等多种类型的数据。通过专…

django基于Python的智能停车管理系统

1.系统概述 1.定义&#xff1a;Django 基于 Python 的智能停车管理系统是一个利用 Django 框架构建的软件系统&#xff0c;用于高效地管理停车场的各种事务&#xff0c;包括车辆进出记录、车位预订、收费管理等诸多功能。 2.目的&#xff1a;它的主要目的是提高停车场的运营效…