数据完整性保证
1. 介绍
在计算机项目开发中,数据的完整性和持久性是至关重要的。
比如:在多进同时对日志文件进行写入的时候,如何避免日志信息的混乱,进程A写入的信息被进程B写入的信息覆盖。以及在电脑断电的情况下,尽力避免数据的丢失。
2. 写操作相关函数
2.1 write()函数
原型:
ssize_t write(int fd, const void *buf, size_t count);
参数说明:
fd
:文件描述符buf
:指向要写入数据的缓冲区的指针count
:要写入的字节数
详细介绍:
- 描述:
write()
函数是系统调用,用于向文件描述符写入数据。 - 特点:直接操作内核缓冲区,没有用户空间的缓存,它会尽可能快地将数据写入到文件中,而不会等待或延迟。这使得它非常适合于需要即时写入数据的场景,如实时日志记录或网络通信。
- 使用场景:适用于需要直接与操作系统内核交互,或需要精确控制数据写入时机 的低级操作。适合于:图像,音频,视频等。
- 功能:基本的二进制写入,不支持格式化或批量操作,每次调用直接将数据写入文件描述符。
- 错误处理:
write()
返回写入的字节数。
示例:
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main() {
// 尝试写入字符串(不是格式化输出,但写入字符串)
char str[] = "Hello, world!你好,世界。";
write(STDOUT_FILENO, str, strlen(str));
write(STDOUT_FILENO, "\n", 1); // 写入换行符
int num=42;
write(STDOUT_FILENO,&num,sizeof(int));// 将整数 42 以二进制形式写入到标准输出(控制台)
write(STDOUT_FILENO, "\n", 1); // 写入换行符
// 尝试批量数据处理(写入整型数组作为字节序列)
int nums[] = {1, 2, 3, 4, 5};
write(STDOUT_FILENO, (char *)nums, sizeof(nums)); // 强制转换为 char* 并写入整个数组
return 0;
}
显示结果:
发现num(42)的显示结果为:* (ASCII码第42号)
而nums数组却无法打印(1-5是特殊字符)
2.2 fwrite()函数
-
原型:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
-
参数说明:
ptr
:指向要写入数据的缓冲区指针size
:每个数据项的大小(以字节为单位)nmemb
:数据项的个数stream
:文件流指针。
-
示例:
#include <stdio.h> int main() { // 格式化输出示例 FILE *file_format = fopen("output_format.txt", "w"); if (file_format) { int num = 123; float pi = 3.1415926; char str[] = "Hello, world!"; fprintf(file_format, "Number: %d\n", num); fprintf(file_format, "Pi: %f\n", pi); fprintf(file_format, "String: %s\n", str); fclose(file_format); } // 批量数据处理示例 FILE *file_batch = fopen("output_batch.bin", "wb"); if (file_batch) { int nums[] = {1, 2, 3, 4, 5}; fwrite(nums, sizeof(int), 5, file_batch); fclose(file_batch); } return 0; }
-
详细介绍:
- 描述:
fwrite()
是C标准库的一部分,提供了对文件的高级写入功能。 - 特点:使用用户空间的缓冲,可以减少系统调用的次数,提高写入效率。这意味着实际的写入可能会延迟发生,直到缓冲区满或者显示的调用
fflush()
。 - 使用场景:格式化输出,批量处理数据等。
- 功能:支持格式化和批量处理数据,可以一次写入多个数据项。
- 错误处理:
fwrite()
返回写入的项数,如果小于请求的项数,可能是发生了错误或者文件结束。
- 描述:
-
工作流程
以fwrite(arr)
为例:fwrite()
会把arr
中的数据复制到标准C库的用户空间缓冲区中。- 标准C库会管理这个用户空间缓冲区,可能会等待缓冲区填满或者手动调用
fflush()
才会触发数据的写入操作。 - 当标准库认为合适的时候,会通过系统调用(write)将用户空间缓冲区的数据传递给内核空间。
- 内核空间收到数据后,进一步对数据写入,最终把数据写入到磁盘中。
优点:
假设你有一个程序,需要将大量数据写入到文件中。如果没有使用缓冲机制,每次写入一个数据项都会触发一个系统调用,这样频繁的系统调用会导致系统开销增加,降低写入效率。
现在,假设你使用了缓冲机制,比如标准C库的fwrite()
函数。当你调用fwrite()
写入数据时,数据首先被复制到用户空间的缓冲区中。在缓冲区填满之前,所有的写入操作都只是在用户空间进行,没有触发系统调用。只有当缓冲区填满、达到一定大小,或者手动调用fflush()
函数时,才会触发系统调用,将缓冲区中的数据一次性写入到内核空间。
这样一来,多个数据项被聚集成一个更大的块,一次性写入到内核空间,从而减少了系统调用的次数。相比每次写入一个数据项都触发系统调用,这种批量写入的方式会显著减少系统调用的频率。
3. 数据写同步
3.1 sync()
- 原型:
void sync(void)
- 描述:
sync()
是一个系统调用,用于将所有修改过的文件缓冲区写入硬盘。这个函数是全局性的触发所有缓冲区的写操作。 - 使用场景
当需要确保整个系统的所有文件数据都安全写入硬盘时使用,例如在系统关机前。
3.2 fsync()
- 原型:
int fsync(int fd)
- 描述:
fsync()
针对单一的文件描述符fd
,确保该文件描述符引用的文件的所有内存中的数据和元数据(比如文件修改时间等属性)都被写入存储设备。 - 使用场景
在需要确保特定文件数据被安全写入存储设备时使用,常见于数据库管理系统和文件编辑器,保证关键数据的持久性和一致性。
3.3 fdatasync()
- 原型:
int fdatasync(int fd);
- 描述:
fdatasync()
类似于fsync()
,但它只确保文件的数据部分被同步到硬盘,不包括与文件相关的元数据(除非这些元数据的改变是由于文件数据更新所必需的)。 - 使用场景:
当不需要文件元数据(如最后的访问时间)被同步时使用。这通常用于提高性能,因为同步较少的数据可以减少I/O操作的数量。适用于例如数据库日志文件的写入,其中只关心数据的持久性,而不关心文件的元数据。