磁盘存储链式结构——B树与B+树

news2024/11/27 16:36:32

        红黑树处理数据都是在内存中,考虑的都是内存中的运算时间复杂度。如果我们要操作的数据集非常大,大到内存已经没办法处理了该怎么办呢?
        试想一下,为了要在一个拥有几十万个文件的磁盘中查找一个文本文件,设计的算法需要读取磁盘(磁盘寻址)上万次还是读取几十次,这是有本质差异的,此时为了降低对外存设备的访问次数,我们就需要新的数据结构来处理这样的问题,B树B+树(n叉树)。

B树、B+树的本质是通过降低树的层高,减少对磁盘访问次数,来提升查询速度,查找复杂度logm(n/m),m是叉的数量

B树与B+树区别与联系

  1. B树所有节点即存储key也存储value,内存中存不下后存磁盘,名称为B-Tree,并没有B-树这种叫法
  2. B+树只有在叶子节点存储数据value(叶子节点放在磁盘中),非叶子节点用来做索引key(非叶子结点在内存中),相比于B树,B+树更适合做磁盘索引,在大数据的存储和查找中使用较多,比如海量图像查找引擎
  3. B树和B+树的结点添加、删除、查询基本相同

B树的性质

  • 每个结点最多有m棵子树。
  • 具有k个子树的非叶结点包含k -1个键。
  • 每个非叶子结点(除了根)具有至少⌈ m/2⌉子树,即最少有⌈ m/2⌉-1个关键字。
  • 如果根不是终端结点,则根结点至少有一个关键字,即至少有2棵子树。【根的关键字取值范围是[1,m-1],子树的取值范围是[2,m]】
  • 所有叶子结点都出现在同一水平,没有任何信息(高度一致)。

B+树的性质

每个结点最多有m棵子树。
如果根不是终端结点,则根结点至少有一个关键字,即至少有2棵子树。【根的关键字取值范围是[1,m-1]】
每个关键字对应一棵子树(与B树的不同),具有k个子树的非叶结点包含k 个键。
每个非叶子结点(除了根)具有至少**⌈ m/2⌉子树**,即最少有**⌈m/2⌉个关键字**。
终端结点包含全部关键字及相应记录的指针,叶结点中将关键字按大小顺序排序,并且相邻叶结点按大小顺序相互链接起来。
所有分支结点(可以视为索引的索引)中金包含他的各个子节点(即下一级的索引块)中关键字最大值,及指向其子结点的指针。

B树与B+树对比

  • 在B+树中,叶结点包含信息,所有非叶结点仅起索引作用,非叶子结点中的每个索引项只是包含了对应子树最大关键字和指向该孩子树的指针,不含有该关键字对应记录的存储地址。
  • 在B+树中,终端结点包含全部关键字及相应记录的指针,即非终端结点出现过的关键字也会在这重复出现一次。而B树是不重复的

一、B树的定义

我们以六叉树为例:我们说的6叉树是指,每个节点最多可拥有的子树个数,6叉树每个节点最多可拥有6颗子树,而每个节点中最多存储5个数据,如下图

至于为什么插入后会是如下图形,如何插入,请观看下面b站视频。

B树(B-树) - 来由, 定义, 插入, 构建_哔哩哔哩_bilibili

//6叉树的定义
#define SUB_M   3
 
struct _btree_node{
    /*
    int keys[2 * SUB_M-1];      //最多5个关键字
    struct _btree_node *childrens[2 * SUB_M];   //最多6颗子树    6叉树
    */
 
    int *keys;      //5
    struct _btree_node **childrens;   //6
 
    int num;    //实际存储的节点数量 <= M-1
    int leaf;   //是否为叶子节点
}
 
struct _btree {
    struct _btree_node *root;
}

二、B树添加结点

B树添加结点,只会添加在叶子结点上,重点是结点满了后,会发生分裂并向父结点上位。

  • 添加的数据都是添加在叶子节点上,不会添加到根节点或中间节点

  • 结点数据个数==M-1(结点满了的情况),发生分裂(把(M-1)/2处数据放到父结点,其他数据分成两个结点)

     

  • 添加U,高度+1


添加结点代码如下:

/*用于结点分裂时创建新结点*/
btree_node *btree_create_node(int leaf){
    btree_node *node = (btree_node*)calloc(1, sizeof(btree_node));  //malloc需要手动清零,calloc会自动清零,set 0
    if(node == NULL) return NULL;   //在内存分配的时候一定要判断,当内存不够用的时候,malloc/calloc就会出错
 
    node->leaf = leaf;
    node->keys = calloc(2 * SUB_M -1, sizeof(int));
    node->childrens = (btree_node**)calloc(2 * SUB_M -1, sizeof(btree_node*));
    node->num = 0;
 
    return node;
}
 
/*删除结点*/
void btree_destory_node(btree_node *node){
    free(node->childrens);
    free(node->keys);
    free(node);
}
 
/*
非根结点分裂、上位:发生在B树添加元素的过程中,结点满了,需要先分裂再添加
btree *T:根节点
btree_node *x:被删除元素的父结点
int idx:位于父结点的第几颗子树
*/
void btree_split_child(btree *T, btree_node *x, int idx){
    btree_node *y = x->childrens[idx];    //满了的结点node_y
    btree_node *z = btree_create_node(y->leaf); //创建分裂后的新结点
 
    //z
    z->num = SUB_M - 1;
 
    int i=0;
    for(i=0; i < SUB_M-1; i++){
        z->keys[i] = y->keys[SUB_M+i];
    }
    if(y->leaf == 0){   //inner 是内结点,子树指针也要copy过去
        for(i=0; i < SUB_M-1; i++){
            z->childrens[i] = y->childrens[SUB_M+i];
        }
    }
 
    //y
    y->num = SUB_M;
 
    //中间元素上位
        //childrens 子树
    for(i=x->num; i >= idx+1; i--){             //寻找上为父结点的位置
        x->childrens[i+1] = x->childrens[i];    //父结点元素后移
    }
    x->childrens[i+1] = z;
        //key   
    for(i=x->num-1; i >= idx; i--){     //寻找上为父结点的位置
        x->keys[i+1] = x->keys[i];      //父结点后边元素后移 
    }
    x->keys[i] = y->keys[SUB_M];
    x->num += 1;
}
 
/*
 
*/
void btree_insert(btree *T, int key){
    btree_node *r = T->root;
    
    //根节点分裂(根节点满了);创建一个空结点 指向root, 
    if(r->num == 2*SUB_M-1){
        btree_node *node = btree_create_node(0);
        T->root = node;
        node->childrens[0] = r;
 
        btree_split_child(T, node, 0);
    }
}

三、B树删除结点

B树删除结点,删除叶子结点和中间结点类似,重点为向父结点借位再合并操作。

  • 先合并或者借位转换成一个B树可以删除的状态,在进行删除


删除B结点,其中路径中间结点“FI”的关键字数量为(M-1)/2-1个,为了避免以后出现资源不足的现象,需要对"FI"先进行借位合并
 


代码如下:

///b树 删除     
 
 
void btree_merge(btree *T, btree_node *x, int idx){
 
    btree_node *left = x->childrens[idx];
    btree_node *right = x->childrens[idx+1];
 
    left->keys[left->num] = x->keys[idx];
 
    int i=0;
    for(i=0; i<right->num; i++){
        left->keys[SUB_M+i] = right->keys[i];
    }
    if(!left->leaf){    //非叶子结点,要合并孩子结点指针
        for(i=0; i<SUB_M; i++){
            left->childrens[SUB_M+i] = right->childrens[i];
        }
    }
    left->num += SUB_M;
 
    btree_destory_node(right);
 
    //x key前移
    for(i=idx+1; i < x->num; i++){
        x->keys[i-1] = x->keys[i];
        x->childrens[i] = x->childrens[i+1];
    }
}

四、完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define DEGREE		3
typedef int KEY_VALUE;

typedef struct _btree_node {
	KEY_VALUE* keys;
	struct _btree_node** childrens;
	int num;
	int leaf;
} btree_node;

typedef struct _btree {
	btree_node* root;
	int t;
} btree;

