Linux I/O编程 实验内容

一、实验目的:

练习用UNIX I/O进行文件读写的编程方法,用UNIX I/O函数管理文本信息、二进制数据、结构体数据,掌握UNIX I/O的基本编程方法。练习测时函数使用,通过测量UNIX I/O函数运行时间,建立UNIX I/O API函数基本开销的概念。

二、实验内容与要求:

先创建用户家目录下创建文件名为“姓名+学号+04”的子目录,作为本次实验目录,本次实验的所有代码都放到该目录下,要求将所有源代码与数据文件打包成文件”学号-姓名-lab4.tar.gz”, 压缩包与实验报告分别上传到指定目录下。

任务1. 在当前用户目录下创建数据文件student.txt,文件的内部信息存储格式为Sname:S#:Sdept:Sage:Ssex,即“姓名:学号:学院:年龄:性别”,每行一条记录,输入不少于10条学生记录,其中包括学生本人记录。调用标准I/O库编写程序task41.c,从文件中查找Sdept字段值为“计算机与网络安全学院”的文本行,输出到文件csStudent.txt中,保存时各字段顺序调整为S#:Sname:Sage: Ssex:Sdept。
提示:从终端读入一个文本行到字符串 char buf[MAXSIZE]可调用函数可调用函数:
“fgets(buf, MAXSIZE, stdin);”,其中stdin是表示键盘输入设备的文件指针。

代码:

#include
#include
#define MAXSIZE 200
int main()
{
	FILE* fp1, * fp2;
	char buf[MAXSIZE];
	char check[] = "计算机与网络安全学院";
	if ((fp1 = fopen("student.txt", "r")) == NULL)
	{
		printf(" Open Failed!");
		return 0;
	}
	if ((fp2 = fopen("csStudent.txt", "a+")) == NULL)
	{
		printf(" Open Failed!");
		return 0;
	}
	while (!feof(fp1))//文本结束时退出循环
	{
		fgets(buf, MAXSIZE, fp1);//一行行读入

		if (strstr(buf, check))//判断是否属于“计算机与网络安全学院”
		{
			char* a[6];
			char buffer[100];
			a[0] = strtok(buf, ":");//切割字符串成五部分
			a[1] = strtok(NULL, ":");
			a[2] = strtok(NULL, ":");
			a[3] = strtok(NULL, ":");
			a[4] = strtok(NULL, ":");
			a[4][3] = 0;//将换行符去掉
			sprintf(buffer, "%s:%s:%s:%s:%s\n", a[1], a[0], a[3], a[4], a[2]);//格式化写入buffer

			fputs(buffer, fp2);//将buffer写入文本csStudent
		}
	}
	fclose(fp1);
	fclose(fp2);
	return 0;
}

Linux I/O编程 实验内容_第1张图片

任务2. 调用Unix I/O库函数,编写程序task42.c,从键盘读入5个学生的成绩信息,包括学号、姓名、语文、数学、英语,成绩允许有一位小数,存入一个结构体数组,结构体定义为:

typedef struct _subject {
	char sno[20];	   //学号
	char name[20];   //姓名
	float chinese;	   //语文成绩
	float math;		//数学成绩
	float english;	   //英语成绩
}  subject;

代码:

#include
#include"wrapper.h"
typedef struct _subject {
	char sno[20];	   //学号
	char name[20];    //姓名
	float chinese;	   //语文成绩
	float math;	  //数学成绩
	float english;	   //英语成绩
}  subject;

void main() {
	int fd = open("task42.txt", O_RDWR | O_CREAT | O_APPEND, 0777);
	subject student[5];
	subject stu[3];
	int i;
	for (i = 0; i < 5; i++) {
		scanf("%s%s%f%f%f", student[i].sno, student[i].name, &student[i].chinese, &student[i].math, &student[i].english);
		write(fd, &student[i], sizeof(student[i]));
	}
	lseek(fd, 0, SEEK_SET);
	read(fd, &stu[0], sizeof(subject));
	lseek(fd, sizeof(subject), SEEK_CUR);
	read(fd, &stu[1], sizeof(subject));
	lseek(fd, sizeof(subject), SEEK_CUR);
	read(fd, &stu[2], sizeof(subject));
	for (i = 0; i < 3; i++) {
		printf("%s %s %.1f %.1f %.1f\n", stu[i].sno, stu[i].name, stu[i].chinese, stu[i].math, stu[i].english);
	}

}

Linux I/O编程 实验内容_第2张图片

任务3(可选):在Linux环境下,可以调用库函数gettimeofday测量一个代码段的执行时间,请写一个程序task43.c,测量read、write、fread、fwrite函数调用所需的执行时间,并与prof/gprof工具测的结果进行对比,看是否基本一致。并对四个函数的运行时间进行对比分析。
提示:由于一次函数调用时间太短,测量误差太多,应测量上述函数多次(如10000次)运行的时间,结果才会准确。

