[APUE]標準IO庫(下)

来源:http://www.cnblogs.com/orlion/archive/2016/12/31/6235006.html
-Advertisement-
Play Games

一、標準IO的效率 對比以下四個程式的用戶CPU、系統CPU與時鐘時間對比 程式1:系統IO 程式2:標準IO getc版本 程式3:標準IO fgets版本 結果: 【註:該表截取自APUE,上表中"表3-1中的最佳時間即《程式1》","表3-1中的單位元組時間指的是《程式1》中BUFSIZE為1時 ...


一、標準IO的效率

  對比以下四個程式的用戶CPU、系統CPU與時鐘時間對比

  程式1:系統IO

  

  程式2:標準IO getc版本

  

  程式3:標準IO fgets版本

  

  結果:

  

  【註:該表截取自APUE,上表中"表3-1中的最佳時間即《程式1》","表3-1中的單位元組時間指的是《程式1》中BUFSIZE為1時運行時間結果",fgetc/fputc版本程式這裡沒放出】

  對於三個標準IO版本的每一個其用戶CPU時間都大於最佳read版本,因為每次讀一個字元版本中有一個要執行150萬次的迴圈,而在每次讀一行的版本中有一個要執行30000次的迴圈。而在read版本中,其迴圈只需執行180次。因為系統CPU時間都相同,所以用戶CPU時間的差別造成了時鐘時間的差別。系統CPU時間相同的原因是所有這些程式對內核提出的讀寫請求數相同。

  上表中最後一列是每個main函數的文本空間位元組數(由c編譯產生的機器指令)。從中可見getc/putc版本在文本空間做了大量巨集替換,所以它所需的指令數超過了調用fgetc/fputc函數所用的指令數。從用戶CPU時間看getc/putc版本與fgetc/fputc版本在此次測試中並沒有多大的差別。

  使用每次一行IO的版本其速度大約是每次一個字元版本的兩倍(包括用戶CPU時間和時鐘時間)。如果fgets/fputs函數用getc/putc實現則可以預計fgets版本的時間會與getc版本相接近。可以預料每次一行的版本會更慢一些,因為除了現存的60000次函數調用外還需增加3百萬次巨集調用。而在本測試中每次一行參數是用memccoy實現的,為了提高效率memccpy函數用彙編寫。

  【重點】fgetc版本與程式1 BUFSIZE=1的版本要快得多,兩者都用了約3百萬次函數調用,造成速度差距這麼大的原因在於《程式1》執行了3百萬次函數調用這也執行了3百萬次系統調用,而fgetc版本雖然執行了3百萬次函數調用但是只引起了360次系統調用。系統調用與普通的函數調用相比是很耗時間的。

  

二、二進位IO

  為了可以讀取二進位文件我們可以通過getc/putc實現的,但是這樣必須迴圈整個結構。而fputs/fgets在遇到null字元時就結束,在結構中可能含有null位元組,所以不能使用fgets/fputs。綜上所以提供了下麵兩個函數以執行二進位IO操作

  

#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp);
size_t fwrite(const char *ptr, size_t size, size_t nobj, FILE *fp);
返回值:讀或寫的對象數

  常見的用法:

  • 讀或寫一個二進位數組。例如將一個浮點數組的第2至第5個元素寫至一個文件上:
    float data[10];
    if (fwrite(&data[2], sizeof(float), 4, fp) != 4) {
        fprintf(stderr, "fwrite error");  
    }

    其中,指定size為每個數組元素的長度,nobj為欲寫的元素數。

  •  讀或寫一個結構。例:

    struct {
        short count;
        long total;
        char name[NAMESIZE];    
    } item;
    
    if (fwrite(&item, sizeof(item), 1, fp) != 1) {
        fprintf(stderr, "fwrite error");
    }

     

  對於讀,如果出錯或到達文件尾,則fread返回的數字可能少於nobj。這時應該調用ferro+feof判斷是哪種情況。對於寫如果返回之小於nobj則出錯。

  使用二進位IO的限制是只能用於讀已寫在同一系統上的數據。但是現在很多異構系統通過網路連接在一起,通常會在一個系統上讀取另外一個系統上的數據,這樣的話這兩個函數就不能工作了,原因:

  • 在一個結構中,同一成員的位移量可能隨編譯程式和系統的不同而異,有些編譯器會有優化選項以對齊或緊密包裝結構(節省存儲空間)以便在運行時易於存取結構中的各個成員。這意味著即使在單一系統中,一個結構的二進位存放方式也可能因編譯器選項不通融而不同。
  • 用來存儲多位元組整數和浮點值的二進位格式在不同系統結構間也可能不同。  