btree_node* btree_create_node(int t, int leaf) {

	btree_node* node = (btree_node*)calloc(1, sizeof(btree_node));
	if (node == NULL) assert(0);

	node->leaf = leaf;
	node->keys = (KEY_VALUE*)calloc(1, (2 * t - 1) * sizeof(KEY_VALUE));
	node->childrens = (btree_node**)calloc(1, (2 * t) * sizeof(btree_node*));
	node->num = 0;

	return node;
}

void btree_destroy_node(btree_node* node) {

	assert(node);
	free(node->keys);
	//free(node->childrens);
	free(node);
}

void btree_create(btree* T, int t) {
	T->t = t;

	btree_node* x = btree_create_node(t, 1);
	T->root = x;
}

void btree_split_child(btree* T, btree_node* x, int i) {
	int t = T->t;

	btree_node* y = x->childrens[i];
	btree_node* z = btree_create_node(t, y->leaf);

	z->num = t - 1;

	int j = 0;
	for (j = 0; j < t - 1; j++) {
		z->keys[j] = y->keys[j + t];
	}
	if (y->leaf == 0) {
		for (j = 0; j < t; j++) {
			z->childrens[j] = y->childrens[j + t];
		}
	}

	y->num = t - 1;
	for (j = x->num; j >= i + 1; j--) {
		x->childrens[j + 1] = x->childrens[j];
	}

	x->childrens[i + 1] = z;

	for (j = x->num - 1; j >= i; j--) {
		x->keys[j + 1] = x->keys[j];
	}
	x->keys[i] = y->keys[t - 1];
	x->num += 1;

}

void btree_insert_nonfull(btree* T, btree_node* x, KEY_VALUE k) {

	int i = x->num - 1;

	if (x->leaf == 1) {

		while (i >= 0 && x->keys[i] > k) {
			x->keys[i + 1] = x->keys[i];
			i--;
		}
		x->keys[i + 1] = k;
		x->num += 1;

	}
	else {
		while (i >= 0 && x->keys[i] > k) i--;

		if (x->childrens[i + 1]->num == (2 * (T->t)) - 1) {
			btree_split_child(T, x, i + 1);
			if (k > x->keys[i + 1]) i++;
		}

		btree_insert_nonfull(T, x->childrens[i + 1], k);
	}
}

void btree_insert(btree* T, KEY_VALUE key) {
	//int t = T->t;

	btree_node* r = T->root;
	if (r->num == 2 * T->t - 1) {

		btree_node* node = btree_create_node(T->t, 0);
		T->root = node;

		node->childrens[0] = r;

		btree_split_child(T, node, 0);

		int i = 0;
		if (node->keys[0] < key) i++;
		btree_insert_nonfull(T, node->childrens[i], key);

	}
	else {
		btree_insert_nonfull(T, r, key);
	}
}

void btree_traverse(btree_node* x) {
	int i = 0;

	for (i = 0; i < x->num; i++) {
		if (x->leaf == 0)
			btree_traverse(x->childrens[i]);
		printf("%C ", x->keys[i]);
	}

	if (x->leaf == 0) btree_traverse(x->childrens[i]);
}

void btree_print(btree* T, btree_node* node, int layer)
{
	btree_node* p = node;
	int i;
	if (p) {
		printf("\nlayer = %d keynum = %d is_leaf = %d\n", layer, p->num, p->leaf);
		for (i = 0; i < node->num; i++)
			printf("%c ", p->keys[i]);
		printf("\n");
#if 0
		printf("%p\n", p);
		for (i = 0; i <= 2 * T->t; i++)
			printf("%p ", p->childrens[i]);
		printf("\n");
#endif
		layer++;
		for (i = 0; i <= p->num; i++)
			if (p->childrens[i])
				btree_print(T, p->childrens[i], layer);
	}
	else printf("the tree is empty\n");
}


int btree_bin_search(btree_node* node, int low, int high, KEY_VALUE key) {
	int mid;
	if (low > high || low < 0 || high < 0) {
		return -1;
	}

	while (low <= high) {
		mid = (low + high) / 2;
		if (key > node->keys[mid]) {
			low = mid + 1;
		}
		else {
			high = mid - 1;
		}
	}

	return low;
}


