当前位置: 首页 > news >正文

[Linux系统编程——Lesson15.文件缓冲区]

目录

前言

一、缓冲区

1.1 🤔一个奇怪的现象!

1.2 什么是文件缓冲区❓

1️⃣解释现象

2️⃣缓冲区的核心本质

1.3 🔥文件缓冲区vs用户级缓冲区🔥

 1.4 🤔缓冲区的位置

1.5、🕵️‍♀️缓冲区的刷新策略

二、🤔 为什么要引⼊缓冲区机制

1️⃣不使用缓冲区的核心痛点

2️⃣缓冲区机制的核心作用(解决痛点的方案)

1. 减少系统调用次数,降低 CPU 切换损耗

2. 实现 CPU 与 I/O 设备的 “并行工作”,避免 CPU 空闲

3️⃣典型场景案例(更直观的理解)

4️⃣总结:缓冲区的核心价值

三、🐧缓冲区是独立的

四、🐧模拟实现 MY_FILE 及其接口

4.1🔥定义 MY_FILE 结构

4.2 🔥my_fopen 模拟实现

4.3 🔥my_fflush模拟实现

4.4 🔥my_fwrite 模拟实现

4.5 🔥my_fclose 模拟实现

六、🕵️‍♀️验证 MY_FILE 及其接口

七、✍️文件缓冲区核心总结

1️⃣核心作用:为何需要文件缓冲区?

2️⃣两种核心缓冲模式

3️⃣关键操作:缓冲区的刷新与关闭

4️⃣不同层面的缓冲区

结束语


前言

经过上一章节的学习,我们已经初步了解到了Linux文件系统[Linux系统编程——Lesson14.基础IO:系统文件IO],

在学习C语言的时候,我们经常会听到缓冲区这一概念,尤其是初学的时候老师会给我们展示:

int main()
{printf("hello world");sleep(5);return 0;
}

代码运行printf时居然没有打印内容,而是在5秒之后内容才输出的。老师会告诉我们printf内容时先放在缓冲区里面的 \n程序退出时会刷新缓冲区,所以才在5秒之后打印内容。
但是对于缓冲区到底是什么,为什么要有缓冲区,缓冲区在哪等等我们还是无法理解。本文将对缓冲区各方面知识进行全面剖析,深入理解文件缓冲区及其作用与原理。并将根据所学来实现一个自己的文件接口✍️


一、缓冲区

1.1 🤔一个奇怪的现象!

  • 首先我们使用C库函数 fprintf 系统调用 write 在标准输出流(屏幕)中各输出一条语句。
#include <stdio.h>
#include <string.h>
#include <unistd.h>int main()
{// C库fprintf(stdout, "hello fprintf\n");// 系统调用const char *msg = "hello write\n";write(1, msg, strlen(msg));return 0;
}
  • 接下来我们将输出的内容重定向到一个文件中,如下:

  • 到目前为止,一切都还很正常。接下来我们将在程序中调用 fork 函数为当前进程创建子进程,然后重复上述的操作。
#include <stdio.h>
#include <string.h>
#include <unistd.h>int main()
{// C库fprintf(stdout, "hello fprintf\n");// 系统调用const char *msg = "hello write\n";write(1, msg, strlen(msg));fork();return 0;
}

此时奇怪的现象终于出现了。为什么在 fork 后,"hello write" 只输出了一次,而 "hello fprintf" 却输出了两次呢?接下来就不得不本章的主角——文件缓冲区了。

1.2 什么是文件缓冲区❓

        当我们调用 fprintf 输出语句时,其内部实现并不会将要输出的内容直接写入到文件中,而是会暂存到缓冲区中。而C库结合相应的刷新策略将缓冲区中的数据通过系统调用(如 write )写入到目标文件中。


