扫描器的功能
实现文件的读一般使用库函数fscanf或者fread,那么按照怎样的读取方式才能让扫描器的性能更佳呢?
(1)使用fscanf逐字扫描,并返回。
char scan(FILE*file){
char ch;
if(fscanf(file,"%c",&ch)==EOF){
ch=-1;
}
return ch;
}
这是最简单的实现方式,缺点是每次读取字符时都需要访问文件进行IO。
我们先看看它的效率,首先在主函数内调用扫描器。
//主函数
int main()
{
FILE*file=fopen("test.c","r");
for(int i=0;i<1000000;i++)
while(scan(file)!=-1);
fclose(file);
return 0;
}
主函数内使用扫描器扫描测试文件1000000次,然后使用time命令查看代码的执行时间(测试文件可以是任意文件,这里不给出文件内容了,区别只是执行的时间不同而已)。
$ time main
我们测试了五次,并取了平均值(单位:ms)。
次数
1
2
3
4
5
平均值
real
370
366
374
368
377
371
user
112
140
132
144
140
133.6
sys
256
228
240
224
232
236
可以计算代码的CPU平均执行时间为133.6+236=369.6ms。
(2)使用fscanf和缓冲区结合的方式,每次读取字符时首先尝试从缓冲区内取,缓冲区为空时使用fscanf重新加载缓冲区。
#define BUFLEN 80
int lineLen=0;
int readPos=-1;
char line[BUFLEN];
char scan(FILE*file)
{
if(readPos==lineLen-1)//缓冲区读取完毕
{
int pos;
for(pos=0;pos
if(fscanf(file,"%c",&line[pos])==EOF){//加载时文件结束
line[pos++]=-1;//文件结束标记
break;
}
lineLen=pos;//记录缓冲区长度
readPos=-1;//恢复读取位置
}
readPos++;//移动读取点
return line[readPos]; //获取新的字符
}
按照前边的方式测试代码的执行时间。
次数
1
2
3
4
5
平均值
real
374
380
371
371
374
374
user
140
124
148
136
108
131.2
sys
232
252
220
232
264
240
计算代码的CPU平均执行时间为131.2+240=371.2ms。虽然这种方法只是在缓冲区为空时重新加载缓冲区,避免了每次读取符号进行文件IO的问题,但是却比第一种方式的性能还差。主要是因为加载缓冲区时使用fscanf是按照字节进行加载的,如果使用fread可能会不同。
(3)和方法二类似,只是加载缓冲区时使用fread。fread的参数size表示加载数据块的大小,count表示加载数据块的个数,这里每次加载BUFLEN个1字节数据块。
#define BUFLEN 80
int lineLen=0;
int readPos=-1;
char line[BUFLEN];
char scan(FILE*file)
{
if(readPos==lineLen-1)//缓冲区读取完毕
{
lineLen=fread(line,1,BUFLEN,file);//重新加载缓冲区数据
if(lineLen==0)//没有数据了
{
lineLen=1;
line[0]=-1;//文件结束标记
}
lineLen=pos;//记录缓冲区长度
readPos=-1;//恢复读取位置
}
readPos++;//移动读取点
return line[readPos]; //获取新的字符
}
测试代码的执行时间。
次数
1
2
3
4
5
平均值
real
339
332
334
341
332
335.6
user
96
92
116
108
84
99.2
sys
244
236
216
228
244
233.6
计算代码的CPU平均执行时间为99.2+233.6=332.8ms,可见使用fread读取文件比使用fscanf的效率要高。该方法是每次加载BUFLEN个1字节数据块,如果每次加载1个BUFLEN字节数据块是不是更高效呢?
(4)和方法三类似,