//{child[idx], key[idx], child[idx+1]} 
void btree_merge(btree* T, btree_node* node, int idx) {

	btree_node* left = node->childrens[idx];
	btree_node* right = node->childrens[idx + 1];

	int i = 0;

	/data merge
	left->keys[T->t - 1] = node->keys[idx];
	for (i = 0; i < T->t - 1; i++) {
		left->keys[T->t + i] = right->keys[i];
	}
	if (!left->leaf) {
		for (i = 0; i < T->t; i++) {
			left->childrens[T->t + i] = right->childrens[i];
		}
	}
	left->num += T->t;

	//destroy right
	btree_destroy_node(right);

	//node 
	for (i = idx + 1; i < node->num; i++) {
		node->keys[i - 1] = node->keys[i];
		node->childrens[i] = node->childrens[i + 1];
	}
	node->childrens[i + 1] = NULL;
	node->num -= 1;

	if (node->num == 0) {
		T->root = left;
		btree_destroy_node(node);
	}
}

void btree_delete_key(btree* T, btree_node* node, KEY_VALUE key) {

	if (node == NULL) return;

	int idx = 0, i;

	while (idx < node->num && key > node->keys[idx]) {
		idx++;
	}

	if (idx < node->num && key == node->keys[idx]) {

		if (node->leaf) {

			for (i = idx; i < node->num - 1; i++) {
				node->keys[i] = node->keys[i + 1];
			}

			node->keys[node->num - 1] = 0;
			node->num--;

			if (node->num == 0) { //root
				free(node);
				T->root = NULL;
			}

			return;
		}
		else if (node->childrens[idx]->num >= T->t) {

			btree_node* left = node->childrens[idx];
			node->keys[idx] = left->keys[left->num - 1];

			btree_delete_key(T, left, left->keys[left->num - 1]);

		}
		else if (node->childrens[idx + 1]->num >= T->t) {

			btree_node* right = node->childrens[idx + 1];
			node->keys[idx] = right->keys[0];

			btree_delete_key(T, right, right->keys[0]);

		}
		else {

			btree_merge(T, node, idx);
			btree_delete_key(T, node->childrens[idx], key);

		}

	}
	else {

		btree_node* child = node->childrens[idx];
		if (child == NULL) {
			printf("Cannot del key = %d\n", key);
			return;
		}

		if (child->num == T->t - 1) {

			btree_node* left = NULL;
			btree_node* right = NULL;
			if (idx - 1 >= 0)
				left = node->childrens[idx - 1];
			if (idx + 1 <= node->num)
				right = node->childrens[idx + 1];

			if ((left && left->num >= T->t) ||
				(right && right->num >= T->t)) {

				int richR = 0;
				if (right) richR = 1;
				if (left && right) richR = (right->num > left->num) ? 1 : 0;

				if (right && right->num >= T->t && richR) { //borrow from next
					child->keys[child->num] = node->keys[idx];
					child->childrens[child->num + 1] = right->childrens[0];
					child->num++;

					node->keys[idx] = right->keys[0];
					for (i = 0; i < right->num - 1; i++) {
						right->keys[i] = right->keys[i + 1];
						right->childrens[i] = right->childrens[i + 1];
					}

					right->keys[right->num - 1] = 0;
					right->childrens[right->num - 1] = right->childrens[right->num];
					right->childrens[right->num] = NULL;
					right->num--;

				}
				else { //borrow from prev

					for (i = child->num; i > 0; i--) {
						child->keys[i] = child->keys[i - 1];
						child->childrens[i + 1] = child->childrens[i];
					}

					child->childrens[1] = child->childrens[0];
					child->childrens[0] = left->childrens[left->num];
					child->keys[0] = node->keys[idx - 1];

					child->num++;

					node->keys[idx - 1] = left->keys[left->num - 1];
					left->keys[left->num - 1] = 0;
					left->childrens[left->num] = NULL;
					left->num--;
				}

			}
			else if ((!left || (left->num == T->t - 1))
				&& (!right || (right->num == T->t - 1))) {

				if (left && left->num == T->t - 1) {
					btree_merge(T, node, idx - 1);
					child = left;
				}
				else if (right && right->num == T->t - 1) {
					btree_merge(T, node, idx);
				}
			}
		}

		btree_delete_key(T, child, key);
	}

}

