【C++】B树及其实现

写目录

  • 一、B树的基本概念
    • 1.引入
    • 2.B树的概念
  • 二、B树的实现
    • 1.B树的定义
    • 2.B树的查找
    • 3.B树的插入操作
    • 4.B树的删除
    • 5.B树的遍历
    • 6.B树的高度
    • 7.整体代码
  • 三、B+树和B*树
    • 1.B+树
    • 2.B*树
    • 3.总结

一、B树的基本概念

1.引入

我们已经学习过二叉排序树、AVL树和红黑树三种树形查找结构,但上述结构适用于数据量相对不是很大,能够一次性放进内存中,进行数据查找的场景。如果数据量非常大,比如由100G数据,无法一次放进内存中,那就只能放在磁盘上了。此时想要搜索数据就需要将存放关键字及其映射的数据的地址放到内存中的搜索树的节点中,那么要访问数据时,先取这个地址去磁盘访问数据。在这里插入图片描述
但是由于磁盘访问的速度很慢,对于上述树形查找结构来说就是需要logN次的IO,这是一个很难接受的结果。
那么如何加速对数据的访问呢?

  1. 提高IO的速度(SSD相比传统机械硬盘是快了不少,但还是没有得到本质性的提升)
  2. 降低树的高度——多路平衡查找树

2.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。

二、B树的实现

1.B树的定义

为了方便学习,我们在这里先将m设为3,这时每个结点能存2个关键字和3个孩子。不过为了方便后续插入,分别额外多开了一个空间。
在这里插入图片描述

template<class K, size_t M>
struct BTreeNode
{
	K _keys[M];						// 用于存储关键字
	BTreeNode<K, M>* _subs[M + 1];	// 用于存储孩子
	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;
		_parent = nullptr;
		_n = 0;
	}
};

2.B树的查找

查找的具体步骤应该是先进入根结点,如果等于data1,则返回,如果小于data1,则进入child1,如果大于data1则++i,此时i指向data2,若小于data2,则进入child2,若大于data2,则进入child3。

pair<Node*, int> Find(const K& key)
{
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		size_t i = 0;
		while (i < cur->_n)		// 在当前结点进行查找
		{
			if (key < cur->_keys[i])	// 如果小于则进入与当前下标相同的孩子
			{
				break;
			}
			else if (key > cur->_keys[i])	//如果大于则++i
			{
				++i;
			}
			else
			{
				return make_pair(cur, i);	// 找到了就返回当前结点以及该关键字所在的位置
			}
		}
		parent = cur;
		cur = cur->_subs[i];
	}
	return make_pair(parent, -1);	//找不到返回父结点
}

3.B树的插入操作

用序列{53, 139, 75, 49, 145, 36, 101}构建B树的过程如下:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
插入过程总结:

  1. 如果树为空,直接插入新节点中,该节点为树的根节点
  2. 树非空,找待插入元素在树中的插入位置(注意:找到的插入节点位置一定在叶子节点中)
  3. 检测是否找到插入位置(假设树中的key唯一,即该元素已经存在时则不插入)
  4. 按照插入排序的思想将该元素插入到找到的节点中
  5. 检测该节点是否满足B-树的性质:即该节点中的元素个数是否等于M,如果小于则满足
  6. 如果插入后节点不满足B树的性质,需要对该节点进行分裂:
    • 申请新节点
    • 找到该节点的中间位置
    • 将该节点中间位置右侧的元素以及其孩子搬移到新节点中
    • 将中间位置元素以及新节点往该节点的双亲节点中插入,即继续4
  7. 如果向上已经分裂到根节点的位置,插入结束
