文件基礎IO

理解"文件"

1-1 狹義理解

  • 文件在磁盤里
  • 磁盤是永久性存儲介質,因此文件在磁盤上的存儲是永久性的
  • 磁盤是外設(即是輸出設備也是輸入設備)
  • 磁盤上的文件 本質是對文件的所有操作,都是對外設的輸入和輸出簡稱IO

?1-2 廣義理解

  • Linux 下?切皆文件(鍵盤、顯示器、網卡、磁盤…… 這些都是抽象化的過程)

1-3 文件操作的歸類認知?

  • 對于 0KB 的空文件是占用磁盤空間的,文件創建時間,屬性,權限....都是需要存儲的
  • 文件是文件屬性(元數據)和文件內容的集合(文件 = 屬性(元數據)+ 內容
  • 所有的文件操作本質是文件內容操作和文件屬性操作

1-4 系統角度?

訪問文件,需要先打開文件!誰打開文件??進程打開文件!對文件的操作,本質是:進程對文件的操作!

  • 對文件的操作本質是進程對文件的操作
  • 磁盤的管理者是操作系統
  • 文件的讀寫本質不是通過 C 語言?/ C++ 的庫函數來操作的(這些庫函數只是為用戶提供方便),而是通過文件相關的系統調用接口來實現的

?回顧C文件接口

?

  1 #include<stdio.h>2 #include<string.h>3 int main()4 {5 FILE *fp=fopen("log.txt","w");6 7   if(fp==NULL)8  {9   perror("fopen");10   return 1;11  }12 const char *msg="hello bit";13 int cnt=1;14 while(cnt<=10)15 {16   char buffer[1024];17   snprintf(buffer,sizeof(buffer),"%s%d\n",msg,cnt++);                                                                                                                                    18   fwrite(buffer,strlen(buffer),1,fp);19 }20 21 fclose(fp);22 23   return 0;24 }

?

?稍作修改,實現簡單cat命令:

#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[])
{
if (argc != 2)
{
printf("argv error!\n");
return 1;
}
FILE *fp = fopen(argv[1], "r");
if(!fp){
printf("fopen error!\n");
return 2;
}
char buf[1024];
while(1){
int s = fread(buf, 1, sizeof(buf), fp);
if(s > 0){
buf[s] = 0;
printf("%s", buf);
}
if(feof(fp)){
break;
}
}
fclose(fp);
return 0;
}

?輸出信息到顯示器三種方法

c++還有cout,其實這些都是封裝了最原始的writ

#include <stdio.h>
#include <string.h>
int main()
{
const char *msg = "hello fwrite\n";
fwrite(msg, strlen(msg), 1, stdout);
printf("hello printf\n");
fprintf(stdout, "hello fprintf\n");
return 0;
}

?2-5 stdin & stdout & stderr

  • C默認會打開三個輸?輸出流,分別是stdin, stdout, stderr
  • 仔細觀察發現,這三個流的類型都是FILE*, fopen返回值類型,文件指針
  • ?stdin 標準輸入 鍵盤文件
  • stdout 標準輸出 顯示器文件
  • stderr標準錯誤 顯示器文件
#include <stdio.h>
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

2-6 打開文件的方式

  1. r:以只讀模式打開文本文件,文件指針位于文件開頭。
  2. r+:以讀寫模式打開文件,文件指針位于文件開頭。可以讀取和寫入文件內容。
  3. w:若文件存在則將其內容清空(截斷為零長度),若文件不存在則創建一個新的文本文件用于寫入,文件指針位于文件開頭。
  4. w+:以讀寫模式打開文件。若文件不存在則創建,若存在則清空內容,文件指針位于文件開頭。
  5. a:以追加模式打開文件,若文件不存在則創建。文件指針位于文件末尾,寫入的內容會追加到文件現有內容之后。
  6. a+:以讀寫和追加模式打開文件。若文件不存在則創建,讀取時文件指針位于文件開頭,寫入時內容總是追加到文件末尾
r
Open text file for reading.
The stream is positioned at the beginning of the file.
r+
Open for reading and writing.
The stream is positioned at the beginning of the file.
w
Truncate(縮短) file to zero length or create text file for writing.
The stream is positioned at the beginning of the file.
w+
Open for reading and writing.
The file is created if it does not exist, otherwise it is truncated.
The stream is positioned at the beginning of the file.
a
Open for appending (writing at end of file).
The file is created if it does not exist.
The stream is positioned at the end of the file.
a+
Open for reading and appending (writing at end of file).
The file is created if it does not exist. The initial file position
for reading is at the beginning of the file,
but output is always appended to the end of the file.

系統文件I/O

?

打開文件的方式不僅僅是fopen,ifstream等流式,語言層的方案,其實系統才是打開文件最底層的方案。不過,在學習系統文件IO之前,先要了解下如何給函數傳遞標志位,該方法在系統文件IO接口中會使用到:

?一種傳遞標志位的方法(位圖)

 #include<stdio.h>2 3 #define ONE_FLAG (1<<0) //0000 0000 0000...0000 00014 #define TWO_FLAG (1<<1) //0000 0000 0000...0000 00105 #define THREE_FLAG (1<<2) //0000 0000 0000...0000 0100 6 #define FOUR_FLAG (1<<3) //0000 0000 0000...0000 0100 7 8 void Print(int flags)9 {10   if(flags & ONE_FLAG)11   {12     printf("One!\n");13   }14   if(flags & TWO_FLAG)15   {16     printf("Two!\n");17   }18   if(flags & THREE_FLAG)19   {20     printf("Three!\n");21   }22   if(flags & FOUR_FLAG)23   {24     printf("Four!\n");25   }26 }27 int main()28 {29 30   Print(1);31   printf("\n");32   Print(1|2);33   printf("\n");                                                                                                                                                                          34   Print(1|2|4);                                                                                                            35   return 0;                                                                                                                36 }

?hello.c寫文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
umask(0);
int fd = open("myfile", O_WRONLY|O_CREAT, 0644);
if(fd < 0){
perror("open");
return 1;
}
int count = 5;
const char *msg = "hello bit!\n";
int len = strlen(msg);
while(count--){
write(fd, msg, len);//fd: 后?講, msg:緩沖區?地址, len: 本次讀取,期望寫
?多少個字節的數據。 返回值:實際寫了多少字節數據
}
close(fd);
return 0;
}

