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<stdio.h>
#include<string.h>
#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;
}

在這裏插入圖片描述

任務2. 調用Unix I/O庫函數,編寫程序task42.c,從鍵盤讀入5個學生的成績信息,包括學號、姓名、語文、數學、英語,成績允許有一位小數,存入一個結構體數組,結構體定義爲:

typedef struct _subject {
	char sno[20];	   //學號
	char name[20];   //姓名
	float chinese;	   //語文成績
	float math;		//數學成績
	float english;	   //英語成績
}  subject;

代碼:

#include<stdio.h>
#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);
	}

}

在這裏插入圖片描述

任務3(可選):在Linux環境下,可以調用庫函數gettimeofday測量一個代碼段的執行時間,請寫一個程序task43.c,測量read、write、fread、fwrite函數調用所需的執行時間,並與prof/gprof工具測的結果進行對比,看是否基本一致。並對四個函數的運行時間進行對比分析。
提示:由於一次函數調用時間太短,測量誤差太多,應測量上述函數多次(如10000次)運行的時間,結果纔會準確。

代碼:

#include<stdio.h>
#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開銷,所以前者的效率更高。(截圖如下:)
在這裏插入圖片描述
附錄: multiply,使用prof/gprof測量程序運行時間

Linux/Unix環境提供了prof/gprof工具來收集一個程序各函數的執行次數和佔用CPU時間等統計信息,使用prof/gprof工具查找程序性能問題,要求編譯命令添加-p選項(prof)或-pg選項(gprof),程序執行時就會產生執行跟蹤文件mon.out(或gmon.out),再運行prof(或gprof)程序讀取跟蹤數據,產生運行報告。現在用gprof對以下程序各函數運行性能(佔用CPU時間)進行測量。先輸入程序源代碼:

#include <stdio.h>
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;
}

在這裏插入圖片描述
在這裏插入圖片描述

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 <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include<malloc.h>
#include<string.h>
//#include<algorithm>
//#include <unistd.h>
#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);
	}

}

在這裏插入圖片描述
在這裏插入圖片描述

後記

第一次寫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"這個頭文件,如果需要進行代碼複用,親手嘗試代碼運行結果的同學,可以私信我轉發。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章