int btree_delete(btree* T, KEY_VALUE key) {
	if (!T->root) return -1;

	btree_delete_key(T, T->root, key);
	return 0;
}

int main() {
	btree T = { 0 };

	btree_create(&T, 3);
	srand(48);

	int i = 0;
	char key[27] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	for (i = 0; i < 26; i++) {
		//key[i] = rand() % 1000;
		printf("%c ", key[i]);
		btree_insert(&T, key[i]);
	}

	btree_print(&T, T.root, 0);

	for (i = 0; i < 26; i++) {
		printf("\n---------------------------------\n");
		btree_delete(&T, key[25 - i]);
		//btree_traverse(T.root);
		btree_print(&T, T.root, 0);
	}
}

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

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

相关文章

测试-BUG篇

文章目录 软件测试的生命周期BUGbug的概念描述bug的要素bug级别bug的生命周期 与开发产生争执怎么办&#xff08;高频考题&#xff09; 软件测试的生命周期 软件测试贯穿于软件的整个生命周期 BUG bug的概念 是指计算机程序中存在的一个错误(error)、缺陷(flaw)、疏忽(mista…

容器领航者:Kubernetes集群部署秘籍

目录 前言 一、ubernetes简介 1.1 应用部署方式演变 1.2 容器编排应用 1.3 kubenetes简介 二、Kubernetes核心概念 2.1 K8S各个组件用途 2.2 K8S 各组件之间的调用关系 2.3 K8S 的常用名词感念 2.4 k8S的分层架构​编辑 三、部署前的准备 3.1 k8s中容器的管理方式 3…

自然种子启发,4D 打印软机器人,环境探索新工具

想象一下&#xff0c;一颗小小的种子&#xff0c;竟能成为软机器人的灵感源泉&#xff01;在科技的奇妙世界里&#xff0c;受牻牛儿苗科种子启发&#xff0c;研究人员利用 4D 打印技术&#xff0c;打造出了具有独特性能的软机器人——《4D Printing of Humidity‐Driven Seed I…

免费!推荐10个可商用模特图片素材网站!

平时我也会用到一些可商用的模特图片素材&#xff0c;包括正版商用和免费商用的都有&#xff0c;接下来罗列国内外10个主流图片下载站点&#xff0c;保证你可以快速得到你想要的正版模特素材&#xff01; 1.包图网 传送门&#xff1a;https://ibaotu.com/sy 国内的一个高清分…

Acwing 区间DP 计数类DP

1.区间DP Acwing 282.石子合并 思路分析&#xff1a; f(i,j)表示将第i堆石子到第j堆石子合并为一堆时的最小代价&#xff1b;状态划分&#xff1a;选一个分割点k&#xff0c;将[i-k]和[k1,j]这两个区间的石子合并&#xff0c;然后加上两个区间的合并总代价&#xff08;采用…

在线Xpath匹配定位测试工具

具体请前往&#xff1a;在线Xpath-匹配-定位-调试/测试工具

速速收藏!这些2024年上映的AI电影与短剧,申请加入你的国庆假期宅家计划!

2024年上映的AI电影 01 科幻惊悚电影《致命AI Afraid》 导演&#xff1a;克里斯韦兹上映日期:2024-08-30(美国)片长:84分钟剧情简介&#xff1a;Curtis一家被选中去测试一种革新性的居家设备&#xff1a;数字家庭助手AIA&#xff0c;包括各种感应设备和摄像头等&#xff0c;…

【2024】前端学习笔记13-JavaScript修改网页样式

学习笔记 1.修改网页样式1.1.修改内联样式(`style`属性)1.2.使用`cssText`属性:2.修改样式类(`classList`属性)2.1.添加和移除类名2.2.切换类名(`toggle`方法)1.修改网页样式 1.1.修改内联样式(style属性) 直接修改元素的style属性: 可以通过获取元素对象,然后直…

java计算机毕设课设—推箱子游戏(附源码、文章、相关截图、部署视频)

这是什么系统&#xff1f; 基于JAVA的推箱子游戏是一个经典的益智游戏&#xff0c;旨在通过推动箱子到指定位置来锻炼玩家的思维和策略能力。本游戏提供了多种不同难度的关卡&#xff0c;以满足不同玩家的需求。整个程序包括五个主要模块&#xff1a;初始化模块、画图模块、移…