hello.c讀文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
const char *msg = "hello bit!\n";
char buf[1024];
while(1){
ssize_t s = read(fd, buf, strlen(msg));//類?write
if(s > 0){
printf("%s", buf);
}else{
break;
}
}
close(fd);
return 0;
}

open

  • 讀 int open(const char *pathname,int flags);
  • 寫 int open (const char *pathname,int flags,mode_t mode );
pathname: 要打開或創建的?標?件
flags: 打開?件時,可以傳?多個參數選項,?下?的?個或者多個常量進?“或”運算,構成
flags。
參數:
O_RDONLY: 只讀打開
O_WRONLY: 只寫打開
O_RDWR : 讀,寫打開
這三個常量,必須指定?個且只能指定?個
O_CREAT : 若?件不存在,則創建它。需要使?mode選項,來指明新?件的訪問
權限
O_APPEND: 追加寫
返回值:
成功:新打開的?件描述符
失敗:-1

open 函數具體使用哪個,和具體應用場景相關,如目標文件不存在,需要open創建,則第三個參數表示創建文件的默認權限,否則,使用兩個參數的open。

#include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 int main()6 {7 8 int fd=open("log.txt",O_CREAT|O_WRONLY,0666);                                                                                                                                            9 if(fd<0)10 {11   perror("open");12   return 1;13 }14 15   return 0;16 }

?下面,權限設的是666但是這里是664,umask掩碼給屏蔽了,open是系統調用,權限掩碼的影響是在系統內部,只要設置umsk(0);就能解決,umsk是設置文件創建時的掩碼可以屏蔽掉系統內部權限的影響,用戶設置說明就是什么。write read close lseek ,類比C文件相關接口。

?

fopen fclose fread fwrite 都是C標準庫當中的函數,我們稱之為庫函數(libc)。而open close read write lseek 都屬于系統提供的接口,稱之為系統調用接口。

read

ssize_t read(int fd,void *buf,size_t count);

從指定文件描述符讀取count個緩沖區的大小,返回讀取成功的字節大小數,小于零表示失敗,等于零都到結尾

回憶一下講操作系統概念時,畫的張圖:

系統調用接口和庫函數的關系,一目了然。
所以,可以認為, f# 系列的函數,都是對系統調用的封裝,方便二次開發。?

文件描述符fd

0 & 1 & 2

Linux進程默認情況下會有3個缺省打開的文件描述符,分別是標準輸入0, 標準輸出1, 標準錯
誤2.
? 0,1,2對應的物理設備一般是:鍵盤,顯示器,顯示器
所以輸入輸出還可以采用如下方式:

#include <stdio.h>
#include <sys/types.h>
1
2
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0){
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}

?而現在知道,文件描述符就是從0開始的小整數。當我們打開文件時,操作系統在內存中要創建相應的數據結構來描述目標文件。于是就有了file結構體。表示一個已經打開的文件對象。而進程執行open系統調用,所以必須讓進程和文件關聯起來。每個進程都有一個指針*files, 指向一張表files_struct,該表最重要的部分就是包含一個指針數組,每個元素都是一個指向打開文件的指針!所以,本質上,文件描述符就是該數組的下標。所以,只要拿著文件描述符,就可以找到對應的文件。對于以上原理結論我們可通過內核源碼驗證:

文件描述符的分配規則

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}

輸出發現是 fd: 3

關閉0或者2,在看?

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}

?發現是結果是: fd: 0 或者 fd 2 ,可見,文件描述符的分配規則:在files_struct數組當中,找到
當前沒有被使用的最小的?個下標,作為新的文件描述符。

?進程里面有一個stuct*file能找到文件描述符表, file是一個結構體,里面含有文件的各種屬性,fd是指針數組,里面通過下標能訪問到各自file,文件被打開時,創建file文件,再把file存入文件描述符表中最小的空的fd里面,fd通過地址文件能找到file能訪問文件。

?

進程在調用read等接口時操作系統拿著fd索引來到文件描述符表找到該fd內的file地址,每一個文件都要有自己的文件緩沖區,操作系統預加載,file找到緩沖區,將緩沖區的內容拷貝給read等接口自己的緩沖內,所以讀寫的本質就是拷貝!

重定向

那如果關閉1呢?看代碼:

#include <stdio.h>2 #include <sys/types.h>3 #include <sys/stat.h>4 #include <fcntl.h>5 #include <stdlib.h>6 int main()7 {8 close(1);9 int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);10 11 printf("fd: %d\n", fd);                                                                                                                                                                12 }

把本來應該寫入顯示器的內容居然寫進了文件里!

為什么不顯示?因為把標準輸出關了。

為什么又寫進了文件里?因為打開了這個文件。?

此時,我們發現,本來應該輸出到顯示器上的內容,輸出到了文件 myfile 當中,其中,fd=1。這
種現象叫做輸出重定向。常見的重定向有: > , >> , <
那重定向的本質是什么呢?

?文件描述符表包含了fd_array[],數組下標就是對應打開的文件,系統默認打開標準輸入(0),標準輸出(1),標準錯誤(2),而我要打開新的文件log.txt之前把標準輸出關了,此時文件描述符表下標為1的指向就不在是標準輸出,后來我open打開了一個文件log.txt,根據文件描述符分配規則,又因為最小未被使用的下標恰好就是剛才釋放的下標為1的文件描述符,1的地址就是log.txt的地址,把1返回給上層用戶,printf就是往stdout內打印的,它是封裝了標準輸出fd=1,上述操作是在操作系統內部實現的,而用戶層printf只認文件描述符1,通過文件描述符1找到對應的文件寫入內容。

所以,這種在操作系統內部更改內容指向,和用戶層沒關系,這種叫重定向,是在內核上做的貍貓換太子!

使用?dup2 系統調用

#include <unistd.h>
int dup2(int oldfd, int newfd);

?輸出重定向

從文件輸出顯示器

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
close(1);
int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);
if(fd<0)return 1;
dup2(fd,1);
printf("fd: %d\n", fd);
printf("hello bit\n");
printf("hello bit\n");
fprintf(stdout,"hello stdout\n");}

輸入重定向?

從文件輸入進顯示器

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
int fd = open("log.txt", O_RDONLY);
if(fd<0)return 1;
dup2(fd,0);
while(1)
{char buffer[64];if(!fgets(buffer,sizeof(buffer),stdin))break;printf("%s",buffer);}}

?

?printf是C庫當中的IO函數,一般往 stdout 中輸出,但是stdout底層訪問文件的時候,找的還是fd:1,但此時,fd:1下標所表示內容,已經變成了myfifile的地址,不再是顯示器文件的地址,所以,輸出的任何消息都會往文件中寫入,進而完成輸出重定向。那追加和輸入重定向如何完成呢?