// 插入操作中用到的函数
void InsertKey(Node* node, const K& key, Node* child)	// 插入新关键字
{
	int end = node->_n - 1;
	while (end >= 0)
	{
		if (key < node->_keys[end])
		{
			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;
	if (child)
	{
		child->_parent = node;
	}
	++node->_n;
}
// B树的插入
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);	// 借助查找操作来判定是否存在要插入的值
	if (ret.second >= 0)				// 如果不存在则可以找到要进行插入的位置
	{
		return false;
	}

	Node* parent = ret.first;
	K newKey = key;
	Node* child = nullptr;
	while (1)
	{
		InsertKey(parent, newKey, child);	// 先利用插入排序的方法进行插入
		if (parent->_n < M)					// 如果没有破坏B树的性质则返回true
		{
			return true;
		}
		else								// 否则进行分裂操作
		{
			size_t mid = M / 2;
			Node* brother = new Node;
			size_t i = mid + 1;
			size_t j = 0;
			for (; i < M; ++i)				// 把一半的数据分给新建的兄弟结点
			{
				brother->_keys[j] = parent->_keys[i];
				brother->_subs[j] = parent->_subs[i];
				if (parent->_subs[i])
				{
					parent->_subs[i]->_parent = brother;
				}
				++j;

				// 拷走重置一下方便观察
				parent->_keys[i] = K();
				parent->_subs[i] = nullptr;
			}
			brother->_subs[j] = parent->_subs[i];

			if (parent->_subs[i])
			{
				parent->_subs[i]->_parent = brother;
			}
			parent->_subs[i] = nullptr;

			brother->_n = j;
			parent->_n -= (brother->_n + 1);

			K midKey = parent->_keys[mid];
			parent->_keys[mid] = K();

			if (parent->_parent == nullptr)	// 如果没有父结点,则新建
			{
				_root = new Node;
				_root->_keys[0] = midKey;
				_root->_subs[0] = parent;
				_root->_subs[1] = brother;
				_root->_n = 1;
				parent->_parent = _root;
				brother->_parent = _root;
				break;
			}
			else	// 有父结点则将插入排序的参数修改,然后回到循环开始插入
			{
				newKey = midKey;
				child = brother;
				parent = parent->_parent;
			}
		}
	}
	return true;
}

B树插入的代码实现非常复杂,需要十分细心,要注意对结点的维护。

4.B树的删除

B树的删除分为3种情况:

  1. 直接删除关键字:若删除当前关键字后仍然满足B树定义,则直接删除该关键字。
  2. 兄弟够借:若再删除一个关键字就会破坏B树定义,并且左,右兄弟的关键字个数大于等于ceil(m/2),则需要调整该结点、右(或左)兄弟结点及其父结点(父子换位法),以达到新的平衡。
  3. 兄弟不够借:若左、右兄弟结点的关键字个数都不足以被借,则将关键字删除后与左(或右)兄弟结点及父结点的关键字进行合并。

由于B树的删除用代码实现非常复杂,就不多讲了。

5.B树的遍历

B树的遍历就不难了,与查找的过程类比即可。

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]); // 最后的那个右子树
}

void InOrder()
{
	_InOrder(_root);
}

6.B树的高度

B树的效率取决于B树的高度,因为磁盘存取次数与高度成正比。
若n>=1,则对任意一颗包含n个关键字、高度为h、阶数为m的B树:

  1. 若让每个结点中的关键字个数达到最多,则容纳同样多关键字的B树的高度达到最小。因为B树种每个结点最多有m棵子树,m-1个关键字,所以在一颗高度为h的m阶B树中关键字的个数应满足 n < = ( m − 1 ) ( 1 + m + m 2 + . . . + m h − 1 ) = m h − 1 n<=(m-1)(1+m+m^2+...+m^{h-1})=m^h-1 n<=(m1)(1+m+m2+...+mh1)=mh1,因此有 h > = l o g m ( n + 1 ) h>=log_m(n+1) h>=logm(n+1)
  2. 若让每个结点中的关键字个数达到最少,则容纳同样多关键字的B树高度达到最大。第一层至少有1个结点;第二层至少有两个结点;除根结点外的每个非叶结点至少有ceil(m/2)棵子树,则第三层至少有2ceil(m/2)个结点……第h+1层至少有 2 ( c e i l ( m / 2 ) ) h − 1 2(ceil(m/2))^{h-1} 2(ceil(m/2))h1个结点,注意到第h+1层是不包含任何信息的叶结点。对于关键字个数为n的B树,叶结点即查找不成功的结点为n+1,由此有 n + 1 > = 2 ( c e i l ( m / 2 ) ) h − 1 n+1>=2(ceil(m/2))^{h-1} n+1>=2(ceil(m/2))h1,即 h < = l o g c e i l ( m / 2 ) ( ( n + 1 ) / 2 ) + 1 h<=log_{ceil(m/2)}((n+1)/2)+1 h<=logceil(m/2)((n+1)/2)+1

7.整体代码

