文件IO學習【二】

来源:https://www.cnblogs.com/fly-home/p/18182960
-Advertisement-
Play Games

目錄文件操作介面說明標準IO標準IO函數介紹打開文件:fopen()fopen使用相關知識補充關閉文件:fclose讀取數據字元讀取(fgetc)按行讀取按塊讀取寫入文件字元寫入字元串寫入按塊寫入讀取文件位置設置位移獲取位移格式訪問 文件操作介面說明 Linux系統為了簡化不同類型文件的操作流程,在 ...


目錄

文件操作介面說明

Linux系統為了簡化不同類型文件的操作流程,在設計訪問介面時也遵循POSIX標準,而POSIX標準就是對不同操作系統的訪問介面做出統一的規範目的是提高程式的相容性和可移植性。

大家經常使用的C語言同樣具有語法標準,並且C語言標準在發佈的時候也會發佈對應的庫函數提供給用戶。這些庫函數也同樣遵循POSIX標準進行設計,而遵循POSIX標準設計出來的函數的集合也被稱為標準庫,比如大家使用的標準C庫中提供了標準的輸入輸出函數,這些函數在Linux系統可以使用,同樣也可以在Windows系統中使用。用戶可以根據標準輸入輸出頭文件<stdio.h>中的函數聲明進行調用,Linux系統下該頭文件路徑為 /user/include。

image

另外,由於任何一種操作系統都會有訪問磁碟文件的需求,所以POSIX標準中同樣對訪問文件的輸入輸出介面做出了約束,這些訪問文件的函數介面在C語言標準中都有具體的描述。

標準IO

標準C庫中關於文件輸入輸出的函數介面一般被稱為標準IO,訪問文件常用的標準IO函數有fopen()、fread()、fwrite()、fclose()、fgetc()、fputc()、fgets()、fputs()、fprintf()、fscanf()等。

image

標準IO函數介紹

打開文件:fopen()

想要對文件進行讀寫訪問的前提是必須先打開文件,標準IO中提供了一個函數叫做fopen(),用戶只需要包含標準輸入輸出頭文件 #include <stdio.h> 即可調用。

image

如上圖所示,調用fopen時需要傳入兩個參數,前者為即將要打開的文件,格式為 “xxx.c”,在未標明路徑的前提下,預設打開當前路徑下的文件,若是想要打開別的路徑下文件,需要加上路徑名,格式為“/demo/demo.c”;後者為需要以怎樣的方式打開該文件,具體分類如下圖:

image

fopen函數是有返回值的,如果文件打開成功,則返回值返回指向該文件的文件流指針,如果文件打開失敗,則返回值為NULL。

fopen使用相關知識補充
  • 為什麼某些情況下,打開的文件大小與Linux內儲存文件大小不同?

image

原因:因為在使用上圖mode打開文件時,預設是將文件以文本(.txt)形式打開,該打開過程中,系統會對文件內容進行解釋轉換,最終導致兩個文件大小不一致

解決辦法:在C99標準中,提供了幾個mode,其與上圖mode的區別在於,打開文件時是以二進位形式打開,此時打開方式與linux一致,便不會出現上圖中文件大小不一致的情況。其他特性與不加b的mode保持一致。

image

註意:

  1. 多出來的mode只在C99後標準有效,在C89標準中,使用無效,系統還是會按照文本形式打開文件。
  2. 使用"a"與”a+“打開文件時,游標會被定位至文件末尾,而其餘模式,游標則是會被定位至文件開頭。
  • fopen函數的返回值是一個指向被打開文件的FILE類型的指針,請問FILE類型是什麼?

回答:

FILE類型其實是一個結構體數據類型,它包含了標準 I/O 庫函數為管理文件所需要的所有信息,比如包括用於實際I/O 的文件描述符、指向文件緩衝區的指針、緩衝區的長度、當前緩衝區中的位元組數以及出錯標誌等。頭文件stdio.h中有關於FILE類型的相關描述,如:
image

image

  • 可以看到FILE類型其實就是一個結構體,結構體類型名稱為struct _IO_FILE,但是經過查找之後發現頭文件stdio.h中並沒有關於該結構體的定義,那這個結構體中到底都有哪些成員?

回答:閱讀stdio.h中的條件編譯選項可以發現在stdio.h中還包含了另一個頭文件<libio.h>,這個頭文件中才有關於FILE結構體類型的定義,該頭文件的路徑同樣在Linux系統的/user/include目錄下。

image

image

可以看到FILE結構體類型中有一個成員是FILE類型的指針變數chain,該指針可以指向下一個被打開文件的文件信息區,也就是可以把FILE類型當做數據結構中的鏈表的結點,結點中除了可以存儲數據域之外,還可以利用指針域存儲下一個結點的地址。