?

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include<unistd.h>
#include<string.h>
int main(int argc,char* argv[])
{if(argc!=2)exit(1);
int fd = open(argv[1], O_RDONLY);
if(fd<0)return 1;
dup2(fd,0);
while(1)
{char buffer[64];if(!fgets(buffer,sizeof(buffer),stdin))break;printf("%s",buffer);}}

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/894851.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/894851.shtml
英文地址,請注明出處:http://en.pswp.cn/news/894851.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Unity 簡易的UI框架

核心內容 UIType.cs namespace MYTOOL.UI {/// <summary>/// UI層級/// </summary>public enum UILayer{/// <summary>/// 主界面層/// </summary>MainUI 0,/// <summary>/// 普通界面層/// </summary>NormalUI 1,/// <summary>/…

VUE2雙向綁定的原理

文章目錄 VUE2雙向綁定的原理1. 什么是雙向綁定2. 雙向綁定的原理2.1 ViewModel的重要作用2.2 雙向綁定的流程 3. 雙向綁定的實現3.1 data響應化處理3.2 Compile編譯3.3 依賴收集 VUE2雙向綁定的原理 1. 什么是雙向綁定 講雙向綁定先講單項綁定&#xff0c;啥叫單項綁定&…

4G核心網的演變與創新:從傳統到虛擬化的跨越

4G核心網 隨著移動通信技術的不斷發展&#xff0c;4G核心網已經經歷了從傳統的硬件密集型架構到現代化、虛擬化網絡架構的重大轉型。這一演變不僅提升了網絡的靈活性和可擴展性&#xff0c;也為未來的5G、物聯網&#xff08;LOT&#xff09;和邊緣計算等技術的發展奠定了基礎。…

云計算——AWS Solutions Architect – Associate(saa)1、什么是云,AWS介紹

什么是云? 什么是云? 云計算(cloud computing)是基于互聯網的相關服務的增加、使用和交付模式&#xff0c;通常涉及通過互聯網來提供動態易護展且經常是虛擬化的資源。云是網絡、互聯網的一種比喻說法。 簡單理解為&#xff1a;云是 共享資源&#xff0c;按需付費&#xff0…

HTML排版標簽、語義化標簽、塊級和行內元素詳解

目錄 前言 一、HTML中的排版標簽 1. 文本相關標簽 1.1 標題標簽 ~ 1.2 段落標簽 1.3 強調和加粗 1.4 換行標簽 1.5 水平線標簽 二、HTML中的語義化標簽 2.1 語義化標簽概述 2.2 常見的語義化標簽 示例&#xff08;核心代碼部分&#xff09;&#xff1a; 三、HTM…

【字節青訓營-7】:初探 Kitex 字節微服務框架(使用ETCD進行服務注冊與發現)

本文目錄 一、Kitex概述二、第一個Kitex應用三、IDL四、服務注冊與發現 一、Kitex概述 長話短說&#xff0c;就是字節跳動內部的 Golang 微服務 RPC 框架&#xff0c;具有高性能、強可擴展的特點&#xff0c;在字節內部已廣泛使用。 如果對微服務性能有要求&#xff0c;又希望…

【數學】矩陣、向量(內含矩陣乘法C++)

目錄 一、前置知識&#xff1a;向量&#xff08;一列或一行的矩陣&#xff09;、矩陣1. 行向量2. 列向量3. 向量其余基本概念4. 矩陣基本概念5. 關于它們的細節 二、運算1. 轉置&#xff08;1&#xff09;定義&#xff08;2&#xff09;性質 2. 矩陣&#xff08;向量&#xff0…

TCP/IP 郵件

TCP/IP 郵件 引言 在互聯網技術飛速發展的今天,電子郵件(Email)已成為人們日常工作和生活中不可或缺的通信工具。TCP/IP協議作為互聯網通信的基礎,為電子郵件的傳輸提供了強大的技術支持。本文將詳細介紹TCP/IP在電子郵件傳輸過程中的作用,以及相關的協議和實現方式。 …

離線安裝Appium Server

1、問題概述? 安裝Appium通常有兩種方式: 第一種:下載exe安裝包,這種是Appium Server GUI安裝方式,缺點是通過命令啟動不方便。 第二種:通過cmd安裝appium server,可以通過命令方式啟動,比較方便。 問題:在沒有外網的情況下,無法通過命令在cmd中安裝appium server…

設計模式六大原則和單例模式

設計模式 目的 實現可重用解決方案&#xff0c;構筑易維護、可擴展的軟件系統。 六大原則 單一職責&#xff1a; 類的職責單一&#xff0c;一個方法做一件事。 開閉原則&#xff1a; 拓展開放&#xff0c;修改關閉。 里氏替換&#xff1a; 父類能出現的地方&#xff0c;子…

淺嘗yolo11全程記錄1-準備環境+官網模型推理(個人備份)

準備工作&#xff08;虛擬環境、導入項目&#xff09; 安裝Anaconda 主要是為了創建和管理虛擬環境&#xff0c;在pycharm里按照項目里的requirments.txt安裝依賴的時候&#xff0c;使用虛擬環境會好很多&#xff08;我記得不用Anaconda也可以直接在pycharm的terminal里頭創建…

5.攻防世界 fileinclude

進入題目頁面如下 提示flag在flag.php ctrlu&#xff0c;查看源碼 給出了一段PHP代碼&#xff0c;進行代碼審計 <?php // 檢查是否開啟了錯誤顯示功能 if( !ini_get(display_errors) ) {// 如果沒有開啟&#xff0c;則將錯誤顯示功能設置為開啟狀態ini_set(display_error…

深入淺出 NRM:加速你的 npm 包管理之旅

文章目錄 前言一、NRM 是什么&#xff1f;二、為什么需要 NRM&#xff1f;三、NRM 的優勢四、NRM 的安裝與使用4.1 安裝 NRM4.2 查看可用的 npm 源4.3 切換 npm 源4.4 測試 npm 源速度4.5 添加自定義 npm 源4.6 刪除 npm 源 五、NRM 的進階使用六、總結 前言 作為一名 JavaScr…

《C#之集訓1-20121019c#基礎》

&#xfeff;&#xfeff; C#是微軟公司發布的一種面向對象的、運行于.NET Framework之上的高級程序設計語言。它是微軟公司研究員Anders Hejlsberg的最新成果。 C#曾經的它在我眼中是很高大上的&#xff0c;一直沒有目睹其風采&#xff0c;現在終于揭開了它神秘的面紗&#xf…

紅包雨項目前端部分

創建項目 pnpm i -g vue/cli vue create red_pakage pnpm i sass sass-locader -D pnpm i --save normalize.css pnpm i --save-dev postcss-px-to-viewportpnpm i vantlatest-v2 -S pnpm i babel-plugin-import -Dhttps://vant.pro/vant/v2/#/zh-CN/<van-button click&…

藍橋杯嵌入式備賽(三)—— LED +按鍵 + LCD

目錄 一、LED1、原理圖介紹2、程序代碼 二、按鍵1、原理圖介紹2、程序代碼 三、LCD1、原理圖介紹2、程序代碼 一、LED 1、原理圖介紹 如果所示&#xff0c;STM32G431RBT6中有八個LED&#xff0c;由八個GPIO控制&#xff0c;分別為PC8-15&#xff0c;當輸出為低電平時點亮。其中…

深入剖析 HTML5 新特性:語義化標簽和表單控件完全指南

系列文章目錄 01-從零開始學 HTML&#xff1a;構建網頁的基本框架與技巧 02-HTML常見文本標簽解析&#xff1a;從基礎到進階的全面指南 03-HTML從入門到精通&#xff1a;鏈接與圖像標簽全解析 04-HTML 列表標簽全解析&#xff1a;無序與有序列表的深度應用 05-HTML表格標簽全面…

[Java基礎]函數式編程

Lambda函數 JDK8新增的語法形式, 使用Lambda函數替代某些匿名內部類對象&#xff0c;從而讓程序代碼更簡潔&#xff0c;可讀性更好。 基本使用 lambda表達式只能簡化函數式接口的匿名內部類寫法 // 1.定義抽象類 abstract class Animal {public abstract void crt(); }publi…

Vue通過觸發與監聽事件進行數據傳遞: 子組件調用 $emit 方法來將數據傳遞給父組件。

文章目錄 引言I 組件事件事件參數defineEmits 宏聲明需要拋出的事件事件校驗例子:子組件告訴父組件放大所有博客文章的文字II 【詳細說明】 子組件通過觸發一個事件,將數據傳遞給父組件調用內建的 `$emit `方法傳入事件名稱來觸發一個事件子組件通過`this.$emit`來觸發一個事…

Vim 多窗口編輯及文件對比

水平分割 :split 默認使用水平分割的方式。 :split :sp 垂直分割 :vsplit :vs 帶文件的分割 :split 文件名 :sp 文件名 在光標所在的窗口&#xff0c;輸入分割窗口命令就會對那個窗口進行分割。 切換窗口 Ctrlw 切換正在編輯的窗口 快速分割窗口 Ctrlwn 快速分割當前…