C/C++/Qt 文件操作 效率比较

news2024/11/19 9:25:27

C/C++/Qt 文件操作 & 效率比较

  • 1 介绍
  • 2 比较结果
    • 2.1 Linux平台上运行程序普遍比Windows上快;Windows下VC编译的程序一般运行比MINGW(MINimal Gcc for Windows)快
    • 2.2 二进制文件的操作要快于文本文件;写文件的操作要快于读文件;C语言文件指针操作方式要快于C++流式读写文件
  • 3 知识点
    • 文本文件和二进制文件
    • 换行符
    • wins下fwrite函数0x0A变成0x0D 0x0A
  • 4 C
    • 4.1 介绍
    • 4.2 基本函数
      • fopen 打开文件
      • fclose 关闭文件
      • fgetc 字符读取( file get char)
      • ferror 判断文件操作是否出错
      • fputc 字符写入
      • fgets和fputs 以字符串的形式读写文件
      • fread和fwrite 以数据块的形式读写文件
      • fscanf和fprintf 格式化读写文件
      • rewind和fseek 随机读写文件(定位)
      • fread 复制
      • FILE 文件指针
      • ftell 获取文件大小
    • 4.3 提高速度
  • 5 C++
    • 5.1 介绍
      • fstream 类一些常用的成员方法
    • 5.2 基本函数
      • open 打开文件
      • ifstream 构造函数时打开文件
      • close 关闭文件
      • flush 刷新缓冲区
      • >> 和 << 读写文件(文本)
      • read() 和 write() 成员方法读写文件(二进制)
      • get()和put()读写文件
      • getline 从文件中读取一行字符串
      • seekp、seekg、tellg、tellp 移动和获取文件读写指针(p-put,g-get ?)
  • 6 Qt
    • 6.1 介绍
    • 6.2 基本函数
      • QFile 构造函数确定操作文件
      • open 打开文件
      • 常用函数
      • QFile+QTextStream 读写文件
      • QFile+QDataStream 读写二进制文件
  • 参考

1 介绍

C++文件操作、QT文件操作因为使用了面向对象,具有更好的封装性。

2 比较结果

2.1 Linux平台上运行程序普遍比Windows上快;Windows下VC编译的程序一般运行比MINGW(MINimal Gcc for Windows)快

  • 引用 探寻C++最快的读取文件的方案

  • Linux平台上运行程序普遍比Windows上快。

  • Windows下VC编译的程序一般运行比MINGW(MINimal Gcc for Windows)快。

  • VC对cin取消同步与否不敏感,前后效率相同。反过来MINGW则非常敏感,前后效率相差8倍。

  • read本是linux系统函数,MINGW可能采用了某种模拟方式,read比fread更慢。

  • Pascal程序运行速度实在令人不敢恭维。

在这里插入图片描述

2.2 二进制文件的操作要快于文本文件;写文件的操作要快于读文件;C语言文件指针操作方式要快于C++流式读写文件

  • 引用 C/C++文件操作效率比较——FILE/fstream

如下程序运行结果

C语言写文本文件操作运行时间为: 157 ms 
C++写文本文件操作运行时间为: 593 ms 

C语言读文本文件操作运行时间为: 172 ms
C++读文本文件操作运行时间为: 235 ms

C语言写二进制文件操作运行时间为: 78 ms
C++写二进制文件操作运行时间为: 203 ms

C语言读二进制文件操作运行时间为: 125 ms
C++读二进制文件操作运行时间为:156 ms
#include <iostream>
#include <fstream>
#include <windows.h>
#include <string.h>
#include <stdio.h>
 
using namespace std;
 
void WriteTXTFile_C()
{
	FILE* fp = fopen("c.txt","wt");
	for(int i = 0; i < 1000; i++)
	{
		for(int j = 0; j < 1000; j++)
		{
			fputc('C', fp);
			fputs("Hello, world!\n", fp);
		}
	}
	fclose(fp);
}
 
void ReadTXTFile_C()
{
	FILE* fp = fopen("c.txt","rt");
	char str[15];
	for(int i = 0; i < 1000; i++)
	{
		for(int j = 0; j < 1000; j++)
		{
			fgetc(fp);
			fgets(str, 15, fp);
		}
	}
	fclose(fp);
}
 
void WriteBINFile_C()
{
	FILE* fp = fopen("c.bin","wb");
	char* str = "CHello, world!\n";
	int len = strlen(str);
	
	for(int i = 0; i < 1000; i++)
	{
		for(int j = 0; j < 1000; j++)
		{
			fwrite(str, 1, len, fp);
		}
	}
	fclose(fp);
}
 
void ReadBINFile_C()
{
	FILE* fp = fopen("c.bin","rb");
	char str[16];
	
	for(int i = 0; i < 1000; i++)
	{
		for(int j = 0; j < 1000; j++)
		{
			fread(str, 1, 16, fp);
		}
	}
	fclose(fp);
}
 
void WriteTXTFile_CPlus()
{
	fstream file;
	file.open("cp.txt", ios::in | ios::out | ios::trunc);//注意ios::in,或者ios::in | ios::out两种方式在文件不存在时会执行写操作但不会建立文件。
 
	for(int i = 0; i < 1000; i++)
	{
		for(int j = 0; j < 1000; j++)
		{
			file.put('C');
			file<<"Hello, world!\n";
		}
	}
	file.close();
}
 