三、 定位流

  有兩種方式可以定位標準IO流。

  • ftell和fseek。這兩個函數都假定文件的位置可以存放在一個長整型中。
  • fgetpos和fsetpos。這兩個函數是ANSI C引入的。這兩個函數引進了一個新的抽象數據類型fpos_t,它記錄文件的位置。在非UNIX系統中,這種數據類型可以定義為記錄一個文件的位置所需的長度。

   需要移植到非UNIX系統上運行的程式應使用fgetpos和fsetpos。

  

#include <stdio.h>

long ftell(FILE *fp); 返回值:成功則為當前位置相對於文件首的偏移位元組數,出錯為-1L
int fseek(FILE *fp, long offset, int whence); 返回值:成功為0,出錯為非0
void rewind(FILE *fp);

  對於一個二進位文件,其位置指示是從文件起始位置開始度量並以位元組為單位的。ftell用於二進位文件時,其返回值就是這種位元組位置。為了用fseek定位一個二進位文件,必須指定一個位元組offset,以及解釋這種位移量的方式。whence與lseek函數相同:SEEK_SET表示從文件的起始位置開始,SEEK_CUR表示從當前位置,SEEK_END表示從文件的尾端。

  對於文本文件,它們的文件當前位置可能不以簡單的位元組位移量來度量。在非UNIX系統中可能以不同的格式存放文本文件,為了定位一個文本文件,whence一定要是SEEK_SET,而且offset只能有兩種值:0(表示反繞文件到其起始位置),或者是對該文件的ftell所返回的值。使用rewind函數也可以將一個流設置到文件的起始位置。

#include <stdio.h>

int fgetpos(FILE *fp, fpos_t *pos);
int fsetpos(FILE *fp, const fpos_t *pos);
返回值:成功為0,出錯非0

  fgetpos將當前位置存入pos指向的對象中。在以後調用fsetpos時,可以使用此值將流重定向至該位置。

四、 格式化IO

  1. 格式化輸出

#incldue <stdio.h>

in printf(const char *format, ...);
回值:成功則為輸出字元數,出錯為負值
int fprintf(FILE *fp, const char *format, ...); 
返回值:成功則為輸出字元數,出錯為負值

int sprintf(char *buf, const char *format, ...);
返回值:存入數組的字元數

  sprintf將格式化的字元送入數組buf中。sprintf在該數組的尾端自動加一個null位元組,但該位元組不包含在返回值中。sprintf有可能會使buf指向的緩存溢出。

  printf族的三種變體類似於上面的三種,只不過是可變參數變成了arg

  

#include<stdarg.h>
#include<stdio.h>
int vprintf(const char * f o r m a t, va_list arg) ;
int vfprintf(FILE *f p, const char * f o r m a t, va_list arg) ;
兩個函數返回:若成功則為輸出字元數,若輸出出錯則為負值
int vsprintf(char *b u f, const char * f o r m a t, va_list arg) ;
返回:存入數組的字元數

  2. 格式化輸入

  三個scanf函數:

#include <stdio.h>

int scanf(const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);
int sscanf(const char *buf, const char *format, ...);

  

五、實現細節

  在UNIX中標準IO最終都要調用系統IO。每個IO流都有一個與其關聯的文件描述符,可以用fileno獲取該流對應的文件描述符。

#include <stdio.h>
int fileno(FILE *fp);
返回值:與流相關聯的文件描述符

  為了瞭解所使用的系統中標準IO的實現最好從stdio.h頭文件開始。

  【註:原書中下麵有一個案例這裡沒有放出】

六、臨時文件

  標準IO庫提供了兩個函數以幫助創建臨時文件

#include <stdio.h>