1️⃣解释现象

  • 首先write 系统调用,并不是C库提供的函数,所以它并不会考虑缓冲区的问题。在 fork 之前就已经将数据写入到文件中了。
  • 但是 fprintf 则面对的是两种不同的情况,当我们输出在屏幕上时,此时采取的刷新策略是行缓冲(❓),所以在 fork 之前,它也已经输出到屏幕上了,并不会有奇怪的现象。
  • 但当我们将输出的数据重定向 test.txt 文件中时,此时刷新策略行缓冲变为了全缓冲,由于缓冲区此时并没有被写满,所以暂时不会输出数据。在 fork 之后父子进程在结束时会刷新各自的缓冲区,此时会发生写时拷贝,所以 “hello fprintf” 被打印了两次。

概念缓冲区内存空间的⼀部分。也就是说,在内存空间中预留了⼀定的存储空间,这些存储空间⽤来缓冲输⼊或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输⼊设备还是输出设备,分为输⼊缓冲区和输出缓冲区

2️⃣缓冲区的核心本质

缓冲区是内存中划分出的一块专用存储空间,核心作用是解决数据产生速度与处理速度不匹配的问题,避免设备或程序之间互相等待。

  • 平衡速度差异:比如键盘输入速度远慢于 CPU 处理速度,输入缓冲区会先暂存按键数据,待 CPU 空闲时一次性读取,避免 CPU 频繁等待。
  • 减少 IO 次数:硬盘等外部设备的读写速度远低于内存,输出缓冲区会先收集足够多的数据,再一次性写入硬盘,大幅减少对硬件的频繁调用,提升效率。

下面就举个例子来帮助大家理解🕵️‍♀️。

  • 假设你在新疆,而你的朋友在北京,并且这时候并没有快递这个概念,如果说你要给你的朋友送一下特产,你就需要自己花很长时间千里迢迢的将这个特产送过去,当特产送到了你朋友的手上你才会认为东西已经送了。而过了一两年后,突然有了快递这个行业,而你和你朋友的周边就正好有一家快递站,这时你又想给你的朋友送点特产,那么你就可以把东西准备好,让后放到快递站让快递员给你送过去,当你把快递放到了快递站的时候,你就会认为这个东西已经送了,在这个例子中,快递站就可以被理解为缓冲区,没有缓冲区的时候,就需要从一个设备中将数据直接拷贝到另一个设备中,而有了缓冲区后,只要将数据写到缓冲区中,那么这个数据就会由操作系统来帮你写入。缓冲区的主要作用就是提高使用者的效率。
  • 那么继续,快递站并不会因为只收到了你的东西,就直接将东西快马加鞭的送过去,这样做的成本会非常的高,快递站能够存储东西,所以快递站会根据东西的情况来决定是否发送,可以是一个货架满了再发送,也可以是整个快递站满了之后再发送,还有特殊的发货条件就是,若是很多天都没有货导致一直没有发送,你也可以打电话投诉让他直接发送,也可能是快递站要倒闭了,在倒闭之前讲东西全部发送出去,这几种情况就分别对应了缓冲区的刷新方式:

现在相信大家已经能够理解缓冲区的概念

1.3 🔥文件缓冲区vs用户级缓冲区🔥

再通过一个示例带大家深入理解缓冲区🤔

  • printf打印的内容是先放在缓冲区里面的,当程序结束或遇到\n后才刷新到文件中去,那么如果在程序结束之前将stdout流关闭,那是不是就打印不出来内容了❓

  • 上面的代码确实没有输出任何内容,那么如果将库函数换成write系统接口会不会打印出来内容❓

  • 输出结果如下

使用write确实打印出了内容,而使用printf却没有打印出内容,这是为什么
是因为write输出的内容不需要先写到缓冲区里面吗