void ReadTXTFile_CPlus()
{
	fstream file;
	file.open("cp.txt", ios::in | ios::out);
	char str[15];
 
	for(int i = 0; i < 1000; i++)
	{
		for(int j = 0; j < 1000; j++)
		{
			file.get(str, 15);
		}
	}
	file.close();
}
 
void WriteBINFile_CPlus()
{
	fstream file;
	file.open("cp.bin", ios::in | ios::out | ios::trunc | ios::binary);
	char* str = "CHello, world!\n";
	int len = strlen(str);
 
	for(int i = 0; i < 1000; i++)
	{
		for(int j = 0; j < 1000; j++)
		{
			file.write(str, len);
		}
	}
	file.close();
}
 
void ReadBINFile_CPlus()
{
	fstream file;
	file.open("cp.bin", ios::in | ios::out | ios::binary);
	char str[16];
 
	for(int i = 0; i < 1000; i++)
	{
		for(int j = 0; j < 1000; j++)
		{
			file.read(str, 16);
		}
	}
	file.close();
}
 
int main()
{
	DWORD start, end;
	start = GetTickCount();
	WriteTXTFile_C();
	end = GetTickCount();
	printf("C语言写文本文件操作运行时间为:%d ms\n", end - start);
	start = GetTickCount();
	WriteTXTFile_CPlus();
	end = GetTickCount();
	cout<<"C++写文本文件操作运行时间为:"<<end - start<<" ms"<<endl;
 
	cout<<endl;
 
	start = GetTickCount();
	ReadTXTFile_C();
	end = GetTickCount();
	printf("C语言读文本文件操作运行时间为:%d ms\n", end - start);
	start = GetTickCount();
	ReadTXTFile_CPlus();
	end = GetTickCount();
	cout<<"C++读文本文件操作运行时间为:"<<end - start<<" ms"<<endl;
 
	cout<<endl;
 
	start = GetTickCount();
	WriteBINFile_C();
	end = GetTickCount();
	printf("C语言写二进制文件操作运行时间为:%d ms\n", end - start);
	start = GetTickCount();
	WriteBINFile_CPlus();
	end = GetTickCount();
	cout<<"C++写二进制文件操作运行时间为:"<<end - start<<" ms"<<endl;
 
	cout<<endl;
 
	start = GetTickCount();
	ReadBINFile_C();
	end = GetTickCount();
	printf("C语言读二进制文件操作运行时间为:%d ms\n", end - start);
	start = GetTickCount();
	ReadBINFile_CPlus();
	end = GetTickCount();
	cout<<"C++读二进制文件操作运行时间为:"<<end - start<<" ms"<<endl;
}

3 知识点

文本文件和二进制文件

  • 都是以二进制方式存储的。
  • 文本文件通常用来保存肉眼可见的字符,比如.txt文件、.c文件、.dat文件等,用文本编辑器打开这些文件,我们能够顺利看懂文件的内容。文本文件中采用的是 ASCII、UTF-8、GBK 等字符编码,文本编辑器可以识别出这些编码格式,并将编码值转换成字符展示出来。
  • 二进制文件通常用来保存视频、图片、程序等不可阅读的内容,用文本编辑器打开这些文件,会看到一堆乱码,根本看不懂。 二进制文件使用的是 mp4、gif、exe 等特殊编码格式,文本编辑器并不认识这些编码格式。

换行符

  • 类 UNIX/Linux 系统在处理文本文件时也将\n作为换行符。
  • Windows 系统却不同,它将\r\n作为文本文件的换行符。

wins下fwrite函数0x0A变成0x0D 0x0A

fwrite 在以文本方式写文件时,碰到0x0A,会自动在前面加上0x0D,以构成回车换行符,因为Windows平台的换行符默认是:0x0D 0x0A。
以二进制方式打开文件,然后进行写文件。

fopen( filename, "a+" );     ------> 改成    fopen( filename, "a+b" );
ofstream outFile(strFilePath, ios::binary | ios::trunc);

4 C

4.1 介绍

文件硬件设备
stdin标准输入文件,一般指键盘;scanf()、getchar() 等函数默认从 stdin 获取输入。
stdout标准输出文件,一般指显示器;printf()、putchar() 等函数默认向 stdout 输出数据。
stderr标准错误文件,一般指显示器;perror() 等函数默认向 stderr 输出数据(后续会讲到)。
stdprn标准打印文件,一般指打印机。

操作文件的正确流程为:打开文件 --> 读写文件 --> 关闭文件。

4.2 基本函数

fopen 打开文件

<stdio.h>
FILE *fopen(char *filename, char *mode);
filename为文件名(包括文件路径),mode为打开方式,它们都是字符串。

只读方式打开当前目录下的 demo.txt 文件
FILE *fp = fopen("demo.txt", "r");

以二进制方式打开 D 盘下的 demo.txt 文件,允许读和写
FILE *fp = fopen("D:\\demo.txt","rb+");
控制读写权限的字符串(必须指明)
打开方式说明
“r”以“只读”方式打开文件。只允许读取,不允许写入。文件必须存在,否则打开失败。
“w”以“写入”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。
“a”以“追加”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。
“r+”以“读写”方式打开文件。既可以读取也可以写入,也就是随意更新文件。文件必须存在,否则打开失败。
“w+”以“写入/更新”方式打开文件,相当于wr+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。
“a+”以“追加/更新”方式打开文件,相当于a和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。
控制读写方式的字符串(可以不写)
打开方式说明
“t”文本文件。如果不写,默认为"t"
“b”二进制文件。
  • r(read):读
  • w(write):写
  • a(append):追加
  • t(text):文本文件
  • b(binary):二进制文件
  • +:读和写

