前言"为什么程序员的电脑里总有那么多文件?
因为他们深谙fopen()之道,却忘了fclose()之德……
欢迎来到C语言文件操作的奇妙世界!在这里,你将学会如何用代码"创造宇宙"(新建文件)、“时空穿梭”(读写数据),甚至"复活亡灵"(恢复误删文件)。别担心,本教程不会让你的程序像初恋一样段错误,而是带你从fopen()到fseek(),一步步成为文件操作界的"扫地僧"。
友情提示:阅读前请备份你的文件——毕竟,rm -rf的悲剧总是从自信满满开始的。"
一、为什么使用文件如果没有⽂件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运⾏程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们可以使用⽂件。
二、什么是文件磁盘(硬盘)上的⽂件是⽂件。
但是在程序设计中,我们⼀般谈的⽂件有两种:程序文件、数据文件(从⽂件功能的⻆度来分类的)。
1、程序文件程序文件包括源程序文件(后缀为**.c **),目标⽂件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
2、数据文件⽂件的内容不⼀定是程序,⽽是程序运行时读写的数据,⽐如程序运行需要从中读取数据的文件,或者输出内容的文件。
本章讨论的是数据⽂件。
在以前各章所处理数据的输⼊输出都是以终端为对象的,即从终端的键盘输⼊数据,运行结果显⽰到显示器上。其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这⾥处理的就是磁盘上文件。
3、文件名⼀个文件要有⼀个唯⼀的文件标识,以便用户识别和引用。
⽂件名包含3部分:⽂件路径+⽂件名主⼲+文件后缀
例如: c:\code\test.txt
为了方便起见,文件标识常被称为文件名。
三、二进制文件和文本文件?根据数据的组织形式,数据文件被称为文本文件或者⼆进制文件。
数据在内存中以⼆进制的形式存储,如果不加转换的输出到外存的⽂件中,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
⼀个数据在文件中是怎么存储的呢?
字符⼀律以ASCII形式存储,数值型数据既可以⽤ASCII形式存储,也可以使用二进制形式存储。
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符⼀个字节),而⼆进制形式输出,则在磁盘上只占4个字节。
代码语言:javascript复制#include
int main()
{
int a = 10000;
FILE * pf = fopen("test.txt", "wb");
fwrite(&a, 4, 1, pf);//⼆进制的形式写到⽂件中
fclose(pf);
pf = NULL;
return 0;
} 在vs上打开二进制文件
在这里插入图片描述四、文件的打开和关闭1、文件的打开函数原型:
代码语言:javascript复制FILE *fopen( const char *filename, const char *mode );该函数的功能就是打开一个文件,函数的第一个参数是你要打开的文件的文件名,第二个参数是打开这个文件的形式。
我们知道打开一个文件时,系统会为该文件创建一个文件信息区,该函数调用完毕后,如果打开该文件成功,那么返回指向该文件信息区的指针(FILE*类型);如果打开文件失败,那么返回一个空指针(NULL)。
1)文件的打开形式文件打开方式
含义
"r"
read:打开文件进行输入操作。该文件必须存在。
"w"
write:为输出操作创建一个空文件。如果已存在同名文件,则丢弃其内容,并将该文件视为新的空文件。
"a"
append:打开文件以在文件末尾输出。输出操作总是在文件末尾写入数据,并对其进行扩展。忽略重新定位操作(fseek、fsetpos、rewind)。如果文件不存在,则创建该文件。
"r+"
read/update:打开一个文件进行更新(输入和输出)。文件必须存在。
"w+"
write/update:创建一个空文件并打开它以进行更新(输入和输出)。如果同名文件已经存在,则将丢弃其内容,并且该文件将被视为新的空文件。
"a+"
append/update:打开一个文件进行更新(包括输入和输出),所有输出操作都在文件末尾写入数据。重新定位操作(fseek、fsetpos、rewind)会影响下一个输入操作,但输出操作会将位置移回文件末尾。如果文件不存在,则创建该文件。
使用上面的模式说明符,文件将作为文本文件打开。为了将文件作为二进制文件打开,模式字符串中必须包含“b”字符。这个附加的“b”字符可以附加在字符串的末尾(从而形成以下复合模式:“rb”、“wb”、“ab”、“r+b”、“w+b”、“a+b”),也可以插入在“+”符号之前(“rb+”、“wb+”、“ab+”)。
如果序列后面有其他字符,则行为取决于库实现:一些实现可能会忽略其他字符,例如,接受额外的“t”(有时用于显式表示文本文件)。
在某些库实现中,使用更新模式打开或创建文本文件可能会视为二进制文件。
举个几个例子:
1.若我们要以文本形式打开一个名叫data.txt的文件,将要对其进行输入操作,那么打开文件时应该这样写:
代码语言:javascript复制FILE* pf = fopen("data.txt", "r");注:data.txt文件必须存在,不然打开文件失败,fopen函数会返回一个空指针。
2.若我们要以二进制打开一个名叫data.bin的文件,将要对其进行输出操作,那么打开文件时应该这样写:
代码语言:javascript复制FILE* pf = fopen("data.bin", "wb");注:data.bin文件若存在,将销毁文件原有内容,再对其进行输出;data.bin文件若不存在,系统将主动创建一个名叫data.bin的文件。
2)检测fopen返回值的有效性如果文件打开成功,fopen函数会返回指向文件信息区的指针,否则fopen函数会返回一个空指针。所以当使用接收fopen函数的返回值的指针前,我们必须检测其有效性,否则可能非法访问内存。
代码语言:javascript复制 FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//失败返回
}
//使用...3)相对路径与绝对路径填写fopen函数的第一个参数的时候,如果我们要打开的文件与我们正在运行的源代码在同级目录下(以打开data.txt文件为例),那么我们应该这样写:
代码语言:javascript复制FILE* pf = fopen("data.txt", "r");//同级但是如果我们想要打开的文件与当前运行的源代码不在同一级目录下呢?
这时我们的写法有两种,相对路径和绝对路径。
相对路径当待打开的文件位于正在运行的源代码的上一级时:
代码语言:javascript复制FILE* pf = fopen("../data.txt", "r");//上一级当待打开的文件位于正在运行的源代码的上上一级时:
代码语言:javascript复制FILE* pf = fopen("../../data.txt", "r");//上上级当待打开的文件位于正在运行的源代码的下一级时:
代码语言:javascript复制FILE* pf = fopen("Debug/data.txt", "r");//下一级注:这里data.txt文件在Debug文件内,Debug文件与正在运行的源代码在同级目录下。
总结:
要打开上级的文件在原来的基础上加上". ./“,再上一级再加一个”. ./",以此类推。要打开下级的文件,就需从源代码这一级开始,写出目标文件的路径。绝对路径有博友可能觉得相对路径的方法比较麻烦(可能还要去数目标文件与源文件相差的级数),绝对路径就没那么麻烦了,绝对路径就直接写出目标文件的完整路径即可,例如:
代码语言:javascript复制FILE* pf = fopen("D:\\code\\File_function\\File_function\\data.txt", "r");注:文件的路径原本为"D:\code\File_function\File_function\data.txt",但是为了防止字符串中的’\‘及其后面的字符被整体视为为转义字符,所以需要在每个’\‘后面再加一个’\’。
2、文件的关闭与动态开辟内存空间时一样,当我们打开文件时,会在内存中开辟一块空间,如果我们打开该文件后不关闭,那么这个空间会一直存在,一直占用那块内存空间,所以当我们对一个文件的操作结束时,一定要记住将该文件关闭。这就需要用到fclose函数来关闭文件。
代码语言:javascript复制int fclose( FILE *stream );我们如果要关闭一个文件,那么直接将该文件的文件指针传入fclose函数即可,fclose函数如果关闭文件成功会返回0。与free函数一样,当我们调用完fclose函数将文件关闭后,我们也要将指向文件信息区的指针置空,避免该指针变成野指针。
代码语言:javascript复制 fclose(pf);//关闭pf指向的文件
pf = NULL;//及时置空五、文件的顺序读写1、顺序读写的介绍函数名
功能
适用于
fgetc()
字符输入函数
所有输入流
fputc()
字符输出函数
所有输出流
fgets()
文本行输入函数
所有输入流
fputs()
文本行输出函数
所有输出流
fscanf()
格式化输入函数
所有输入流
fprintf()
格式化输出函数
所有输出流
fread()
二进制输入
文件输入流
fwrite()
二进制输出
文件输出流
fput与fgetfput
代码语言:javascript复制int fputc( int c, FILE *stream );fputc函数的第一个参数是待输出的字符,第二个参数该字符输出的位置,即fputc函数的功能是将一个字符输出到指定的位置。该函数调用完毕会返回用户传入的字符。
代码语言:javascript复制#include
#include
#include
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件进行输出字符操作
char i = 0;
for (i = 'a'; i <= 'z'; i++)
{
fputc(i, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}fget
代码语言:javascript复制int fgetc( FILE *stream );getc函数只有一个参数,即你要读取的文件的文件指针。fgets函数的功能就是从指定位置读取一个字符。该函数调用成功会返回读取到的的字符;若读取文件时发生错误,或是已经读取到文件末尾,则返回EOF。
例如,我们要将文件data.txt文件中的内容全部读取,并打印到屏幕上。
代码语言:javascript复制#include
#include
#include
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件进行输入字符操作
int ch = 0;
while ((ch = fgetc(pf))!= EOF)
{
printf("%c", ch);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}fputs与fgetsfputs
代码语言:javascript复制int fputs( const char *string, FILE *stream );puts函数的第一个参数是待输出的字符串,第二个参数该字符串输出的位置,即fputs函数的功能是将一个字符串输出到指定的位置(有没有发现fputs函数的参数设计和fputc函数参数的设计非常类似)。该函数调用成功会返回一个非负值;若输出时发生错误,则返回EOF。
例如,我们要将字符串"hello world!"写入到data.txt文件中。
代码语言:javascript复制#include
#include
#include
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件进行输出字符串操作
char arr[] = "hello world!";
fputs(arr, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}fgets
代码语言:javascript复制char *fgets( char *string, int n, FILE *stream );gets函数的第三个参数是你要读取的文件的文件指针,第二个参数是你要读取的字符个数(也可以说是字节个数),第一个参数是你所读取到的数据的储存位置。fgets函数的功能就是从指定位置读取指定字符个数的数据储存到指定位置。该函数调用成功会返回用于储存数据的位置的地址,如果读取过程中发生错误,或是读取到了文件末尾,则返回一个空指针(NULL)。
例如,我们要从data.txt文件中提取数据。
代码语言:javascript复制#include
#include
#include
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件进行输入字符串操作
char arr[10] = { 0 };
fgets(arr, 6, pf);
printf("%s\n", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}fgets函数读取字符的过程中会出现两种情况:
在fgets函数读取到指定字符数之前,若读取到换行符(’\n’),则停止读取,读取带回的字符包含换行符。直到fgets函数读取到第n-1个字符时都没有遇到换行符(’\n’),则返回读取到的n-1个字符,并在末尾加上一个空字符一同返回(共n个字符)。fprintf与fcanffprintf
代码语言:javascript复制int fprintf( FILE *stream, const char *format [, argument ]...);fprintf函数的第一个参数是数据输出的目的地,后面的参数博友们可能看不太懂,但是我们可以看看库函数printf函数的函数声明:
代码语言:javascript复制int printf( const char *format [, argument]... );
//printf函数的声明我们发现fprintf函数除了第一个参数以外,其它参数都与printf函数的参数一样,虽然我们不知道fprintf函数后面的参数代表的意思,但是我们会用printf函数啊,所以我们使用fprintf函数传参时可以模仿者printf的传参形式。
例如我们要将一个结构体类型的变量信息输出到data.txt文件中去。
代码语言:javascript复制#include
#include
#include
struct S
{
char name[20];
char sex[5];
int age;
};
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件进行格式化输出操作
struct S s = { "zhangsan", "nan", 20 };
fprintf(pf, "%s %s %d\n", s.name, s.sex, s.age);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}在这里插入图片描述
其实,fprintf函数的功能就是将“区域三”的数据,以“区域二”的格式输出到“区域一”。
fscanf
代码语言:javascript复制int fscanf( FILE *stream, const char *format [, argument ]... );fscanf函数的第一个参数是读取数据的位置,后面的参数我们也可以类比scanf函数的参数:
代码语言:javascript复制int scanf( const char *format [,argument]... );我们可以看到fscanf函数的参数也是,除了第一个参数以外,其他参数就是scanf函数的参数,于是我们可以试着尝试使用。
例如,我们要将刚才用fprintf函数输出到data.txt文件中的数据读取出来。
代码语言:javascript复制#include
#include
#include
struct S
{
char name[20];
char sex[5];
int age;
};
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件进行格式化输入操作
struct S tmp = { 0 };
fscanf(pf, "%s %s %d", tmp.name, tmp.sex, &(tmp.age));
printf("%s %s %d\n", tmp.name, tmp.sex, tmp.age);
//将tmp中的内容打印出来,看是否读取成功
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}在这里插入图片描述
其实,fscanf函数的功能就是将“区域一”的数据,以“区域二”的格式输入到“区域三”。
fwrite与freadfwrite
代码语言:javascript复制size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );write函数的第一个参数是输出数据的位置,第二个参数是要输出数据的元素个数,第三个参数是每个元素的大小,第四个参数是数据输出的目标位置。该函数调用完后,会返回实际写入目标位置的元素个数,当输出时发生错误或是待输出数据元素个数小于要求输出的元素个数时,会返回一个小于count的数。
概括一下,fwrite函数的功能就是将buffer位置的,每个元素大小为size的,count个元素,以二进制的形式输出到stream位置。
在这里插入图片描述
例如,我们要将数组arr中的10个元素以二进制的形式输出到data.txt文件中去。
代码语言:javascript复制#include
#include
#include
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "wb");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件以二进制形式进行输出操作
int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
fwrite(arr, sizeof(int), 10, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}注:此时打开文件的形式应为"wb",即以二进制的形式打开文件,进行输出操作。
fread
代码语言:javascript复制size_t fread( void *buffer, size_t size, size_t count, FILE *stream );fread函数的第一个参数是接收数据的位置,第二个参数是要读取的每个元素的大小,第三个参数是要读取的元素个数,第四个参数是读取数据的位置。函数调用完会返回实际读取的元素个数,若在读取过程中发生错误或是在未读取到指定元素个数时读取到文件末尾,则返回一个小于count的数。
概括一下,fread函数的功能就是从stream位置,以二进制的形式读取count个每个元素大小为size的数据,到buffer位置。
例如,我们要将刚才用fwrite函数输出到data.txt文件的数据读取出来。
代码语言:javascript复制#include
#include
#include
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "rb");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//文件打开失败,失败返回
}
//对文件以二进制形式进行输入操作
int arr[10] = { 0 };
fread(arr,sizeof(int),10,pf);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}//将arr中的内容打印出来,看是否读取成功
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}注:此时打开文件的形式应为"rb",即以二进制的形式打开文件,进行输入操作。
六、文件的随机读写1、核心函数:文件随机读写三剑客1) 定位函数 fseek()代码语言:javascript复制int fseek(FILE *stream, long offset, int origin);作用:移动文件指针到指定位置参数: origin:基准位置(可选以下常量): SEEK_SET:文件开头(0位置)SEEK_CUR:当前位置SEEK_END:文件末尾offset:偏移量(字节数),可正可负示例:跳转到文件第100字节处
代码语言:javascript复制fseek(fp, 100, SEEK_SET); // 从开头向后跳100字节2) 获取位置 ftell()代码语言:javascript复制long ftell(FILE *stream);作用:返回当前文件指针的位置(相对于开头的字节数)示例:记录当前位置
代码语言:javascript复制long pos = ftell(fp); // 获取当前位置3) 重置指针 rewind()代码语言:javascript复制void rewind(FILE *stream);作用:将文件指针重置到开头(等价于fseek(fp, 0, SEEK_SET))2、 实战演示:修改文件中间内容假设有一个日志文件log.txt,内容如下:
代码语言:javascript复制[1] 启动成功
[2] 加载配置
[3] 连接数据库现在需要将第2条日志改为[2] 加载配置(已缓存):
代码语言:javascript复制#include
int main() {
FILE *fp = fopen("log.txt", "r+"); // 必须用"r+"模式
if (fp == NULL) {
perror("文件打开失败");
return 1;
}
// 定位到第2行开头(假设每行固定20字节)
fseek(fp, 20, SEEK_SET);
// 修改内容(注意长度必须一致,否则会覆盖后续内容)
fprintf(fp, "[2] 加载配置(已缓存)");
fclose(fp);
return 0;
}3、 高级技巧:二进制文件随机访问对于二进制文件(如结构体数据),可以直接跳转到指定记录:
代码语言:javascript复制struct Record {
int id;
char name[50];
};
// 读取第3条记录(每条记录占54字节)
fseek(fp, 2 * sizeof(struct Record), SEEK_SET);
struct Record r;
fread(&r, sizeof(struct Record), 1, fp);4.、避坑指南模式选择:随机读写必须用"r+"、"w+"或"a+"模式文本文件陷阱:在Windows中,文本模式的\n会被转换为\r\n,影响偏移量计算越界检查:跳转时确保offset不超过文件大小(可通过fseek(fp, 0, SEEK_END)获取文件长度)思考题:如何用fseek()和ftell()实现文件大小检测?欢迎在评论区讨论!
相关推荐:
C语言文件操作大全用内存映射实现超高速文件读写七、文件读取结束的判定1、核心方法1.)feof()函数(最常用)代码语言:javascript复制int feof(FILE *stream);作用:检测文件指针是否到达文件末尾返回值: 非0值:已到达文件尾(EOF)0:未到达示例:
代码语言:javascript复制while(!feof(fp)) {
char ch = fgetc(fp);
// 处理字符...
}2.)读取函数返回值(最可靠)适用于:fgetc(), fgets(), fread()等原理:这些函数遇到EOF时会返回特殊值: fgetc() 返回 EOFfgets() 返回 NULLfread() 返回实际读取项数(小于请求项数表示EOF)示例:
代码语言:javascript复制// fgetc版
int ch;
while((ch = fgetc(fp)) != EOF) {
putchar(ch);
}
// fgets版
char buffer[100];
while(fgets(buffer, 100, fp) != NULL) {
printf("%s", buffer);
}3.)文件位置检测(适合二进制文件)代码语言:javascript复制fseek(fp, 0, SEEK_END); // 跳到文件尾
long file_size = ftell(fp); // 获取总字节数
rewind(fp); // 回到文件头
while(ftell(fp) < file_size) {
// 读取操作...
}2、重要注意事项feof()的滞后性:必须在读取操作后调用才会正确检测EOF二进制文件:推荐用fread()返回值判断Windows换行符:文本模式下\r\n会被视为单个字符3、典型错误案例代码语言:javascript复制// 错误写法!会多读一次
while(!feof(fp)) {
char ch = fgetc(fp); // 最后一次读取到EOF后仍会进入循环
printf("%c", ch);
}4、最佳实践代码语言:javascript复制// 正确组合写法
while(1) {
int ch = fgetc(fp);
if (feof(fp)) break; // 在读取后立即检查
printf("%c", ch);
}八、文件缓冲区ANSI C 标准下的缓冲文件系统解析1) 缓冲文件系统工作机制ANSI C 标准采用缓冲文件系统处理数据文件。该系统会在内存中为每个打开的文件自动分配文件缓冲区,作为内存与磁盘之间的数据中转站。这种设计通过减少直接磁盘I/O操作次数,显著提升文件读写效率。
2)缓冲工作原理 输出缓冲(内存→磁盘)
程序数据首先写入内存缓冲区缓冲区满后,系统自动整块写入磁盘示例:连续写入10字节数据时,可能暂存缓冲区直至达到512字节(典型缓冲区大小)才实际写盘 输入缓冲(磁盘→内存)
系统从磁盘预读数据块(如512字节)到缓冲区程序读取数据时直接从缓冲区获取,避免频繁磁盘访问示例:fgetc()实际可能已预加载了后续511个字符到内存3)缓冲区特性自动管理:由C运行时库维护,程序员无需手动干预大小不可见:缓冲区容量取决于编译器实现(通常512字节或4KB)刷新条件: 缓冲区满调用fflush()文件关闭(fclose())程序正常终止4)缓冲类型对比缓冲模式
特性
设置方法
全缓冲
缓冲区满时刷新(默认用于磁盘文件)
setvbuf(fp, buf, _IOFBF, size)
行缓冲
遇到换行符\n时刷新(用于终端)
setvbuf(fp, buf, _IOLBF, size)
无缓冲
立即输出(用于错误流stderr)
setvbuf(fp, buf, _IONBF, 0)
5)编程注意事项数据完整性:重要数据应手动fflush(),避免程序崩溃导致缓冲数据丢失
性能权衡:
代码语言:javascript复制// 禁用缓冲可降低延迟,但增加I/O次数
setvbuf(fp, NULL, _IONBF, 0); 跨平台差异:Windows换行符\r\n在文本模式下会转换为\n,可能影响缓冲计数
关键理解:缓冲系统如同快递集货站——小件包裹先暂存,攒够一车再统一发货,这种批处理思想贯穿计算机系统设计。
结语:掌握文件操作,解锁C语言编程新境界通过本文的系统讲解,相信您已经对C语言文件操作有了全面深入的理解。从基础的文件打开模式到高效的随机读写,再到关键的缓冲机制,这些知识将帮助您:
提升程序效率:合理利用缓冲机制可显著优化I/O性能增强数据控制力:精准的文件定位让数据处理更加灵活避免常见陷阱:理解文本/二进制模式差异,规避跨平台兼容性问题记住,优秀的程序员不仅要让代码"能工作",更要让程序"工作得好"。文件操作作为数据持久化的核心技能,值得您反复实践和深入钻研。
下一步建议:
尝试实现一个简单的文件数据库对比不同缓冲策略的性能差异探索内存映射文件等高级技术愿您在C语言文件操作的世界里游刃有余,让数据流动更加优雅高效!如有任何问题,欢迎在评论区交流讨论。