🕵️‍♀️实际上在计算机中有两个缓冲区分别是文件缓冲区和语言提供的用户级别的缓冲区(以下称其为用户级缓冲区)我们常说的缓冲区通常指的是用户级别的缓冲区

  • 对于文件缓冲区来说文件缓冲区就在操作系统内部一些操作系统接口进行读取和写入的时候都是从文件缓冲区中进行读取和写入的,所以write向文件中进行写入时要先向文件缓冲区中进行写入。
  • 对于用户级缓冲区来说:用户级缓冲区不是操作系统提供的可以理解为操作系统都不知道有这个缓冲区,该缓冲区是语言提供的在调用一些库函数进行写入的时候要先将数据写入到该缓冲区中,当缓冲区中的内容满足一定的条件的时候就会被刷新到文件缓冲区中,再通过文件缓冲区写入到文件中。

那为什么close关闭stdout文件后,write能成功写入数据,而printf无法写入数据❓

  • 这是因为close()在关闭文件的时候会刷新该文件的文件缓冲区,所以write()的数据能够被刷新到文件中那么为什么close()不去刷新用户级缓冲区,在前面我们说了用户级缓冲区操作系统根本就不知道也看不见,那么操作系统接口也同样不知道还有其他的缓冲区,close()接口无法对用户级缓冲区进行刷新;所以printf的内容在用户级缓冲区中没有被刷新,当程序结束时还是没有被刷新。

两者也存在一定联系用户级缓冲区中的数据最终需要通过系统调用等方式刷新到文件缓冲区,再由文件缓冲区写入到文件中。例如 C 语言中,使用printf函数写入的数据先存于用户级缓冲区,满足条件后会被刷新到文件缓冲区,最后才写入文件。

 1.4 🤔缓冲区的位置

  • 文件缓冲区毫无疑问是在操作系统内部,由操作系统进行管理;
  • 对于用户级缓冲区是语言提供的,那么一定是使用的语言进行管理的,不同的语言可能还存在一些差异。

在C语言中是使用结构体对文件进行管理的FILE结构体,缓冲区就放在该结构体里面,是动态开辟出来的堆空间

  • 任何情况下,我们进行输入输出的操作时,都有一个FILEFILE是一个结构体,里面包含了fd(文件描述符),还提供了一段缓冲区。在C标准库中,有一些IO操作函数,它们在设计上默认操作标准输入(stdin)、标准输出(stdout)或标准错误(stderr)。这些函数在调用时无需显式传递FILE类型的指针,因为它们在内部已经预定义了这些标准流。

  • 接下来我们在C标准库中查找一下FILE,是否符合上面的内容。

  • 所以刷新缓冲区原理图如下:

所以系统调用接口_exit()终止进程时不会刷新用户缓冲区,因为_exit()是内核层面的系统调用,它不知道内核外还存在用户缓冲区

1.5、🕵️‍♀️缓冲区的刷新策略

注意⚠️:此处的缓冲区指的是用户级别的缓冲区,关于文件缓冲区刷新归操作系统管,我们不用操心。

  • 无缓冲(每次输出都会直接写入文件,没有缓冲);
  • 行缓冲(当输出的数据包含换行符时,或者缓冲区满时,数据才会被写入文件);
  • 全缓冲(数据会在缓冲区满时或者调用fflush函数时才会被写入文件);

首先终端设备上(显示器)一般采用的刷新策略是行缓冲,而普通文件(磁盘文件)采用的是全缓冲。

除了上述列举的默认刷新⽅式,下列特殊情况也会引发缓冲区的刷新:

1. 缓冲区满时;

2. 执⾏flush语句;

3. 进程结束


二、🤔 为什么要引⼊缓冲区机制

引入缓冲区机制的核心目的,是解决高速 CPU 与低速 I/O 设备(如磁盘、打印机)之间的速度不匹配问题,通过减少资源损耗和优化数据传输效率,提升整体系统性能。

1️⃣不使用缓冲区的核心痛点

直接通过系统调用操作 I/O 设备时,会面临两大关键问题,导致性能严重损耗:

1.频繁系统调用与上下文切换

  • 每次读写操作都需触发系统调用(如writeread),而系统调用会强制 CPU 从 “用户空间” 切换到 “内核空间”,完成进程上下文的保存与恢复。
  • 上下文切换本身需要消耗 CPU 时间,若存在大量高频读写(如循环写入单字符),切换开销会累积,大幅占用 CPU 资源,导致程序执行效率下降。

2./I/O 设备速度远低于 CPU

  • 磁盘、打印机等 I/O 设备的物理操作速度(如磁盘磁头寻址、打印机机械运动)远慢于 CPU 的运算速度,两者差距可达数个数量级。
  • 若 CPU 直接等待 I/O 设备完成操作(即 “同步等待”),会陷入长时间空闲,无法处理其他任务,造成 CPU 资源的严重浪费。

2️⃣缓冲区机制的核心作用(解决痛点的方案)

缓冲区本质是一块内存区域,通过 “暂存数据、批量传输” 的方式,从两个维度优化性能:

1. 减少系统调用次数,降低 CPU 切换损耗
  • 缓冲区会先将多次零散的读写请求 “合并”:例如读取文件时,一次性从磁盘读取大量数据存入缓冲区,后续程序访问这部分数据时,无需再触发系统调用,直接从内存缓冲区获取。
  • 原本需要 100 次系统调用的零散操作,通过缓冲区可能只需 1 次,大幅减少上下文切换的次数,节省 CPU 的切换开销。
2. 实现 CPU 与 I/O 设备的 “并行工作”,避免 CPU 空闲
  • 当需要向低速设备(如打印机)输出数据时,CPU 只需将数据一次性写入缓冲区,随后即可立即去处理其他任务,无需等待设备完成打印。
  • I/O 设备会自行从缓冲区中 “缓慢读取” 数据并执行操作(如打印机逐行打印),两者通过缓冲区解耦,实现了并行工作,最大化利用 CPU 资源。

3️⃣典型场景案例(更直观的理解)

通过两个常见场景,可清晰看到缓冲区的价值:

场景无缓冲区的操作流程有缓冲区的操作流程核心差异
磁盘文件写入(循环写 1000 个字符)每次写 1 个字符 → 触发 1 次系统调用 → 上下文切换 1 次 → 等待磁盘写入先将 1000 个字符写入缓冲区 → 触发 1 次系统调用 → 1 次上下文切换 → 磁盘批量写入系统调用从 1000 次减少到 1 次,切换开销几乎消失
打印机打印文档CPU 发送 1 行数据 → 等待打印机打印完成 → 再发送下 1 行(CPU 全程等待)CPU 将整份文档写入缓冲区 → 立即去处理其他任务 → 打印机从缓冲区自行读取并打印CPU 无需等待,实现 “并行工作”

4️⃣总结:缓冲区的核心价值

缓冲区并非直接 “加速” I/O 设备,而是通过 **“内存暂存 + 批量传输”** 的策略,解决了计算机体系中 “高速 CPU 与低速 I/O” 的根本性矛盾:

  • 对 CPU:减少上下文切换损耗,避免 CPU 因等待 I/O 而空闲,提升 CPU 利用率;
  • 对 I/O 设备:减少频繁的零散访问,通过批量操作降低设备的物理损耗(如磁盘磁头频繁移动),同时提升设备的实际吞吐量;
  • 对整体系统:最终实现 “CPU 高效运算” 与 “I/O 设备稳定工作” 的协调,显著提升程序与系统的整体性能。

三、🐧缓冲区是独立的

此处的缓冲区指的依旧是用户级缓冲区。缓冲区在进程之间是独立的,这并不难理解,因为上面我们说了缓冲区其实就是C语言中FILE结构体中的堆空间是进程的数据,进程之间是独立的,那么这些数据也必定是独立的。

这里我们就可以再次回顾第一个奇怪的现象了🤔

为什么printf被打印了两次,而write只被打印了一次