fclose 关闭文件

int fclose(FILE *fp);

fgetc 字符读取( file get char)

int fgetc (FILE *fp);
成功返回读取到的字符,读取到文件末尾或读取失败时返回EOF(end of file)

在屏幕上显示 F:\bot.txt 文件的内容

#include<stdio.h>
int main(){
    FILE *fp;
    char ch;
   
    //如果文件不存在,给出提示并退出
    if( (fp=fopen("F:\\bot.txt","rt")) == NULL ){
        puts("Fail to open file!");
        exit(0);
    }
    //每次读取一个字节,直到读取完毕
    while( (ch=fgetc(fp)) != EOF ){
        putchar(ch);
    }
    putchar('\n');  //输出换行符
    fclose(fp);
    return 0;
}

ferror 判断文件操作是否出错

int ferror ( FILE *fp );
出错时返回非零值,否则返回零值。

fputc 字符写入

int fputc ( int ch, FILE *fp );

char ch = 'a';
fputc(ch, fp);

fgets和fputs 以字符串的形式读写文件

char *fgets ( char *str, int n, FILE *fp );
int fputs( char *str, FILE *fp );

fread和fwrite 以数据块的形式读写文件

size_t fread ( void *ptr, size_t size, size_t count, FILE *fp );
size_t fwrite ( void * ptr, size_t size, size_t count, FILE *fp );

ptr 为内存区块的指针,它可以是数组、变量、结构体等。
fread() 中的 ptr 用来存放读取到的数据,fwrite() 中的 ptr 用来存放要写入的数据。
size:表示每个数据块的字节数。
count:表示要读写的数据块的块数。
fp:表示文件指针。
理论上,每次读写 size*count 个字节的数据。

fscanf和fprintf 格式化读写文件

fscanf() 和 fprintf() 函数与前面使用的 scanf() 和 printf() 功能相似,都是格式化读写函数,两者的区别在于 fscanf() 和 fprintf() 的读写对象不是键盘和显示器,而是磁盘文件。

fp 为文件指针,format 为格式控制字符串,… 表示参数列表。
int fscanf ( FILE *fp, char * format, ... );
int fprintf ( FILE *fp, char * format, ... );

rewind和fseek 随机读写文件(定位)

  • 实际开发中经常需要读写文件的中间部分,要解决这个问题,就得先移动文件内部的位置指针,再进行读写。这种读写方式称为随机读写,也就是说从文件的任意位置开始读写。
  • 实现随机读写的关键是要按要求移动位置指针,这称为文件的定位。
rewind() 用来将位置指针移动到文件开头
void rewind ( FILE *fp );
fseek() 用来将位置指针移动到任意位置
int fseek ( FILE *fp, long offset, int origin );
  • fp 为文件指针,也就是被移动的文件。
  • offset 为偏移量,也就是要移动的字节数。之所以为 long 类型,是希望移动的范围更大,能处理的文件更大。offset 为正时,向后移动;offset 为负时,向前移动。
  • origin 为起始位置,也就是从何处开始计算偏移量。C语言规定的起始位置有三种,分别为文件开头、当前位置和文件末尾,每个位置都用对应的常量来表示。
起始点常量名常量值
文件开头SEEK_SET0
当前位置SEEK_CUR1
文件末尾SEEK_END2

fread 复制

文件复制思路:开辟一个缓冲区,不断从原文件中读取内容到缓冲区,每读取完一次就将缓冲区中的内容写入到新建的文件,直到把原文件的内容读取完。
注意:以二进制形式打开,保证txt文件外的mp4,jpg等格式拷贝无异常。

  • 开辟多大的缓冲区合适?缓冲区过小会造成读写次数的增加,过大也不能明显提高效率。目前大部分磁盘的扇区都是4K对齐的,如果读写的数据不是4K的整数倍,就会跨扇区读取,降低效率,所以我们开辟4K的缓冲区
  • 缓冲区中的数据是没有结束标志的,如果缓冲区填充不满,如何确定写入的字节数?最好的办法就是每次读取都能返回读取到的字节数。
size_t fread ( void *ptr, size_t size, size_t count, FILE *fp );
#include <stdio.h>
#include <stdlib.h>
int copyFile(char *fileRead, char *fileWrite);
int main(){
    char fileRead[100];  // 要复制的文件名
    char fileWrite[100];  // 复制后的文件名
   
    // 获取用户输入
    printf("要复制的文件:");
    scanf("%s", fileRead);
    printf("将文件复制到:");
    scanf("%s", fileWrite);
    // 进行复制操作
    if( copyFile(fileRead, fileWrite) ){
        printf("恭喜你,文件复制成功!\n");
    }else{
        printf("文件复制失败!\n");
    }
    return 0;
}
/**
* 文件复制函数
* @param    fileRead    要复制的文件
* @param    fileWrite   复制后文件的保存路径
* @return   int         1: 复制成功;2: 复制失败
**/
int copyFile(char *fileRead, char *fileWrite){
    FILE *fpRead;  // 指向要复制的文件
    FILE *fpWrite;  // 指向复制后的文件
    int bufferLen = 1024*4;  // 缓冲区长度
    char *buffer = (char*)malloc(bufferLen);  // 开辟缓存
    int readCount;  // 实际读取的字节数
    if( (fpRead=fopen(fileRead, "rb")) == NULL || (fpWrite=fopen(fileWrite, "wb")) == NULL ){
        printf("Cannot open file, press any key to exit!\n");
        getch();
        exit(1);
    }
    // 不断从fileRead读取内容,放在缓冲区,再将缓冲区的内容写入fileWrite
    while( (readCount=fread(buffer, 1, bufferLen, fpRead)) > 0 ){
        fwrite(buffer, readCount, 1, fpWrite);
    }
    free(buffer);
    fclose(fpRead);
    fclose(fpWrite);
    return 1;
}