#include <iostream>
using namespace std;

template<class K, size_t M>
struct BTreeNode
{
	K _keys[M];
	BTreeNode<K, M>* _subs[M + 1];
	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;
		_parent = nullptr;
		_n = 0;
	}
};

template<class K, size_t M>
class BTree
{
	typedef BTreeNode<K, M> Node;
public:
	pair<Node*, int> Find(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			size_t i = 0;
			while (i < cur->_n)		// 在当前结点进行查找
			{
				if (key < cur->_keys[i])	// 如果小于则进入与当前下标相同的孩子
				{
					break;
				}
				else if (key > cur->_keys[i])	//如果大于则++i
				{
					++i;
				}
				else
				{
					return make_pair(cur, i);	// 找到了就返回当前结点以及该关键字所在的位置
				}
			}
			parent = cur;
			cur = cur->_subs[i];
		}
		return make_pair(parent, -1);	//找不到返回父结点
	}
	void InsertKey(Node* node, const K& key, Node* child)	// 插入新关键字
	{
		int end = node->_n - 1;
		while (end >= 0)
		{
			if (key < node->_keys[end])
			{
				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;
		if (child)
		{
			child->_parent = node;
		}
		++node->_n;
	}
	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);	// 借助查找操作来判定是否存在要插入的值
		if (ret.second >= 0)				// 如果不存在则可以找到要进行插入的位置
		{
			return false;
		}

		Node* parent = ret.first;
		K newKey = key;
		Node* child = nullptr;
		while (1)
		{
			InsertKey(parent, newKey, child);	// 先利用插入排序的方法进行插入
			if (parent->_n < M)					// 如果没有破坏B树的性质则返回true
			{
				return true;
			}
			else								// 否则进行分裂操作
			{
				size_t mid = M / 2;
				Node* brother = new Node;
				size_t i = mid + 1;
				size_t j = 0;
				for (; i < M; ++i)				// 把一半的数据分给新建的兄弟结点
				{
					brother->_keys[j] = parent->_keys[i];
					brother->_subs[j] = parent->_subs[i];
					if (parent->_subs[i])
					{
						parent->_subs[i]->_parent = brother;
					}
					++j;

					// 拷走重置一下方便观察
					parent->_keys[i] = K();
					parent->_subs[i] = nullptr;
				}
				brother->_subs[j] = parent->_subs[i];

				if (parent->_subs[i])
				{
					parent->_subs[i]->_parent = brother;
				}
				parent->_subs[i] = nullptr;

				brother->_n = j;
				parent->_n -= (brother->_n + 1);

				K midKey = parent->_keys[mid];
				parent->_keys[mid] = K();

				if (parent->_parent == nullptr)	// 如果没有父结点,则新建
				{
					_root = new Node;
					_root->_keys[0] = midKey;
					_root->_subs[0] = parent;
					_root->_subs[1] = brother;
					_root->_n = 1;
					parent->_parent = _root;
					brother->_parent = _root;
					break;
				}
				else	// 有父结点则将插入排序的参数修改,然后回到循环开始插入
				{
					newKey = midKey;
					child = brother;
					parent = parent->_parent;
				}
			}
		}
		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]); // 最后的那个右子树
	}

	void InOrder()
	{
		_InOrder(_root);
	}
private:
	Node* _root = nullptr;
};

void TestBtree()
{
	int a[] = { 53, 139, 75, 49, 145, 36, 101 };
	BTree<int, 3> t;
	for (auto e : a)
	{
		t.Insert(e);
	}
	t.InOrder();
}

三、B+树和B*树

1.B+树

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

  1. 分支节点的子树指针与关键字个数相同
  2. 分支节点的子树指针p[i]指向关键字值大小在[k[i],k[i+1])区间之间
  3. 所有叶子节点增加一个链接指针链接在一起
  4. 所有关键字及其映射数据都在叶子节点出现

在这里插入图片描述

B+树的特性

  1. 所有关键字都出现在叶子结点的链表中,且链表中的结点都是有序的。
  2. 不可能在分支结点中命中。
  3. 分支结点相当于是叶子结点的索引,叶子结点才是存储数据的数据层。

B+树的分裂
当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增
加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向
兄弟的指针。

2.B*树

B*树是B+树的变形,在B+树的非根和非叶子节点再增加指向兄弟节点的指针。
在这里插入图片描述