答案因为缓冲区属于进程的数据,所以在创建子进程后,缓冲区中的数据父进程和子进程以写实拷贝的方式独自占有一份,最终在进程结束时都刷新到文件缓冲区中了;而write()系统调用接口的数据在创建子进程之前已经写入到了文件缓冲区中,已经不再是进程的数据了,所以只打印了一份。

  • 再来看一个示例:

  • 输出结果:

为什么printf还是打印了两边,明明在打印末尾加了\n呀,为什么还是缓冲区没有刷新

因为在将一号文件替换之后,一号文件变成了普通文件,普通文件的刷新策略不再是行缓冲了,而是全缓冲,所以没有进行刷新。


四、🐧模拟实现 MY_FILE 及其接口

为了更加清晰的认识缓冲区的工作原理,我们尝试模仿C库中的 FILE 来实现一个自己的文件接口。实现分为5个板块:

  1. 模拟实现FILE结构体
  2. 模拟实现fopen打开文件
  3. 模拟实现fflush刷新缓冲区
  4. 模拟实现fwrite向文件中写入
  5. 模拟实现fclose关闭文件

4.1🔥定义 MY_FILE 结构

首先是定义一个 MY_FILE结构体,以下仅作粗略的模仿,所以我们只需要几个关键的字段。

结构体中必须:

1.文件描述符来确定指向的文件;

2.输入输出缓冲区,以及使用情况;

3.缓冲区处理方案。

/* mystdio.h 文件下 */
#pragma once #include <stdio.h>#define NUM 1024 		// 缓冲区大小
#define BUFF_NONE 0x1   // 无缓冲
#define BUFF_LINE 0x2	// 行缓冲
#define BUFF_ALL  0x4	// 全缓冲typedef struct _MY_FILE
{int fd; // 文件描述符int flags; // 文件打开方式char outputbuffer[NUM]; // 文件缓冲区int current; // 当前写入的位置
} MY_FILE;MY_FILE* my_fopen(const char* path, const char* mode);
size_t my_fwrite(const void* ptr, size_t size, size_t num, MY_FILE* stream);
int my_fclose(MY_FILE* fp);
int my_fflush(MY_FILE* fp);

4.2 🔥my_fopen 模拟实现

1.调用系统接口open打开文件;

2.对FILE结构体进行初始化。

/* mystdio.c 文件下*/
#include "mystdio.h"
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <malloc.h>
#include <unistd.h>
#include <assert.h>MY_FILE* my_fopen(const char* path, const char* mode)
{// 1.识别标志位int flag = 0;if(strcmp(mode, "r") == 0) flag |= O_RDONLY;else if(strcmp(mode, "w") == 0) flag |= (O_CREAT | O_WRONLY | O_TRUNC);else if(strcmp(mode, "a") == 0) flag |= (O_CREAT | O_WRONLY | O_APPEND);else {// r+/w+....}// 2.尝试打开文件mode_t m = 0666;int fd = 0;if(flag & O_CREAT) fd = open(path, flag, m);else fd = open(path, flag);if(fd < 0) return NULL;// 3.给用户返回MY_FILE对象,需要先进行构建MY_FILE* mf = (MY_FILE*)malloc(sizeof(MY_FILE));if(mf == NULL){close(fd);return NULL;}// 4.初始化MY_FILE对象mf->fd = fd;mf->flags = 0;mf->flags |= BUFF_LINE;memset(mf->outputbuffer, '\0', sizeof(mf->outputbuffer));mf->current = 0;// 5.返回打开的文件return mf;
}

4.3 🔥my_fflush模拟实现

1.调用系统接口write将缓冲区中的内容写到文件缓冲区中;

2.对FILE结构体进行更新。

int my_fflush(MY_FILE* fp)
{assert(fp);// 将用户缓冲区中的数据,通过系统调用接口冲刷给OSwrite(fp->fd, fp->outputbuffer, fp->current);fp->current = 0;fsync(fp->fd);return 0;
}