FILE 文件指针

FILE  *fp;

typedef struct _iobuf {
    int cnt;  // 剩余的字符,如果是输入缓冲区,那么就表示缓冲区中还有多少个字符未被读取
    char *ptr;  // 下一个要被读取的字符的地址
    char *base;  // 缓冲区基地址
    int flag;  // 读写状态标志位
    int fd;  // 文件描述符
    // 其他成员
} FILE;

ftell 获取文件大小

long int ftell ( FILE * fp );

先使用 fseek() 将文件内部指针定位到文件末尾,再使用 ftell() 返回内部指针距离文件开头的字节数,这个返回值就等于文件的大小。
long fsize(FILE *fp){
    long n;
    fpos_t fpos;  //当前位置
    fgetpos(fp, &fpos);  //获取当前位置
    fseek(fp, 0, SEEK_END);
    n = ftell(fp);
    fsetpos(fp,&fpos);  //恢复之前的位置
    return n;
}

4.3 提高速度

  • 内存映射
  • 使用WINAPI
  • 优化算法

FILE自己维护了一套缓存机制
FILE会使用默认的一个缓存值作为io缓存(4k),或者也可以通过setbuf来设置这个缓存的大小。

假 设你fread 1字节 会导致ReadFile 4k,然后fread再将要读取的数据copy到指定的缓冲区中。以后访问只要不过这个边界,就一直从该io缓存中读取,fwrite也是,直到超过io 缓存边界才真正的调用WriteFile。可以调用flush主动强制刷新从而调用WriteFile 或者fclose被动刷新调用WriteFile(这时fclose会阻塞)。

再说一下硬盘的 硬盘的cache由硬盘控制器管理和使用 就像处理器的cache没法直接操作一样 写硬盘的时候会先写入cache 然后硬盘内部会把数据慢慢写入磁盘 这个过程中没有优化 也就是说硬盘驱动按什么顺序写的 写入磁盘就是什么顺序。
而实际上 硬盘是个随机访问设备 先写哪个后写哪个无所谓 所以一般在把应用层的io访问转化为底层的io请求后 内核层会做io请求优化排序。

假设一个io队列上目前挂着10个请求 内核层会事先计算每个请求在物理上的位置 然后进行排序 以保证磁头转动一周,尽量让10个请求中的多个在一周内完成,想像一下 最好的情况 10个请求都在一个盘面上 磁头旋转1周 10个请求全部完成 最坏的情况 要转10周 10周的原因是一次只能操作一个磁头 而10个请求可能不幸的在10个盘面上(这时候内核也不会再排序了)。

因此让自己的io操作尽可能维持在连续的磁盘空间 且在物理上不跨越盘面 这样效果最好。为此你可能需要硬盘的准确的参数 并精确计算。

缓存的优势在高强度的io操作会被抵消,因为硬盘的写入速度始终跟不上处理器的请求,cache只能帮助缓冲一下。 cache越大,缓冲的时间越长,当cache填满,硬件上ready信号为无效,硬盘驱动不能再写了。只能挂起内核的io队列,这时候上层还在不停的请求,内核层要么继续往io请求队列上挂装请求 要么阻塞发起io的进程,等到cache有空间了,硬件使能ready信号,驱动重新从内河的io请求队列上摘取io请求,再填cache 又满 。。。。 也就是说cache的优势只在一开始的缓存时间上,这个优势对于小的io请求特别有好处,因为能在填满cache之前不会遭到阻塞或挂起。

5 C++

5.1 介绍

重定向后的 cin 和 cout 可分别用于读取文件中的数据和向文件中写入数据。除此之外,C++ 标准库中还专门提供了 3 个类用于实现文件操作,它们统称为文件流类。

  • ifstream:专用于从文件中读取数据;
  • ofstream:专用于向文件中写入数据;
  • fstream:既可用于从文件中读取数据,又可用于向文件中写入数据。
    在这里插入图片描述

fstream 类一些常用的成员方法

成员方法名适用类对象功 能
open()fstream ifstream ofstream打开指定文件,使其与文件流对象相关联。
is_open()检查指定文件是否已打开。
close()关闭文件,切断和文件流对象的关联。
swap()交换 2 个文件流对象。
operator>>fstream ifstream重载 >> 运算符,用于从指定文件中读取数据。
gcount()返回上次从文件流提取出的字符个数。该函数常和 get()、getline()、ignore()、peek()、read()、readsome()、putback() 和 unget() 联用。
get()从文件流中读取一个字符,同时该字符会从输入流中消失。
getline(str,n,ch)从文件流中接收 n-1 个字符给 str 变量,当遇到指定 ch 字符时会停止读取,默认情况下 ch 为 ‘\0’。
ignore(n,ch)从文件流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 个字符,或者当前读取的字符为 ch。
peek()返回文件流中的第一个字符,但并不是提取该字符。
putback©将字符 c 置入文件流(缓冲区)。
operator<<fstream ofstream重载 << 运算符,用于向文件中写入指定数据。
put()向指定文件流中写入单个字符。
write()向指定文件中写入字符串。
tellp()用于获取当前文件输出流指针的位置。
seekp()设置输出文件输出流指针的位置。
flush()刷新文件输出流缓冲区。
good()fstream ofstream ifstream操作成功,没有发生任何错误。
eof()到达输入末尾或文件尾。