簡單理解:用戶可以在一個程式中利用fopen函數打開多個文件,每次打開一個文件,內核就會從*堆記憶體*中申請一塊FILE結構體大小的空間用來存儲文件的所有信息,然後按照文件打開的順序把每個打開的文件的結構體形成一條鏈表,然後使用鏈表頭進行管理。

註意:打開文件的目的無非就是對文件進行讀寫操作,所以每次當程式運行的時候已經有三個文件流被打開分別是標準輸入stdin、標準輸出stdout、標準出錯stderr,這三者在stdio.h中也是FILE指針。

image

所以內核在管理被打開文件的時候,鏈表中已經有三個結點存在,然後再把新節點頭插入到鏈表中。

image

  • 請問為什麼內核在為文件流申請記憶體的時候是申請的堆記憶體?請問有什麼具體依據?

image

回答:如上圖所示,當我們對打開的一個文件進行兩次關閉時,系統在執行時會報不能兩次釋放該記憶體的錯誤,我們可知,fclose實際上是間接調用了free函數進行文件的關閉,側面驗證了內核為文件流申請記憶體時申請的是堆記憶體。

註意:

  1. 使用標準IO的時候,是不可以反覆關閉相同的文件,因為釋放已經被釋放的堆記憶體,會導致段錯誤!!
  2. 但是可以反覆打開同一文件,只不過申請的堆記憶體的地址是在變化的,且關閉時需要一一對應關閉,即打開幾次就需要關閉幾次。

關閉文件:fclose

利用fopen()打開文件之後內核會申請一塊堆記憶體用來存儲文件信息,申請的堆記憶體大小就是FILE結構體類型的大小,那麼如果用戶完成了對文件的讀寫訪問之後,則需要利用fclose()函數來關閉文件,這樣這塊堆記憶體就會被內核先從鏈表中刪除,然後再釋放掉。

image

讀取數據

用戶打開文件後可以從文件中讀取數據,標準C庫中提供了多個讀取函數來滿足用戶的不同需求,這些函數大體分為三類:字元讀取(fgetc)、按行讀取(fgets)、按塊讀取(fread)。

字元讀取(fgetc)

image

image

標準庫中提供了一個fgetc函數,通過C99標準可以知道該函數的作用是從文件指針stream指向的文件中讀取一個字元,併在讀取一個位元組後把文件的游標位置向後移一個位元組,然後把讀取到的字元所對應的ASCII碼通過返回值返回。

在調用該函數時如果文件的游標已經到達文件末尾或者遇到讀取錯誤時,則函數會返回EOFEOF是文件結束標誌,其實是個巨集定義,巨集定義的值為 -1,在頭文件libio.h中有相關描述。

image

另外,在標準庫中還提供了另一個*函數getc()*,這個函數的作用等效於fgetc()函數,只不過getc()函數的實現是利用巨集定義而已。二者的作用是一致的,總體上來說這兩個函數是等價的,但是fgetc函數的使用頻率會更高。

image

image

而還有一個函數可以完成讀取字元的工作---getchar(),但是該函數相較於fgetc()和getc()來說,存在局限性,getchar()函數只能從stdin(標準輸入)中讀取一個字元。

image

註意:

  • 某種特殊情況下,三個函數的作用一致,如:

getchar() == fgetc(stdin) == getc(stdin)

  • 當讀取數據失敗時,我們無法通過返回值判斷是到達文件末尾還是遇到了錯誤

練習:在本地磁碟打開一個存儲少量數據的文本demo.txt,利用fgetc函數把文本中的字元輸出到屏幕,當文本中所有字元都輸出完成後就結束程式。

image

按行讀取

image

標準庫中提供了一個*fgets*函數,通過C99標準可以知道該函數的作用是從文件指針stream指向的文件中讀取一行字元,並把讀取的字元存儲在指針s所指向的字元串內,n為自定義的緩衝區大小,FILE *為需要讀取的目標文件。讀取成功後,返回自定義緩衝區指針s,讀取失敗時,返回NULL;

image

fgets讀取結束情況:

  • 當讀取到n-1個字元時
  • 已經讀取到文件末尾(EOF)
  • 讀取到換行符’\n’時

**思考: ** 為什麼fgets函數讀取到換行符\n時會結束?fgets函數中的參數n的意義是什麼??