B*树的分裂
当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结
点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如
果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父
结点增加新结点的指针。
所以,B*树分配新结点的概率比B+树要低,空间使用率更高;

3.总结

通过以上介绍,大致将B树,B+树,B*树总结如下:

  1. B树:有序数组+平衡多叉树;
  2. B+树:有序数组链表+平衡多叉树;
  3. B*树:一棵更丰满的,空间利用率更高的B+树。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1-3 NLP为什么这么难做

1-3 NLP为什么这么难做 主目录点这里 字词结构的复杂性 中文以汉字为基础单位&#xff0c;一个词通常由一个或多个汉字组成&#xff0c;而不像英语词汇单元由字母构成。这使得中文分词&#xff08;切分句子为词语&#xff09;成为一个具有挑战性的任务。语言歧义性 中文中常…

Mysql-常见DML-DQL-语句语法用法总结

1、常见DML语句 1.1 INSERT语句 说明&#xff1a;将数据插入到数据库表中。 INSERT INTO table_name (column1, column2, ...) VALUES (value1, value2, ...); 实例&#xff1a;添加C罗信息到数据库表中 insert into employee (ID, name, gender, entrydate, age) values …

eclipse断点调试(用图说话)

eclipse断点调试&#xff08;用图说话&#xff09; debug方式启动项目&#xff0c;后端调试bug调试 前端代码调试&#xff0c;请参考浏览器断点调试&#xff08;用图说话&#xff09; 1、前端 选中一条数据&#xff0c;点击删除按钮 2、后端接口打断点 断点按钮 介绍 resu…

python如何设计窗口

PyQt是一个基于Qt的接口包&#xff0c;可以直接拖拽控件设计UI界面&#xff0c;下面我简单介绍一下这个包的安装和使用&#xff0c;感兴趣的朋友可以自己尝试一下&#xff1a; 1、首先&#xff0c;安装PyQt模块&#xff0c;这个直接在cmd窗口输入命令“pip install pyqt5”就行…

Hugging Face 全球政策负责人首次参加WAIC 2024 前沿 AI 安全和治理论坛

Hugging Face 全球政策负责人艾琳-索莱曼 &#xff08; Irene Solaiman &#xff09;将参加7月5日在上海举办的WAIC-前沿人工智能安全和治理论坛&#xff0c;并在现场进行主旨演讲和参加圆桌讨论。具体时间信息如下&#xff1a;主旨演讲&#xff1a;开源治理的国际影响时间 &am…

YOLOv8改进 添加轻量级注意力机制ELAttention

一、ELA论文 论文地址:2403.01123 (arxiv.org) 二、Efficient Local Attention结构 ELA (Efficient Local Attention) 被用于处理自然语言处理任务中的序列数据。它旨在提高传统注意力机制的效率,并减少其计算和存储成本。 在传统的注意力机制中,计算每个输入位置与所有其…

fastadmin 如何给页面添加水印

偶然发现fastadmin框架有个水印插件&#xff0c;看起来漂亮&#xff0c;就想也实现这样的功能&#xff0c;看到需要费用。但是现成的插件需要费用&#xff0c;自己动手丰衣足食。说干就干。 1. 找到watermark.js &#xff0c;放到assets/js/ 下面 2.具体页面引入 <script…

【深度学习】图形模型基础(5):线性回归模型第四部分:预测与贝叶斯推断

1.引言 贝叶斯推断超越了传统估计方法&#xff0c;它包含三个关键步骤&#xff1a;结合数据和模型形成后验分布&#xff0c;通过模拟传播不确定性&#xff0c;以及利用先验分布整合额外信息。本文将通过实际案例阐释这些步骤&#xff0c;展示它们在预测和推断中的挑战和应用。…

Docker 运行Nacos无法访问地址解决方法

参考我的上一篇文章去配置好镜像加速器&#xff0c;镜像加速器不是配置越多越好&#xff0c;重试次数多了会失败 Dockerhub无法拉取镜像配置阿里镜像加速器-CSDN博客 错误的尝试 最开始按照网上的方式去配了一大堆&#xff0c;发现下不下来。 镜像源地址&#xff1a;https:…

奇景光电战略投资Obsidian,共筑热成像技术新未来