5.2 基本函数

open 打开文件

void open(const char* szFileName, int mode)
第一个参数是指向文件名的指针,第二个参数是文件的打开模式标记。
模式标记适用对象作用
ios::inifstream fstream打开文件用于读取数据。如果文件不存在,则打开出错。
ios::outofstream fstream打开文件用于写入数据。如果文件不存在,则新建该文件;如果文件原来就存在,则打开时清除原来的内容。
ios::appofstream fstream打开文件,用于在其尾部添加数据。如果文件不存在,则新建该文件。
ios::ateifstream打开一个已有的文件,并将文件读指针指向文件末尾(读写指 的概念后面解释)。如果文件不存在,则打开出错。
ios:: truncofstream打开文件时会清空内部存储的所有数据,单独使用时与 ios::out 相同。
ios::binaryifstream ofstream fstream以二进制方式打开文件。若不指定此模式,则以文本模式打开。
ios::in | ios::outfstream打开已存在的文件,既可读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
ios::in | ios::outofstream打开已存在的文件,可以向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
ios::in | ios::out | ios::truncfstream打开文件,既可读取其内容,也可向其写入数据。如果文件本来就存在,则打开时清除原来的内容;如果文件不存在,则新建该文件。

ifstream 构造函数时打开文件

定义流对象时,在构造函数中给出文件名和打开模式也可以打开文件。

ifstream::ifstream (const char* szFileName, int mode = ios::in, int);

close 关闭文件

bool Append4Log(const string_t &sFileName, uint8_t *sValue, int size)
{
    if (sFileName.empty())
    {
        return false;
    }
    fstream_t outfile(sFileName.c_str(), std::ios::out | std::ios::app);
    if (outfile)
    {
        outfile << sValue; // << std::endl;	// out put to file
        outfile.close();
        outfile.clear();
    }
    return true;
}

flush 刷新缓冲区

在很多实际场景中,即便已经对文件执行了写操作,但后续还可能会执行其他的写操作。对于这种情况,我们可能并不想频繁地打开/关闭文件,可以使用 flush() 方法及时刷新输出流缓冲区,也能起到防止写入文件失败的作用。

>> 和 << 读写文件(文本)

fstream 或者 ifstream 类负责实现对文件的读取,它们内部都对 >> 输出流运算符做了重载;同样,fstream 和 ofstream 类负责实现对文件的写入,它们的内部也都对 << 输出流运算符做了重载。

read() 和 write() 成员方法读写文件(二进制)

ostream & write(char* buffer, int count);
istream & read(char* buffer, int count);

get()和put()读写文件

ostream& put (char c);
istream& get (char& c);
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
    char c;
    //以二进制形式打开文件
    ofstream outFile("out.txt", ios::out | ios::binary);
    if (!outFile) {
        cout << "error" << endl;
        return 0;
    }
    while (cin >> c) {
        //将字符 c 写入 out.txt 文件
        outFile.put(c);
    }
    outFile.close();
    return 0;
}

注意,由于文件存放在硬盘中,硬盘的访问速度远远低于内存。如果每次写一个字节都要访问硬盘,那么文件的读写速度就会慢得不可忍受。因此,操作系统在接收到 put() 方法写文件的请求时,会先将指定字符存储在一块指定的内存空间中(称为文件流输出缓冲区),等刷新该缓冲区(缓冲区满、关闭文件、手动调用 flush() 方法等,都会导致缓冲区刷新)时,才会将缓冲区中存储的所有字符“一股脑儿”全写入文件。

#include <iostream>
#include <fstream>
using namespace std;
int main()
{
    char c;
    //以二进制形式打开文件
    ifstream inFile("out.txt", ios::out | ios::binary);
    if (!inFile) {
        cout << "error" << endl;
        return 0;
    }
    while ( (c=inFile.get())&&c!=EOF )   //或者 while(inFile.get(c)),对应第二种语法格式
    {
        cout << c ;
    }
    inFile.close();
    return 0;
}

和 put() 方法一样,操作系统在接收到 get() 方法的请求后,哪怕只读取一个字符,也会一次性从文件中将很多数据(通常至少是 512 个字节,因为硬盘的一个扇区是 512 B)读到一块内存空间中(可称为文件流输入缓冲区),这样当读取下一个字符时,就不需要再访问硬盘中的文件,直接从该缓冲区中读取即可。

getline 从文件中读取一行字符串

istream & getline(char* buf, int bufSize);
istream & getline(char* buf, int bufSize, char delim);

seekp、seekg、tellg、tellp 移动和获取文件读写指针(p-put,g-get ?)

* ifstream 类和 fstream 类有 seekg 成员函数,可以设置文件读指针的位置;
* ofstream 类和 fstream 类有 seekp 成员函数,可以设置文件写指针的位置。
ostream & seekp (int offset, int mode);
istream & seekg (int offset, int mode);