回答:用戶調用fopen打開文件之後,可以把數據寫入到文件中以及從文件中讀取數據,但是實現讀取和寫入的過程中其實內核並沒有直接操作文件,而是在操作指向文件的結構體指針FILE,也就是用戶寫入的數據和讀取的數據會先存儲在FILE結構體的*緩衝區*當用戶調用刷新緩衝區的函數或者其他讀寫函數時,FILE結構體的緩衝區會被刷新,數據才會被系統寫入文件。

image

可以看到,每當使用標準IO的讀操作函數,試圖將數據從文件 a.txt讀取出來時,數據都會流過標準*輸入**緩衝區*,然後再在適當的時刻沖洗(或稱刷新,flush)到內核緩衝區,最後才真正得到數據。

思考:什麼是緩衝區?為什麼要有緩衝區?

緩衝區的出現其實就是由於輸入設備和輸出設備對於數據的讀寫速度比較慢,其實就是CPU為了降低輸入輸出次數,目的是為了提高運行效率,避免長時間的等待,所以內核就在記憶體中提供了一塊空間作為緩衝區,緩衝區也可以稱為緩存(Cache),是屬於記憶體空間的一部分。

根據IO設備的不同,可以把緩衝區分為輸入緩衝區和輸出緩衝區【也可以叫做讀緩存區和寫緩衝區】,同樣,根據刷新形式的不同,可以把緩衝區分為三種:全緩衝、行緩衝、無緩衝。

  • 全緩衝:指的是當緩衝區被填滿就立即把數據沖刷到文件、或者在關閉文件、讀取文件內容以及修改緩衝區類型時也會立即把數據沖刷到文件,一般讀寫文件的時候會採用
  • 無緩衝:指的是沒有緩衝區,直接輸出,一般linux系統的標準出錯stderr就是採用無緩衝,這樣可以把錯誤信息直接輸出。
  • 行緩衝:指的是當緩衝區被填滿(一般緩衝區為4KB,就是4096位元組)或者緩衝區中遇到換行符’\n’時,或者在關閉文件、讀取文件內容以及修改緩衝區類型時也會立即把數據沖刷到文件中,一般操作IO設備時會採用,比如printf函數就是採用行緩衝。

當然,全緩衝和行緩衝除了以上幾種情況外,當程式結束時緩衝區也會被刷新,另外,也可以採用函數庫中的fflush函數手動刷新緩衝區。

image

註意:

緩衝類型 全緩衝 無緩衝 行緩衝
例子 普通文件 stderr(標準出錯) stdout(標準輸出)
按塊讀取

標準庫中提供了一個fread函數,通過C99標準可以知道該函數的作用是從給定的文件輸入流stream中讀取最多nmemb個對象到指針ptr指向的字元串中,每個對象的大小為size位元組,函數返回成功讀取的對象個數,若出現錯誤或到達文件末尾,則可能小於nmemb。即讀取是否成功需要拿返回值與預計值進行比較。

註意:若size或nmemb為零,則fread函數返回0且不進行其他動作。但是這樣使用並不會報錯,只是意義而已。

image

image

思考:可以知道函數的返回值如果小於nmemb則說明可能出現讀取錯誤或者到達文件末尾,那應該如何區分這兩種情況?

回答:可以通過標準庫中提供的兩個函數區分,一個函數是feof(),另一個則是ferror函數。

image

註意:

feof 函數在C語言中用於檢測文件結束標誌是否設置。但是, feof 的行為可能會讓人有些誤解,因為它並不直接檢測文件是否已到達未尾。相反,它檢測的是在上一次調用文件讀取函數(如 fgetc、fread 等)時是否遇到了文件結束(EOF)標記

也就是說,feof並不是通過此時游標所在位置來判斷是否到達文件末尾,所以並不能通過結合使用fseek函數來判斷是否到達文件末尾。

具體來說,feof 的工作原理是這樣的:
1.當你嘗試讀取一個文件時,如果文件尚未到達末尾,feof 將返回0(假)
2.當你讀取到文件的末尾時,並不會立即設置文件結束標誌。相反,當你嘗試再次讀取(即超過文件的未尾)時,文件結束標誌會被設置,並且此時 feof 將返回非0值(真)。
3.如果你在讀取文件末尾後沒有再次嘗試讀取,那麼 feof 仍然會返回0(假),!即使文件實際上已經讀取完畢。

因此,在使用 feof 時,,一個常見的做法是在一個迴圈中讀取文件,併在迴圈結束後檢查 feof 的值來確定是否已到達文件末尾。但是,請請註意,如果文件讀取操作因為其他原因(如磁碟錯誤、許可權問題等)而失敗,feof 也可能返回非0值。因此,通常還需要檢查 ferror 函數來確定是否發生了錯誤。

寫入文件

字元寫入

image

image

註意:

​ 特殊情況下,三種函數作用一致,如:

putchar(a) == fputc(a, stdout) == putc(a, stdout)

字元串寫入

image

image

註意:

  • 字元串寫入時,fputs和puts均遇到'\0'便會結束寫入
  • puts函數擁有著自動換行自動刷新緩衝區的特性
按塊寫入

image

image

與按塊讀取函數fread特性大體一致,均是依靠返回值與目標值比較來判斷是否寫入成功,且若size或nmemb為零,則fread函數返回0且不進行其他動作。但是這樣使用並不會報錯,只是意義而已。

讀取文件位置

每個被打開文件的結構體中都有一個位置指示器(簡單理解:位置指示器是文件游標),註意:被打開的文件的游標預設是在文件開頭的。除非打開的模式是“a"或者"a+"。

設置位移

image

image

註意:

該設置游標位置可以靈活使用,來滿足當前需要的條件。如:

當我們利用模式“a”打開文件後,又需要將游標偏移至文件首部,則可以利用 fseek(p,0,SEEK_SET)指令

獲取位移

image

image

註意:

該函數返回的文件位置偏移量是相對於文件開頭來說的

練習:

要求利用標準IO函數介面實現計算一個本地磁碟某個文件的大小,要求文件名稱通過命令行進行傳遞,併進行驗證是否正確( ls -l)。

image

image

格式訪問

標準庫中除了以上關於文件讀寫的函數之外,還提供了一些可以對文件進行格式化讀寫的函數介面,在C99標準中有關於這些函數的描述,如下:

image

註意:一般常用的關於文件IO的格式化函數有printf、fprintf、scanf、fscanf、sprintf、snprintf。

image

image


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • CentOS上搭建SFTP 在CentOS上安裝SFTP服務通常是通過安裝OpenSSH來實現的,因為OpenSSH預設提供了SFTP功能。以下是在CentOS上安裝SFTP的步驟: 一、安裝OpenSSH伺服器: sudo yum install openssh-server 啟動SSH服務: s ...
  • 開發工具 IDE工具:jetbrain IDEA 工具插件:EmmyLua 本機lua項目和調試lua文件 添加lua項目模板,安裝完EmmyLua插件就有了 添加模板項目後,在項目中添加lua類型的文件 為lua項目添加一個編譯調試器 在調試器中,配置lua和lua項目的信息 現在就可以輸出hel ...
  • 目錄系統IO介面說明概念解釋標準IO和系統IO的區別常用系統IO函數介紹打開文件關閉文件文件讀取文件寫入位置偏移 系統IO介面說明 概念解釋 由於Linux系統下“一切皆文件”,也就是Linux系統下的數據和程式都是以文件的形式存儲的,所以Linux內核會提供一組操作文件的函數介面,這組函數介面也被 ...
  • UART UART 1. 什麼是UART? 2. 硬體接線 3. 數據幀格式 4. 波特率 4.1. 波特率和比特率的定義 4.2. 波特率和比特率之間的關係 4.3. 波特率的作用 5. UART通信分析實踐 5.1. 發送"Hello, World!",請寫出數據幀。 5.2. 每秒傳輸多少個字 ...
  • 時序圖 時序圖 1. 參考資料 2. 基礎 3. 符號 3.1. 斜線形式的上升沿、下降沿 3.2. Either or 信號 3.3. 波形省略 3.2.1. 虛線 3.2.2. 波浪號 3.4. 地址&數據表示 4. 實例-WT588F語音晶元時序圖 4.1. 瞭解背景 4.2. 分析 4.3. ...
  • 首先來看下什麼是漏桶演算法和令牌桶演算法 Nginx並不直接實現漏桶演算法或令牌桶演算法,但這些演算法在控制網路流量和請求速率方面非常有用。這些演算法通常在網路編程、API服務、負載均衡等領域中使用,以確保系統的穩定性和性能。 漏桶演算法(Leaky Bucket): * 漏桶演算法用於限制數據的傳輸速率。它可以將 ...
  • 文件IO 筆試題 作業:設計程式,獲取當前系統時間,把時間轉換為特定格式”yy年mm月dd日 星期x tt:mm:ss”,並每隔1s寫入到本地磁碟中一個叫做log.txt的文本中,如果文本不存在則創建。 代碼: /******************************************* ...
  • 目錄標準IO練習題題目:分析:代碼展示結果展示總結知識擴展time()函數localtime()函數 標準IO練習題 題目: 設計程式,獲取當前系統時間,把時間轉換為特定格式”yy年mm月dd日 星期x tt:mm:ss”,並每隔1s寫入到本地磁碟中一個叫做log.txt的文本中,如果文本不存在則創 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...