4.4 🔥my_fwrite 模拟实现

1.将字符串中的内容写到缓冲区中;

2.判断是否需要刷新缓冲区;

3.更新FILE成员。

size_t my_fwrite(const void* ptr, size_t size, size_t num, MY_FILE* stream) {assert(ptr && stream);size_t user_bytes = size * num;  // 总字节数size_t total_written = 0;        // 总写入字节数while (total_written < user_bytes) {// 缓冲区满则刷新if (stream->current == NUM) {if (my_fflush(stream) != 0) break; // 刷新失败则退出}// 计算可写入缓冲区的字节数size_t remain = NUM - stream->current;size_t write_now = (remain < user_bytes - total_written) ? remain : (user_bytes - total_written);// 从缓冲区当前位置开始拷贝(memcpy(stream->outputbuffer + stream->current, (char*)ptr + total_written, write_now);stream->current += write_now;total_written += write_now;// 行缓冲:遇到换行刷新if ((stream->flags & BUFF_LINE) && (stream->outputbuffer[stream->current - 1] == '\n')) {if (my_fflush(stream) != 0) break;}// 全缓冲:缓冲区满则刷新else if ((stream->flags & BUFF_ALL) && (stream->current == NUM)) {if (my_fflush(stream) != 0) break;}// 无缓冲:直接刷新else if (stream->flags & BUFF_NONE) {if (my_fflush(stream) != 0) break;}}// 返回成功写入的元素个数(修复返回值)return total_written / size;
}

4.5 🔥my_fclose 模拟实现

1.刷新缓冲区中的数据;

2.调用系统接口close关闭文件。

int my_fclose(MY_FILE* fp)
{assert(fp);// 1. 冲刷缓冲区if(fp->current > 0) my_fflush(fp);// 2. 关闭文件close(fp->fd);// 3. 释放堆空间free(fp);// 4. 指针置空fp = NULL;return 0;
}

五、🕵️‍♀️验证 MY_FILE 及其接口

首先我们来验证自己实现的 MY_FILE 及其接口是否正确,像平时使用 FILE 一样来使用我们自己实现的 MY_FILE如下:

/* main.c 文件中 */
#include "mystdio.h"
#include <string.h>
#include <unistd.h>#define MYFILE "log.txt"int main()
{MY_FILE* fp = my_fopen(MYFILE, "w");if(fp == NULL) return 1;const char* str = "hello my write!";int cnt = 500;// 操作文件while(cnt){char buffer[1024];snprintf(buffer, sizeof(buffer), "%s : %d\n", str, cnt--);size_t size = my_fwrite(buffer, strlen(buffer), 1, fp);sleep(1);printf("当前成功写入: %lu 个字节\n", size);}my_fclose(fp);return 0;
}
$ gcc -o mytest main.c mystdio.c
  • 使用分屏模式,在另一半屏幕进行监控。