mode:

  • ios::beg:让文件读指针(或写指针)指向从文件开始向后的 offset 字节处。offset 等于 0 即代表文件开头。在此情况下,offset 只能是非负数。
  • ios::cur:在此情况下,offset 为负数则表示将读指针(或写指针)从当前位置朝文件开头方向移动 offset 字节,为正数则表示将读指针(或写指针)从当前位置朝文件尾部移动 offset字节,为 0 则不移动。
  • ios::end:让文件读指针(或写指针)指向从文件结尾往前的 |offset|(offset 的绝对值)字节处。在此情况下,offset 只能是 0 或者负数。
* ifstream 类和 fstream 类还有 tellg 成员函数,能够返回文件读指针的位置;
* ofstream 类和 fstream 类还有 tellp 成员函数,能够返回文件写指针的位置。
int tellg();
int tellp();

6 Qt

6.1 介绍

QFile 类支持对文件进行读取、写入、删除、重命名、拷贝等操作,它既可以操作文件文件,也可以操作二进制文件。

6.2 基本函数

QFile 构造函数确定操作文件

QFile::QFile()
QFile::QFile(const QString &name)

参数 name 用来指定要操作的目标文件,包含文件的存储路径和文件名,存储路径可以使用绝对路径(比如 “D:/Demo/test.txt”)或者相对路径(比如"./Demo/test.txt"),路径中的分隔符要用 “/” 表示。

open 打开文件

bool QFile::open(OpenMode mode)

mode 参数用来指定文件的打开方式。mode 参数一次性指定多个值,值和值之间用|分割。

表 1 QFile文件打开方式
打开方式含 义
QIODevice::ReadOnly只能对文件进行读操作
QIODevice::WriteOnly只能对文件进行写操作,如果目标文件不存在,会自行创建一个新文件。
QIODevice::ReadWrite等价于 ReadOnly | WriteOnly,能对文件进行读和写操作。
QIODevice::Append以追加模式打开文件,写入的数据会追加到文件的末尾(文件原有的内容保留)。
QIODevice::Truncate以重写模式打开,写入的数据会将原有数据全部清除。注意,此打开方式不能单独使用,通常会和 ReadOnly 或 WriteOnly 搭配。
QIODevice::Text读取文件时,会将行尾结束符(Unix 系统中是 "\n",Windows 系统中是 "\r\n")转换成‘\n’;将数据写入文件时,会将行尾结束符转换成本地格式,例如 Win32 平台上是‘\r\n’。

常用函数

表 2 QFile常用方法
普通成员方法功 能
qint64 QFile::size() const获取当前文件的大小。对于打开的文件,该方法返回文件中可以读取的字节数。
bool QIODevice::getChar(char *c)从文件中读取一个字符,并存储到 c 中。读取成功时,方法返回 true,否则返回 false。
bool QIODevice::putChar(char c)向文件中写入字符 c,成功时返回 true,否则返回 false。
QByteArray QIODevice::read(qint64 maxSize)从文件中一次性最多读取 maxSize 个字节,然后返回读取到的字节。
qint64 QIODevice::read(char *data, qint64 maxSize)从文件中一次性对多读取 maxSize 个字节,读取到的字节存储到 data 指针指定的内存控件中。该方法返回成功读取到的字节数。
QByteArray QIODevice::readAll()读取文件中所有的数据。
qint64 QIODevice::readLine(char *data, qint64 maxSize)每次从文件中读取一行数据或者读取最多 maxSize-1 个字节,存储到 data 中。该方法返回实际读取到的字节数。
qint64 QIODevice::write(const char *data, qint64 maxSize)向 data 数据一次性最多写入 maxSize 个字节,该方法返回实际写入的字节数。 
qint64 QIODevice::write(const char *data)将 data 数据写入文件,该方法返回实际写入的字节数。
qint64 QIODevice::write(const QByteArray &byteArray)将 byteArray 数组中存储的字节写入文件,返回实际写入的字节数。
bool QFile::copy(const QString &newName)将当前文件的内容拷贝到名为 newName 的文件中,如果成功,方法返回 true,否则返回 false。

copy 方法在执行复制操作之前,会关闭源文件。
bool QFile::rename(const QString &newName)对当前文件进行重命名,新名称为 newName,成功返回 true,失败返回 false。
bool QFile::remove()删除当前文件,成功返回 true,失败返回 false。

QFile+QTextStream 读写文件

QTextStream(QIODevice *device)
表 3 QTextStream常用方法
成员方法功 能
bool QTextStream::atEnd() const判断是否读到文件末尾,如果已经达到末尾,返回 true,否则返回 false。
QString QTextStream::read(qint64 maxlen)从文件中读最多 maxlen 个字符,返回这些字符组成的 QString 字符串。
QString QTextStream::readAll()从文件中读取所有内容,返回由读取内容组成的 QString 字符串。
QString QTextStream::readLine(qint64 maxlen = 0)默认读取一行文本,如果手动指定 maxlen 的值,则最多读取 maxlen 个字符,并返回读取内容组成的 QString 字符串。
void QTextStream::setFieldAlignment(FieldAlignment mode)设置对齐方式,通常与 setFieldWidth() 一起使用。
void QTextStream::setFieldWidth(int width)设置每份数据占用的位置宽度为 width。
表 4 QTextStream常用格式描述符
描述符功能相同的方法功 能
Qt::hexQTextStream::setIntegerBase(16)将指定整数对应的 16 进制数写入到文件中。
Qt::showbaseQTextStream::setNumberFlags(numberFlags() | ShowBase)对于非十进制数,写入到文件中时带上相应的前缀。二进制数前缀是 0b,八进制数前缀是 0,十六进制数前缀是 0x。
Qt::forcesignQTextStream::setNumberFlags(numberFlags() | ForceSign)将数字写入文件时,带上正负号。
Qt::fixedQTextStream::setRealNumberNotation(FixedNotation)将浮点数以普通小数的形式写入文件。
Qt::scientificQTextStream::setRealNumberNotation(ScientificNotation)将浮点数以科学计数法的形式写入文件。
Qt::leftQTextStream::setFieldAlignment(AlignLeft)左对齐
Qt::rightQTextStream::setFieldAlignment(AlignRight)右对齐
Qt::centerQTextStream::setFieldAlignment(AlignCenter)居中对齐