Dart 高级语法

Dart 是一种由 Google 开发的开源编程语言&#xff0c;旨在为构建高性能、可移植的应用程序提供支持。它被设计用于多种平台&#xff0c;包括Web、移动设备&#xff08;通过 Flutter 框架&#xff09;、服务器端应用以及桌面应用。以下是一些 Dart 中比较高级的语言特性和模式。…

Ambari搭建Hadoop集群 — — 问题总结

Ambari搭建Hadoop集群 — — 问题总结 一、部署教程&#xff1a; 参考链接&#xff1a;基于Ambari搭建大数据分析平台-CSDN博客 二、问题总结&#xff1a; 1. VMwear Workstation 查看网关 2. 资源分配 参考&#xff1a; 硬盘&#xff1a;master&#xff08;29 GB&#xff…

连续时间傅里叶变换

一、非周期信号的表示&#xff1a;连续时间傅里叶变换 傅里叶变换对&#xff1a; 通常称为的频谱 二、傅里叶变换的收敛 1、绝对可积 2、在任何有限区间内&#xff0c;只有有限个最大值和最小值 3、在任何有限区间内&#xff0c;有有限个不连续点&#xff0c;且在每个不连…

C语言动态内存管理(26)

文章目录 前言一、引子二、malloc三、calloc四、realloc五、free六、常见的动态内存错误对NULL指针进行解引用操作对动态开辟空间的越界访问对非动态开辟的内存使用free释放使用free释放动态开辟内存的一部分对同一块内存多次释放动态开辟内存忘记释放&#xff08;内存泄漏&…

k8s 之安装helm服务

helm安装包下载helm官网_zh 作者&#xff1a;程序那点事儿 日期&#xff1a;2024/01/30 00:51 下载安装包 wget https://get.helm.sh/helm-v3.2.3-linux-amd64.tar.gz 解压安装包 tar -zxcf helm-v3.2.3-linux-amd64.tar.gz 进入到解压目录 cd linux-amd64 将helm目录拷贝…

101. 对称二叉树【 力扣(LeetCode) 】

文章目录 零、原题链接一、题目描述二、测试用例三、解题思路3.1 递归3.2 迭代 四、参考代码4.1 递归4.2 迭代 零、原题链接 101. 对称二叉树 一、题目描述 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 进阶&#xff1a;你可以运用递归和迭代两种方法解决…

YOLO11改进|注意力机制篇|引入上下文锚注意力机制CAA

目录 一、【CAA】注意力机制1.1【CAA】注意力介绍1.2【CAA】核心代码 二、添加【CAA】注意力机制2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、【CAA】注意力机制 1.1【CAA】注意力介绍 CAA注意力机制的结构图如下&#xff0c;下面根据…

Selenium WebDriver和Chrome对照表

PS&#xff1a;我的没下载WebDriver 也没配置环境变量 也能用Selenium 网上有说把WebDriver放到chrome的安装目录并将路径配到path中【可能之前用playwright下载过】 查看浏览器版本号 在浏览器的地址栏&#xff0c;输入chrome://version/&#xff0c;回车后即可查看到对应版…

Java项目实战II基于Java+Spring Boot+MySQL的智能推荐的卫生健康系统(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者 一、前言 在健康意识日益增强的今天&#xff0c;如何为用户提供个性化、精准的卫生健康建议成为了一个亟待解决…

java中的多层循环控制,包括金字塔和九九乘法表的打印

多重循环控制 多重循环控制练习 多重循环控制 1.将一个循环放在另一个循环体内&#xff0c;就形成了嵌套循环。其中&#xff0c;for&#xff0c;while&#xff0c;do…while均可以作为外层循环和内层循环。【建议一般用两层&#xff0c;最多不要超过3层&#xff0c;否则代码的…

【前端】前端数据转化为后端数据

【前端】前端数据转化为后端数据 写在最前面格式化数组代码解释hasOwnProperty是什么&#xff1f; &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 2024每日百字篆刻时光&#xff0c;感谢你的陪伴与支持 ~ &#x1f680; 欢迎一起踏上探险之旅&#xff0c;挖掘无限…