$ while : ; do cat log.txt; echo "#####"; sleep 1; done

  • 如图所示,可以清晰的观察到每隔一秒,my_fwrite 都会将 buffer 中的内容写入到 log.txt 中。

    六、✍️文件缓冲区核心总结

    1️⃣核心作用:为何需要文件缓冲区?

    文件缓冲区的核心价值在于解决 “内存操作速度” 与 “磁盘 I/O 速度” 不匹配的问题,具体体现在以下 3 点:

    1.减少物理 I/O 次数:将多次小数据的读写请求,合并为一次大数据块的物理磁盘操作,大幅降低磁盘机械运动(寻道、旋转)的耗时。

    2.提升整体效率:内存读写速度远快于磁盘,程序先与缓冲区交互,再由系统 / 语言 runtime 统一处理磁盘操作,减少程序等待时间。

    3.降低系统开销:每一次磁盘 I/O 都需操作系统内核参与,缓冲区合并请求后,能减少内核态与用户态的切换次数。

    2️⃣两种核心缓冲模式

    根据缓冲区的刷新时机,主要分为 “全缓冲” 和 “行缓冲”,另有特殊的 “无缓冲” 模式,三者差异如下所示:

    缓冲模式刷新时机典型应用场景
    全缓冲

    1. 缓冲区被写满;

    2. 主动调用刷新函数(如fflush);

    3. 程序正常退出

    普通磁盘文件(如.txt.log
    行缓冲

    1. 遇到换行符\n

    2. 缓冲区被写满;

    3. 主动刷新 / 程序退出

    标准输入(stdin)、标准输出(stdout,如终端打印)
    无缓冲数据直接写入磁盘,不经过缓冲区标准错误输出(stderr,需即时打印错误信息)、紧急日志

    3️⃣关键操作:缓冲区的刷新与关闭

    1. 主动刷新:通过函数强制将缓冲区数据写入磁盘,避免数据滞留。
    2. 自动刷新:满足缓冲模式的触发条件时,系统自动执行刷新,如全缓冲写满行缓冲遇\n
    3. 关闭时刷新:关闭文件(如fclosefile.close())时,系统会先自动刷新缓冲区,确保数据不丢失后再释放资源。若程序异常崩溃未关闭文件,缓冲区中未刷新的数据可能丢失。

    4️⃣不同层面的缓冲区

    文件缓冲区存在于多个层面,需注意区分,避免混淆:

    • 用户层缓冲区:由编程语言提供(如 C 的FILE结构体),位于用户态内存,程序可直接控制刷新。
    • 内核层缓冲区:由操作系统内核维护(如 Linux 的页缓存 Page Cache),位于内核态内存,用户程序无法直接操作,需通过系统调用(如write)触发内核刷新到磁盘。

    结束语

    以上就是我对于【Linux系统编程】基础IO:文件缓冲区的理解

    感谢你的三连支持!!!

    http://www.dtcms.com/a/495943.html

    相关文章:

  1. 江苏天德建设工程有限公司网站黄冈公司网站建设平台
  2. springboot中server.main.web-application-type=reactive导致的拦截器不生效
  3. 1688黄页网免费网站做外贸服饰哪个个网站好
  4. 杭州做企业网站公司网络营销策略应遵循的原则
  5. 对“机器人VCU”进行一个详细、系统的讲解。
  6. 陕西省城乡住房和建设厅网站网站建设shzanen
  7. 49.字母异位词分组
  8. 移动网站登录入口wordpress孕婴模板
  9. 网站开发的四个高级阶段包括天津网站优化流程
  10. 3.6 第一个JSON Schema(一)
  11. 指针终极理解
  12. 门头沟区专业网站制作网站建设wordpress登录框插件
  13. 没网站做推广wordpress建站 域名
  14. 企业客户管理优化方案:构建高效客户关系体系
  15. 【隐语SecretFlow用户案例】亚信科技构建统一隐私计算框架探索实践
  16. Win11系统安装TranslucentTB报错解决
  17. 国外唯美flash个人网站欣赏资阳房产网站建设
  18. 营销型企业网站怎么建站虚拟商品自动发货网站搭建教程
  19. 网站文件命名规则网站做电源
  20. ProE/Creo模型高效转换3DXML技术方案:在线转换工具全解析
  21. 投资网站策划wordpress配置七牛
  22. 吉林电商网站建设公司哪家好旅游网页制作教程
  23. C#实现SQL Server→Snowflake建表语句转换工具
  24. 一种面向 AIoT 定制化场景的服务架构设计方案
  25. 免费做网站刮刮卡舆情查询
  26. 深圳建设厅网站官网免费虚拟主机官网
  27. 塑胶制造生产ERP:有哪些系统值得关注
  28. 怎么学习做网站vue is做的购物网站
  29. 广州白云手机网站建设佛山专业做网站公司有哪些
  30. 自己可以做一个网站吗如何删除网站后台的文章