QFile+QDataStream 读写二进制文件

QDataStream::QDataStream(QIODevice *d)
表 5 QDataStream常用方法
成员方法功 能
bool QDataStream::atEnd() const判断是否读到文件末尾,如果已经达到末尾,返回 true,否则返回 false。
QDataStream &QDataStream::readBytes(char *&s, uint &l)对于用 writeBytes() 方法写入文件的 l 和 s,只能使用 readBytes() 方法读取出来。 
int QDataStream::readRawData(char *s, int len)从文件中读取最多 len 字节的数据到 s 中,返回值表示实际读取的字节数。注意,调用该方法之前,需要先给 s 参数分配好内存空间。
void QDataStream::setVersion(int v)不同版本的 Qt 中,同名称的数据类型也可能存在差异,通过调用此方法手动指定版本号,可以确保读取数据的一致性。
int QDataStream::skipRawData(int len)跳过文件中的 len 个字节,返回实际跳过的字节数。
QDataStream &QDataStream::writeBytes(const char *s, uint len)将长度 len 和 s 一起写入到文件中,对于 writeBytes() 写入的数据,只能用 readBytes() 方法读取。
int QDataStream::writeRawData(const char *s, int len)将 s 中前 len 字节的数据写入文件,返回值表示成功写入的字节数。

参考

1、
2、C++入门教程,C++基础教程(更新完毕)
3、探寻C++最快的读取文件的方案
4、C语言文件操作、C++文件操作、QT文件操作汇总及对比分析
5、C/C++文件操作效率比较——FILE/fstream
6、C\C++文件操作对比
7、C语言文件操作
8、C++文件操作
9、QT文件操作
10、Qt QFile文件操作详解
11、fseek、ftell和rewind函数,C语言fseek、ftell和rewind函数详解
12、缓冲区与setvbuf函数
13、C++ 各类输入输出方式效率比较+输入输出优化[含fread、fwrite版]
14、Windows平台下fwrite函数0x0A变成0x0D 0x0A

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

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

相关文章

chatgpt赋能python:Python剔除函数的使用介绍

Python剔除函数的使用介绍 在Python编程中&#xff0c;剔除函数是非常有用的工具&#xff0c;它可以帮助程序员快速筛选出不符合条件的数据。本文将介绍剔除函数的概念和常见用法&#xff0c;以及如何有效使用剔除函数解决实际问题。 剔除函数的概念 剔除函数是指Python中的…

反射相关知识点

这里写目录标题 反射概述获取Class对象的三种方式总结具体代码演示 获取构造方法以及构造方法里的信息利用Class对象调用对应方法&#xff0c;以及用调取出来的Constructor&#xff08;构造器类&#xff09;创建对象具体代码 获取成员变量利用Class对象调用对应方法&#xff0c…

chatgpt赋能python:Python加速读取CSV文件的方法

Python加速读取CSV文件的方法 介绍 CSV文件是一种常见的数据格式&#xff0c;因为其简单和易于理解&#xff0c;被广泛应用于数据处理和数据分析。然而&#xff0c;在处理大型CSV文件时&#xff0c;读取速度会成为问题。Python作为一种高级编程语言&#xff0c;具有易学易用的…

AI+是企业管理软件的下一站和终点站

作为GPT综合症的表现&#xff0c;准备陆续写一点关于AI的文章。就从这一篇开始吧。 这篇文章原来是在2019年1月份我发在新浪微博和LinkedIn上的。刚搜了一下&#xff0c;全然不见了踪影。原因大家也都知道。但是&#xff0c;我想那个思想的小火花一定还在&#xff0c;在某处酝…

Jenkins概念及安装配置教程(三)

如何配置Jenkins&#xff1f; Jenkins 中的用户管理 要在 Jenkins 中管理用户&#xff0c;您应该导航到管理 Jenkins &#x1f86a; 配置全局安全。理想的选择是让 Jenkins 拥有自己的用户数据库。您可以创建一个只有读取权限的匿名用户。为您打算在下一步中添加的用户创建条…

【ARMv8 SIMD和浮点指令编程】NEON 加法指令——加法都能玩出花

向量加法包括常见的普通加指令&#xff0c;还包括长加、宽加、半加、饱和加、按对加、按对加并累加、选择高半部分结果加、全部元素加等。如果你和我一开始以为的只有一种普通加&#xff0c;那就太小看设计者了&#xff01;同时这么多加法指令的确会提升我们设计程序的效率&…