代码:

#include
#include"wrapper.h"
//struct  timeval{

//       long  tv_sec;  /*秒*/

//       long  tv_usec; /*微秒*/

//};
int main() {
	struct timeval begin, end;
	double write_time , fwrite_time , read_time , fread_time;
	int i;
	char buf[100]="you are good!";
	int fd = open("test.txt",O_RDWR|O_CREAT|O_APPEND,0777);

	gettimeofday(&begin,NULL);//10000次write
	for(i =0;i<10000;i++){
		write(fd,buf,1);
	}
	gettimeofday(&end,NULL);
	write_time = end.tv_usec-begin.tv_usec;
	printf("10000次write的write_time =%.1lf微秒\n",write_time);

	
	lseek(fd,0,SEEK_SET);
	gettimeofday(&begin,NULL);//10000次read
	for(i =0;i<10000;i++){
		read(fd,buf,1);
	}
	gettimeofday(&end,NULL);
	read_time = end.tv_usec-begin.tv_usec;
	printf("10000次read的read_time =%.1lf微秒\n",read_time);
	close(fd);

	FILE* fp1 = fopen("test.txt","w+");
	gettimeofday(&begin,NULL);//10000次fwrite
	for(i =0;i<10000;i++){
		fwrite(buf,1,1,fp1);
	}
	gettimeofday(&end,NULL);
	fwrite_time = end.tv_usec-begin.tv_usec;
	printf("10000次fwrite的fwrite_time =%.2lf微秒\n",fwrite_time);
	fclose(fp1);

	FILE* fp2 = fopen("test.txt","r+");
	gettimeofday(&begin,NULL);//10000次fread
	for(i =0;i<10000;i++){
		fread(buf,1,1,fp2);
	}
	gettimeofday(&end,NULL);
	fread_time = end.tv_usec-begin.tv_usec;
	printf("10000次fread的fread_time =%.2lf微秒\n",fread_time);

	return 0;
}

fwrite和fread有自己的缓冲区,直到缓冲区写满或者指针指向文本结束位置时,才会进行一次I/O操作,大大减少了write和read函数在内核空间和用户空间之间的转换,这种转换会带来非常大的cpu开销,所以前者的效率更高。(截图如下:)
Linux I/O编程 实验内容_第3张图片
附录: multiply,使用prof/gprof测量程序运行时间

Linux/Unix环境提供了prof/gprof工具来收集一个程序各函数的执行次数和占用CPU时间等统计信息,使用prof/gprof工具查找程序性能问题,要求编译命令添加-p选项(prof)或-pg选项(gprof),程序执行时就会产生执行跟踪文件mon.out(或gmon.out),再运行prof(或gprof)程序读取跟踪数据,产生运行报告。现在用gprof对以下程序各函数运行性能(占用CPU时间)进行测量。先输入程序源代码:

#include 
int multiply_quick( int x, int y)
{
	return x * y;
}
int multiply_slow(int x,int  y)
{
	int i, j, k;
	for (i = 0, k = 0; i < x; i++)
		k = k + y;
	return k;
}
int main(int argc, char* argv[])
{
	int i, j;
	int x, y;
	for (i = 0; i < 1000; i++) {
		for (j = 0; j < 1000; j++) {
			x = multiply_quick(i, j);
			y = multiply_slow(i, j);
		}
	}
	printf("x=%d, y=%d\n", x, y);
	return 0;
}

Linux I/O编程 实验内容_第4张图片
Linux I/O编程 实验内容_第5张图片

multipy_slow花费了0.98s,multipy_quick花费了0.01s

任务4:在Linux系统环境下,编写程序task44.c,对一篇英文文章文件的英文单词词频进行统计。
(1)以“单词:次数”格式输出所有单词的词频(必做)
(2)以“单词:次数”格式、按词典序输出各单词的词频(选做)
(3)以“单词:次数”格式输出出现频度最高的10个单词的词频
例如,若某个输入文件内容为:
GNU is an operating system that is free software—that is, it respects users’ freedom.
The development of GNU made it possible to use a computer without software that would trample your freedom.
则输出应该是:
GNU:2
is:3
it:2
……

代码:

#include 
#include 
#include 
#include 
#include
#include
//#include
//#include 
#define MAXSIZE 200
typedef struct Word {
	char word[30];		 //设单词最长为30个字母
	int count;			//该单词出现的次数
}WordNode;
typedef struct article {
	WordNode Word[MAXSIZE]; //单词的集合
	int num;			//这篇文章不同的英文单词的种数
}Article;
void Insert_word(char* temp, Article* a) {
	if (a->num == MAXSIZE) {
		printf("单词表已经满,输入失败\n");
		return;
	}
	if (temp == NULL)
		return;
	if (a->num == 0) {		//情况1:单词表里暂时还没有保存单词
		a->Word[0].word[0] = '\0';
		strcpy(a->Word[0].word, temp);
		a->Word[0].count = 1;
		a->num = 1;
	}
	else {
		int i;
		for (i = 0; i < a->num; i++) {
			if (strcmp(temp, a->Word[i].word) == 0) {//情况2:该单词已经存在
				a->Word[i].count++;
				return;
			}
		}
		a->Word[a->num].word[0] = '\0';
		strcpy(a->Word[a->num].word, temp);			//情况2:该单词是首次输入
		a->Word[a->num].count = 1;
		a->num++;
	}
}
void divide_word(char* temp) {
	int i;
	if (temp == NULL) {
		return;
	}

	for (i = 0; temp[i] != '\0'; i++) {
		if (!((temp[i] >= 'A' && temp[i] <= 'Z') || (temp[i] >= 'a' && temp[i] <= 'z'))) {

			temp[i] = ' ';
		}

	}
}

void paixu(Article* article) {
	int i, j;
	for (i = 0; i < article->num - 1; i++) {
		for (j = 0; j < article->num - 1 - i; j++) {
			if (article->Word[j].count < article->Word[j + 1].count) {
				WordNode temp = article->Word[j];
				article->Word[j] = article->Word[j + 1];
				article->Word[j + 1] = temp;
			}
		}
	}
}
void main()
{
	Article article = {.num=0};
	char temp[1000];
	int i;
	FILE* fd = fopen("./task44.txt", "r");
	while (!feof(fd)) {
		char* p;
		fscanf(fd, "%s", temp);
		divide_word(temp);		//有可能一次读入不止一个单词,所以要分隔开

		p = strtok(temp, " ");	//strtok把temp分成多个字符串
		do {
			Insert_word(p, &article);
		} while ((p = strtok(NULL, " ")) != NULL);//合适调整循环跳出条件

	}
	printf("按词典序输出,所有单词的词频如下:\n");
	for (i = 0; i < article.num; i++) {
		printf("%s:%d\n", article.Word[i].word, article.Word[i].count);
	}
	printf("\n最高词频的前十个单词:\n");
	paixu(&article);			//使用稳定排序法,才能符合题目要求
	for (i = 0; i < 10; i++) {
		printf("%s:%d\n", article.Word[i].word, article.Word[i].count);
	}

}

Linux I/O编程 实验内容_第6张图片
Linux I/O编程 实验内容_第7张图片

后记

第一次写C for Linux ,特别是gcc 编译器特别不习惯,出现了很多没见过的问题。

第一个就是

段错误 (核心已转储)

这种字样,一般就是相当于vs上数组越界,访问到不合理的内存这种情况,多去留意下自己write和read这些I/O类的函数,一行行仔细比对,一定能找到问题所在。

第二个就是c语言很久没写过了,很多语法都不记得了。还犯了在结构体里初始化char数组的小失误。

第三个就是某些头文件在gcc编译器里找不到,只能自己编写成库文件,否则就只能换一个函数来使用。

收获:
read和write是要进行内核模式和用户模式之间的切换的,这种切换会带来非常大的cpu开销,所以相比之下,fwrite和fread有自己的缓冲区,缓冲区满了或文件指针结束了才进行一次读出/写入操作,效率更高。(都是二进制的读写,不适合文本数据)

fgets和fputs,可以一行行的读写,适合文本数据
当不足n个字符时,读到\n就停止,会在末尾增添\0。如果刚刚好n个字符,优先读入\0,不读入\n。

read和write:(参数)

(int fd, const void *buf, size_t nbyte)
文件描述符,缓冲区,指定I/O字节数

fread和fwrite: (参数)

(const void *ptr, size_t size, size_t nmemb, FILE *stream)
目标元素数组的指针,每个元素大小(字节),元素个数(字节),文本指针

fgets和fputs: (参数)

(char *str, int n, FILE *stream)
字符数组的指针,读取的最大字符数,文本指针

注意:FILE * 类型和int类型不相等

编译.c文件时,命令中添加-p选项(prof)或-pg选项(gprof),程序执行时就会产生执行跟踪文件mon.out(或gmon.out),再运行prof(或gprof)程序读取跟踪数据,产生运行报告。

strtok函数可以把字符串切割成若干个字符串。

任务四其实可以用字典树来做,但是很久没摸索过c语言了,实在吃力,不得不放弃了。

最后温馨提醒:以上所有代码都需要"wrapper.h"这个头文件,如果需要进行代码复用,亲手尝试代码运行结果的同学,可以私信我转发。这个头文件只是个针对课程学习内容的万能头文件,有能力的同学可以自己按需添加头文件也可,不一定需要wrapper.h

如果需要wrapper.h,可以私信我转发,但效率一般不高,如果有积分的同学可以到wrapper.h头文件CSDN下载地址https://download.csdn.net/download/enjoy_code_/12521655
自行下载,我虽然设置成了0积分,但还是被系统管理员改成了1积分,请见谅。跪谢理解。

你可能感兴趣的:(linux编程)