char *tmpnam(char *ptr);
返回值:指向一唯一路徑名的指針
FILE *tmpfile(void);
返回值:成功則為文件指針,出錯為NULL

  tmpnam產生一個與現在文件名(改文件名不是指ptr!該函數用來產生一個唯一文件)不同的一個有效路徑名字元串。每次調用它時,它都產生一個不同的路徑名,最多調用次數是TMP_MAX。TMP_MAX定義在<stdio.h>中

  若ptr是NULL,則所產生的路徑名存放在一個靜態區中,指向該靜態區的指針作為函數值返回。下一次再調用tmpnam時會重寫該靜態區。(這意味著如果我們調用此函數多次,而且想保存路徑名,那我們應該保存該路徑名的副本而不是指針的副本) 如果ptr不是NULL,則認為它指向長度至少是L_tmpnam個字元的數組。(常數L_tmpnam定義在<stdio.h>中)所產生的路徑名存放在該數組中,ptr也作為函數值返回。

  tmpfile創建一個臨時二進位文件。在關閉該文件或程式結束時會自動刪除這種文件。

  tempnam是tmpnam的一個變體,它允許調用者為所產生的路徑名指定目錄和首碼。

#include <stdio.h>
char *tempnam(const char *directory, const char *prefix);
返回值:指向一唯一路徑名的指針

  對於目錄有四種不同的選擇:(優先順序從高至低)

  (1) 如果定義了環境變數TMPDIR,則用其作為目錄。

  (2) 如果參數directory非NULL,則用其作為目錄。

  (3) 將<stdio.h>中的字元串P_tmpdir用作為目錄。

  (4) 將本地目錄,通常是/tmp用作為目錄。

  如果prefix非NULL,則它通常是最多包含5個字元的字元串,用其作為文件名的前幾個字元。

  該函數調用malloc函數分配動態存儲區,用其存放所構造的路徑名。當不再使用該路徑名時就可釋放次存儲區。


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

-Advertisement-
Play Games
更多相關文章
  • 今天學習主要內容: Python: 1、with語句(補充昨天的文件操作) 用with打開的文件在腳本結束會自動關閉,以防普通打開方式忘記關閉文件連接 語法: with open("demo.txt","r",encoding="utf-8") as file: for line in file: ...
  • JVM的類載入機制就是:JVM把描述類的class文件載入到記憶體,並對數據進行校驗、轉換解析和初始化,最終形成可以被JVM直接使用的Java類型 ...
  • 假設網站A有以下功能需求:1,pc端微信掃碼登錄;2,微信瀏覽器中的靜默登錄功能需求,這兩種需求就需要用到用戶的unionID,這樣才能在多個登錄點(終端)識別用戶。那麼這兩種需求下用戶的unionID該如何獲取呢? 1,先看pc端的解決方案 以snsapi_login為scope發起網頁授權,先拿 ...
  • 攔截導彈動態規劃問題 某國為了防禦敵國的導彈襲擊,發展出一種導彈攔截系統。但是這種導彈攔截系統有一個缺陷:雖然它的第一發炮彈能夠到達任意的高度,但是以後每一發炮彈都不能高於前一發的高度。某天,雷達捕捉到敵國的導彈來襲。由於該系統還在試用階段,所以只有一套系統,因此有可能不能攔截所有的導彈。 輸入導彈 ...
  • DispatcherServlet是Spring MVC的核心,按照傳統方式, 需要把它配置到web.xml中. 我個人比較不喜歡XML配置方式, XML看起來太累, 冗長繁瑣. 還好藉助於Servlet 3規範和Spring 3.1的功能增強, 可以採用一種全新的,更簡潔的方式配置Spring M ...
  • 作者:虛靜 鏈接:https://zhuanlan.zhihu.com/p/24656161 來源:知乎 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。 先說明幾件事: 題目的意思是,用於獲取“QQ空間動態”的爬蟲,而不是”針對QQ空間“的”動態爬蟲“ 這裡的QQ空間動態,特指 ...
  • 如果SSH框架下,前段頁面通過from表單提交數據之後,在後臺對象顯示空值,也就是接收不到值得情況下。首先保證前段輸入框有值,這個可以在提交的時候用jQuery的id或者name選擇器alert彈出測試下。如果前段彈出顯示有值的情況下。可以去後臺action中看看接受的對象有沒有給get跟set方法 ...
  • 轉載:http://blog.csdn.net/luoweifu/article/details/10721543 我進行了一些加工,不是本人原創但比原博主要更完善~ 淺談Java異常 以前雖然知道一些異常的處理,也用過一些,但是對throw和throws區別還是有不太清楚。今天用實例測試一下 異常 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...