numpy包中的编码函数和解码函数numpy.char.encode() numpy.char.decode()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 numpy包中的编码函数和解码函数 numpy.char.encode() numpy.char.decode() [太阳]选择题 下列代码最后输出的结果是&#xff1f; import numpy as np x np.array([I, Love, Python]) print(…

Python jieba库

前言 Jieba库是优秀的中文分词第三方库&#xff0c;中文文本需要通过分词获得单个的词语。 Jieba库的分词原理&#xff1a;利用一个中文词库&#xff0c;确定汉字之间的关联概率&#xff0c;汉字间概率大的组成词组&#xff0c;形成分词结果。除了分词&#xff0c;用户还可以…

【数据库原理与应用 - 第八章】数据库的事务管理与并发控制

目录 一、事务管理 1、概念及特性 2、事务控制 &#xff08;1&#xff09;事务控制语句 显示事务举例 二、并发控制 1、问题引入 2、并发执行带来的问题 &#xff08;1&#xff09;丢失修改 &#xff08;2&#xff09;不可重复读 &#xff08;3&#xff09;读"…

[自学记录02|百人计划]纹理压缩

一、什么是纹理压缩 纹理压缩是为了解决内存、带宽问题&#xff0c;专为在计算机图形渲染系统中存储纹理而使用的图像压缩技术。 1.图片格式和纹理格式的区别 (1)图片格式 图片格式是图片文件的存储格式&#xff0c;通常在磁盘、内存中储存和传输文件时使用&#xff1b;例如…

单片机GD32F303RCT6 (Macos环境)开发 (三十三)—— 光照传感器 (BH1750)

GD32 光照传感器 BH1750的使用 1、GPIO模拟i2c配置 使用管脚为SCL PB10 SDA PB11&#xff0c;移植代码时可换自己的管脚。软件模拟i2c在十九章中讲过&#xff0c;与其不同的地方是&#xff0c;这里的us延时函数&#xff0c;换成了定时器3做us级的延时。 tim3的配置&#xf…

linux 找回root密码(CentOS7.6)

linux 找回root密码(CentOS7.6) 首先&#xff0c;启动系统&#xff0c;进入开机界面&#xff0c;在界面中按“e”进入编辑界面。如图 2. 进入编辑界面&#xff0c;使用键盘上的上下键把光标往下移动&#xff0c;找到以““Linux16”开头内容所在的行数”&#xff0c;在行的最后…

java-字符流和字节流(二)

java-字符流和字节流(二) 一、字节缓冲流 1.1字节缓冲流构造方法 字节缓冲流介绍 BufferOutputStream&#xff1a;该类实现缓冲输出流。 通过设置这样的输出流&#xff0c;应用程序可以向底层输出流写入字节&#xff0c;而不必为写入的每个字节导致底层系统的调用 BufferedIn…

chatgpt赋能python:Python动图如何优化SEO?

Python动图如何优化SEO&#xff1f; Python是一种高级编程语言&#xff0c;广泛应用于数据分析、人工智能和网站开发等领域。Python还支持创建动态图像&#xff0c;这些动态图像通常用于数据可视化、演示和教育目的。在本文中&#xff0c;我们将探讨如何使用Python创建动态图像…

chatgpt赋能python:Python加f之SEO的重要性

Python加f之SEO的重要性 随着互联网的不断发展和普及&#xff0c;越来越多的企业和个人纷纷进入到了网站建设&#xff0c;网络营销的大军之中。而SEO作为重要的一环&#xff0c;在各个领域内也变得愈加重要。而Python中的f字符串是近些年来引起广泛关注的一种新的字符串格式化…

chatgpt赋能python:Python动态内存分配:如何优化你的代码

Python动态内存分配&#xff1a;如何优化你的代码 在编写Python代码时&#xff0c;你可能已经注意到内存使用方面的一些问题。Python动态内存分配是一个重要的话题&#xff0c;它涉及到Python程序如何在运行时使用内存。本文将向您介绍Python动态内存分配的基本概念和如何优化…

chatgpt赋能python:Python动态代码在SEO中的重要性

Python动态代码在SEO中的重要性 Python是一种非常流行的编程语言&#xff0c;用于开发Web应用程序、数据分析、人工智能和机器学习。Python的动态代码能够动态生成HTML、CSS和JavaScript来创建动态网页。这种能力使Python在SEO中非常有用&#xff0c;因为它可以帮助网站排名更…

chatgpt赋能python:如何使用Python制作动画?

如何使用Python制作动画&#xff1f; Python是一种高级编程语言&#xff0c;被广泛应用于各种领域&#xff0c;包括动画制作。Python的简洁性和强大的功能使得它成为一个很好的选择来制作动画。在这篇文章中&#xff0c;我将向您介绍使用Python如何制作动画。 第一步&#xf…

Vue3 相关Composition Api 2

一&#xff0c;其他Composition Api shallowReactive 与 shallowRef shallowReactive:只处理对象最外层属性的响应式&#xff08;浅响应式&#xff09;。 shallowRef:只处理基本数据类型的响应式&#xff0c;不进行对象的响应式处理。 什么时候使用&#xff1f; 如果有一个对…

Hive

Hive 概览 Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张数据库表&#xff0c;并提供类SQL查询功能。 本质是将SQL转换为MapReduce程序。 主要用途&#xff1a;用来做离线数据分析&#xff0c;比直接用MapReduce开发效率更高。 架构 数…