5月29日,业界领先的IC设计公司奇景光电宣布,将对热成像传感器解决方案制造商Obsidian进行战略性投资,并以主要投资者的身份,参与到Obsidian的可转换票据融资活动中。虽然奇景光电并未公开具体的投资金额,但这一举动无疑向市场传递了一个明确的信号:奇景光电对Obsidian的技…

web Worker学习笔记 | 浏览器切换标签,定时器失效的解决办法

文章目录 web Workerweb Worker介绍 - 多线程解决方案浏览器多进程架构 web workers 的使用关闭worker引用其他js文件 浏览器切换标签&#xff0c;定时器失效的解决办法窗口可见性 API解决定时器失效的方案 web Worker web Worker介绍 - 多线程解决方案 Web Workers 是Html5提…

项目收获总结--MySQL的知识收获

一、概述 最近几天公司项目开发上线完成&#xff0c;做个收获总结吧~ 今天先记录MySQL的收获和提升。 二、MySQL表分区 项目中遇到数据量过大导致在查询过程中会出现各种超时的情况&#xff0c;当然是可以使用各种中间件比如MyCat&#xff0c;ShardingJDBC 等分库工具来进行…

项目实战--Spring Boot + Minio文件切片上传下载

1.搭建环境 引入项目依赖 <!-- 操作minio的java客户端--> <dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.2</version> </dependency> <!-- jwt鉴权相应依赖--> &…

Redis三种模式——主从复制、哨兵模式、集群

一、Redis模式 Redis有三种模式&#xff1a;分别是主从同步/复制、哨兵模式、Cluster 主从复制&#xff1a;主从复制是高可用Redis的基础&#xff0c;哨兵和群集都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的负载均衡和简…

Linux安装达梦

文章目录 前言一、docker安装1.下载镜像2.导入镜像3.生成容器 二、ios安装1.环境准备2.iso安装3.配置实例4.注册服务5.启停服务 总结 前言 公司要求我将数据从oracle迁移到达梦数据库&#xff0c;这个国产数据库以前没用过&#xff0c;所以记录一下这次的安装过程。 一、docke…

Bahdanau 注意力中上下文变量 ′的公式解释

公式 (10.4.1) 是 Bahdanau 注意力模型中的一个关键公式&#xff0c;用于计算在解码时间步 ( t’ ) 的上下文变量 (\mathbf{c}_{t’})&#xff1a; [ \mathbf{c}{t’} \sum{t1}^T \alpha(\mathbf{s}_{t’ - 1}, \mathbf{h}_t) \mathbf{h}_t ] 下面对公式进行详细解释&#x…

7月6日 VueConf 技术大会即将在深圳举办

7月6日&#xff0c;VueConf 2024 即将在深圳召开&#xff0c;本次大会正值 Vue.js 十周年&#xff0c;旨在聚焦 Vue.js 社区的成员&#xff0c;分享最新的技术动态、经验以及创新实践。 本次参与 VueConf 大会的是来自全球 Vue.js 核心团队成员、行业专家及前端开发者。其中&a…

排序——数据结构与算法 总结8

目录 8.1 排序相关概念 8.2 插入排序 8.2.1 直接插入排序&#xff1a; 8.2.2 折半插入排序&#xff1a; 8.2.3 希尔排序&#xff1a; 8.3 交换排序 8.3.1 冒泡排序&#xff1a; 8.3.2 快速排序&#xff1a; 8.4 选择排序 8.4.1 简单选择排序 8.4.2 堆排序 8.5 归并…

基于LabVIEW的设备安装螺栓连接设计

介绍了一种基于LabVIEW的辅助设备安装螺栓连接设计案例。通过LabVIEW软件&#xff0c;实现了从螺栓规格预估、强度校核到物料选用的整个流程的软件化&#xff0c;提高了设计效率和安装可靠性。 项目背景 在轨道车辆设备安装中&#xff0c;螺栓连接作为一种常见的紧固方式&…

SpringBoot 中的参数校验:构建健壮应用的基石

前言 在开发Web应用时&#xff0c;处理用户输入是不可避免的一环。然而&#xff0c;用户输入往往充满不确定性&#xff0c;可能是格式不正确、类型不匹配&#xff0c;甚至包含恶意内容。为了确保应用的稳定性和安全性&#xff0c;对输入参数进行有效校验显得尤为重要。Spring …