電腦程式的思維邏輯 (56) - 文件概述

来源:http://www.cnblogs.com/swiftma/archive/2016/12/05/6132357.html
-Advertisement-
Play Games

從本節開始,我們探討文件,本節主要介紹文件的一些基本概念和常識,Java中處理文件的基本思路和類,以及接下來的章節安排思路。 ...


我們在日常電腦操作中,接觸和處理最多的,除了上網,大概就是各種各樣的文件了,從本節開始,我們就來探討文件處理,本節主要介紹文件有關的一些基本概念和常識,Java中處理文件的基本思路和類結構,以及接來下章節的安排思路。

基本概念和常識

二進位思維

為了透徹理解文件,我們首先要有一個二進位思維。所有文件,不論是可執行文件、圖片文件、視頻文件、Word文件、壓縮文件、txt文件,都沒什麼可神秘的,它們都是以0和1的二進位形式保存的。我們所看到的圖片、視頻、文本,都是應用程式對這些二進位的解析結果。

作為程式員,我們應該有一個編輯器,能查看文件的二進位形式,比如UltraEdit,它支持以十六進位進行查看和編輯。比如說,一個文本文件,看到的內容為:

hello, 123, 老馬

打開十六進位編輯,看到的內容為:


左邊的部分就是其對應的十六進位,"hello"對應的十六進位是"68 65 6C 6C 6F",對應ASCII碼編號"104 101 108 108 111","馬"對應的十六進位是"E9 A9 AC",這是"馬"的UTF-8編碼。

文件類型

正如我們在第一節講到的,所有數據都是以二進位形式保存的,但為了方便處理數據,高級語言引入了數據類型的概念,文件處理也類似,所有文件都是以二進位形式保存的,但為了便於理解和處理文件,文件也有文件類型的概念。

文件類型通常以尾碼名的形式體現,比如,PDF文件類型的尾碼是.pdf,圖片文件的一種常見尾碼是.jpg,壓縮文件的一種常見尾碼是.zip。每種文件類型都有一定的格式,代表著文件含義和二進位之間的映射關係。比如一個Word文件,其中有文本、圖片、表格,文本可能有顏色、字體、字型大小等,doc文件類型就定義了這些內容和二進位表示之間的映射關係。有的文件類型的格式是公開的,有的可能是私有的,我們也可以定義自己私有的文件格式。

對於一種文件類型,往往有一種或多種應用程式可以解讀它,進行查看和編輯,一個應用程式往往可以解讀一種或多種文件類型。

在操作系統中,一種尾碼名往往關聯一個應用程式,比如.doc尾碼關聯Word應用。用戶通過雙擊試圖打開某尾碼名的文件時,操作系統查找關聯的應用程式,啟動該程式,傳遞該文件路徑給它,程式再打開該文件。

需要說明的是,給文件加正確的尾碼名是一種慣例,但並不是強制的,如果尾碼名和文件類型不匹配,應用程式試圖打開該文件時可能會報錯。另外,一個文件可以選擇使用多種應用程式進行解讀,在操作系統中,一般通過右鍵單擊文件,選擇打開方式即可。

文件類型可以粗略分為兩類,一類是文本文件,另一類是二進位文件。文本文件的例子有普通的.txt文件, 程式源代碼文件.java, HTML文件.html等,二進位文件的例子有壓縮文件.zip, pdf文件, mp3文件, excel文件等。

基本上,文本文件里的每個二進位位元組都是某個可列印字元的一部分,都可以用最基本的文本編輯器進行查看和編輯,如Windows上的notepad, Linux上的vi。

二進位文件中,每個位元組就不一定表示字元,可能表示顏色、可能表示字體、可能表示聲音大小等,如果用基本的文本編輯器打開,一般都是滿屏的亂碼,需要專門的應用程式進行查看和編輯。

文本文件的編碼

對於文本文件,我們還必須註意文件的編碼方式。文本文件中包含的基本都是可列印字元,但字元到二進位的映射,即編碼,卻有多種方式,如GB18030, UTF-8,我們在如何從亂碼中恢復一節詳細介紹過各種編碼,這裡就不贅述了。

對於一個給定的文本文件,它採用的是什麼編碼方式呢?一般而言,我們是不知道的。那應用程式用什麼編碼方式進行解讀呢?一般使用某種預設的編碼方式,可能是應用程式預設的,也可能是操作系統預設的,當然也可能採用一些比較智能的演算法自動推斷編碼方式。

對於UTF-8編碼的文件,我們需要特別說明一下,有一種方式,可以標記該文件是UTF-8編碼的,那就是在文件最開頭,加入三個特殊位元組 (0xEF 0xBB 0xBF),這三個特殊位元組被稱為BOM頭,BOM是Byte Order Mark (即位元組序標記) 的縮寫。比如,對前面的hello.txt文件,帶BOM頭的UTF-8編碼的十六進位形式為:

都是UTF-8編碼,看到的字元內容也一樣,但二進位內容不一樣,一個帶BOM頭,一個不帶BOM頭。

需要註意的是,帶BOM頭的UTF-8編碼文件不是所有應用程式都支持的,比如PHP就不支持BOM,如果你的PHP源代碼文件帶BOM頭的,PHP運行就會出錯,碰到這種問題時,前面介紹的二進位思維就特別重要,不要只看文件的顯示,還要看文件背後的二進位。

另外,我們需要說明下文本文件的換行符,在Windows系統中,換行符一般是兩個字元"\r\n",即ASCII碼的13('\r')和10('\n'),在Linux系統中,換行符一般是一個字元"\n"。

文件系統

文件一般是放在硬碟上的,一個機器上可能有多個硬碟,但各種操作系統都會隱藏物理硬碟概念,提供一個邏輯上的統一結構。在Windows中,可以有多個邏輯盤,C, D, E等,每個盤可以被格式化為一種不同的文件系統,常見的文件系統有FAT32和NTFS。在Linux中,只有一個邏輯的根目錄,用斜線/表示,Linux支持多種不同的文件系統,如Ext2/Ext3/Ext4等。不同的文件系統有不同的文件組織方式、結構和特點,不過,一般編程時,語言和類庫為我們提供了統一的API,我們並不需要關心其細節。

在邏輯上,Windows中就是有多個根目錄,Linux就是有一個根目錄,每個根目錄下就是一顆子目錄和文件構成的樹。每個文件都有文件路徑的概念,路徑有兩種形式,一種是絕對路徑,另一種是相對路徑。

所謂絕對路徑就是從根目錄開始到當前文件的完整路徑,在Windows中,目錄之間用反斜線分隔,如"C:\code\hello.java",在Linux中,目錄之間用斜線分隔,如"/Users/laoma/Desktop/code/hello.java"。在Java中,java.io.File類定義了一個靜態變數File.separator,表示路徑分隔符,編程時應使用該變數而避免硬編碼。

所謂相對路徑是相對於當前目錄而言的,在命令行終端上,通過cd命令進入到的目錄就是當前目錄,在Java中,通過System.getProperty("user.dir")可以得到運行Java程式的當前目錄,相對路徑不以根目錄開頭,比如在Windows上,當前目錄為"D:\laoma",相對路徑為"code\hello.java",則完整路徑為"D:\laoma\code\hello.java"。

每個文件除了有具體內容,還有元數據信息,如文件名、創建時間、修改時間、文件大小等。文件還有一個是否隱藏的性質,在Linux系統中,如果文件名以.開頭,則為隱藏文件,在Windows系統中,隱藏是文件的一個屬性,可以進行設置。

大部分文件系統,每個文件和目錄還有訪問許可權的概念,對所有者、用戶組可以有不同的許可權,許可權具體包括讀、寫、執行。

文件名有大小寫是否敏感的概念,在Windows系統中,一般是大小寫不敏感的,而Linux則一般是大小寫敏感的,也就是說,同一個目錄下,"abc.txt"和"ABC.txt"在Windows中被視為同一個文件,而Linux視為不同的文件。

操作系統中有一個臨時文件的概念,臨時文件位於一個特定目錄,比如Windows 7,一般位於"C:\Users\用戶名\AppData\Local\Temp",Linux系統,位於"/tmp",操作系統會有一定的策略自動清理不用的臨時文件。臨時文件一般不是用戶手工創建的,而是應用程式產生的,用於臨時目的。

文件讀寫

文件是放在硬碟上的,程式處理文件需要將文件讀入記憶體,修改後,需要寫回硬碟。操作系統提供了對文件讀寫的基本API,不同操作系統的介面和實現是不一樣的,不過,有一些共同的概念,Java封裝了操作系統的功能,提供了統一的API。

一個基本常識是,硬碟的訪問延時,相比記憶體,是很慢的,操作系統和硬碟一般是按塊批量傳輸,而不是按位元組,以攤銷延時開銷,塊大小一般至少為512位元組,即使應用程式只需要文件的一個位元組,操作系統也會至少將一個塊讀進來。一般而言,應儘量減少接觸硬碟,接觸一次,就一次多做一些事情,對於網路請求,和其他輸入輸出設備,原則都是類似的。

另一個基本常識是,一般讀寫文件需要兩次數據拷貝,比如讀文件,需要先從硬碟拷貝到操作系統內核,再從內核拷貝到應用程式分配的記憶體中,操作系統運行所在的環境和應用程式是不一樣的,操作系統所在的環境是內核態,應用程式是用戶態,應用程式調用操作系統的功能,需要兩次環境的切換,先從用戶態切到內核態,再從內核態切到用戶態,問題是,這種用戶態/內核態的切換是有開銷的,應儘量減少這種切換。

為了提升文件操作的效率,應用程式經常使用一種常見的策略,即使用緩衝區。讀文件時,即使目前只需要少量內容,但預知還會接著讀取,就一次讀取比較多的內容,放到讀緩衝區,下次讀取時,緩衝區有,就直接從緩衝區讀,減少訪問操作系統和硬碟。寫文件時,先寫到寫緩衝區,寫緩衝區滿了之後,再一次性的調用操作系統寫到硬碟。不過,需要註意的是,在寫結束的時候,要記住將緩衝區的剩餘內容同步到硬碟。操作系統自身也會使用緩衝區,不過,應用程式更瞭解讀寫模式,恰當使用往往可以有更高的效率。

操作系統操作文件一般有打開和關閉的概念,打開文件會在操作系統內核建立一個有關該文件的記憶體結構,這個結構一般通過一個整數索引來引用,這個索引一般稱為文件描述符,這個結構是消耗記憶體的,操作系統能同時打開的文件一般也是有限的,在不用文件的時候,應該記住關閉文件,關閉文件一般會同步緩衝區內容到硬碟,並釋放占據的記憶體結構。

操作系統一般支持一種稱之為記憶體映射文件的高效的隨機讀寫大文件的方法,將文件直接映射到記憶體,操作記憶體就是操作文件,在記憶體映射文件中,只有訪問到的數據才會被實際拷貝到記憶體,且數據只會拷貝一次,被操作系統以及多個應用程式共用。後面章節會進一步介紹。

Java文件概述

在Java中(很多其他語言也類似),文件一般不是單獨處理的,而是視為輸入輸出(IO - Input/Output)設備的一種。Java使用基本統一的概念處理所有的IO,包括鍵盤、顯示終端、網路等。

這個統一的概念是流,流有輸入流輸出流,輸入流就是可以從中獲取數據,輸入流的實際提供者可以是鍵盤、文件、網路等,輸出流就是可以向其中寫入數據,輸出流的實際目的地可以是顯示終端、文件、網路等。

Java IO的基本類大多位於包java.io中,類InputStream表示輸入流,OutputStream表示輸出流,而FileInputStream表示文件輸入流,FileOutputStream表示文件輸出流。

有了流的概念,就有了很多面向流的代碼,比如對流做加密、壓縮、計算信息摘要、計算檢驗和等,這些代碼接受的參數和返回結果都是抽象的流,它們構成了一個協作體系,這類似於之前介紹的介面概念、面向介面的編程、以及容器類協作體系。一些實際上不是IO的數據源和目的地也轉換為了流,以方便參與這種協作,比如位元組數組,也包裝為了流ByteArrayInputStream和ByteArrayOutputStream。

裝飾器設計模式

基本的流按位元組讀寫,沒有緩衝區,這不方便使用,Java解決這個問題的方法是使用裝飾器設計模式,引入了很多裝飾類,對基本的流增加功能,以方便使用,一般一個類只關註一個方面,實際使用時,經常會需要多個裝飾類。

Java中有很多裝飾類,有兩個基類,過濾器輸入流FilterInputStream和過濾器輸出流FilterOutputStream,所謂過濾,就類似於自來水管道,流入的是水,流出的也是水,功能不變,或者只是增加功能,它有很多子類,這裡列舉一些:

  • 對流起緩衝裝飾的子類是BufferedInputStream和BufferedOutputStream。
  • 可以按八種基本類型和字元串對流進行讀寫的子類是DataInputStream和DataOutputStream。
  • 可以對流進行壓縮和解壓縮的子類有GZIPInputStream, ZipInputStream, GZIPOutputStream, ZipOutputStream。
  • 可以將基本類型、對象輸出為其字元串表示的子類有PrintStream。

眾多的裝飾類,使得整個類結構變的比較複雜,完成基本的操作也需要比較多的代碼,但優點是非常靈活,在解決某些問題時也很優雅。

Reader/Writer

以InputStream/OutputStream為基類的流基本都是以二進位形式處理數據的,不能夠方便的處理文本文件,沒有編碼的概念,能夠方便的按字元處理文本數據的基類是Reader和Writer,它也有很多子類:

  • 讀寫文件的子類是FileReader和FileWriter。
  • 起緩衝裝飾的子類是BufferedReader和BufferedWriter。
  • 將字元數組包裝為Reader/Writer的子類是CharArrayReader和CharArrayWriter。
  • 將字元串包裝為Reader/Writer的子類是StringReader和StringWriter。
  • 將InputStream/OutputStream轉換為Reader/Writer的子類是InputStreamReader OutputStreamWriter。
  • 將基本類型、對象輸出為其字元串表示的子類PrintWriter。

隨機讀寫文件

大部分情況下,使用流或Reader/Writer讀寫文件內容,但Java提供了一個獨立的可以隨機讀寫文件的類RandomAccessFile,適用於大小已知的記錄組成的文件,我們日常應用開發中用的會比較少,但在一些系統程式中用到的會比較多。

File

上面介紹的都是操作數據本身,而關於文件路徑、文件元數據、文件目錄、臨時文件、訪問許可權管理等,Java使用File這個類來表示。

Java NIO

以上介紹的類基本都位於包java.io下,Java還有一個關於IO操作的包java.nio,nio表示New IO,這個包下同樣包括大量的類。

NIO代表一種不同的看待IO的方式,它有緩衝區通道的概念,利用緩衝區和通道往往可以達成和流類似的目的,不過,它們更接近操作系統的概念,某些操作的性能也更高。比如,拷貝文件到網路,通道可以利用操作系統和硬體提供的DMA機制(Direct Memory Access,直接記憶體存取) ,不用CPU和應用程式參與,直接將數據從硬碟拷貝到網卡。

除了看待方式不同,NIO還支持一些比較底層的功能,如記憶體映射文件、文件加鎖、自定義文件系統、非阻塞式IO、非同步IO等。

不過,這些功能要麼是比較底層,普通應用程式用到的比較少,要麼主要適用於網路IO操作,我們大多不會介紹,只會介紹記憶體映射文件。

序列化和反序列化

簡單來說,序列化就是將記憶體中的Java對象持久保存到一個流中,反序列化就是從流中恢復Java對象到記憶體。序列化/反序列化主要有兩個用處,一個是對象狀態持久化,另一個是網路遠程調用,用於傳遞和返回對象。

Java主要通過介面Serializable和類ObjectInputStream/ObjectOutputStream提供對序列化的支持,基本的使用是比較簡單的,但也有一些複雜的地方。

不過,Java的預設序列化有一些缺點,比如,序列化後的形式比較大、浪費空間,序列化/反序列化的性能也比較低,更重要的問題是,它是Java特有的技術,不能與其他語言交互。

XML是前幾年最為流行的描述結構性數據的語言和格式,Java對象也可以序列化為XML格式,XML容易閱讀和編輯,且可以方便的與其他語言進行交互。

XML強調格式化但比較"笨重",JSON是近幾年來逐漸流行的輕量級的數據交換格式,在很多場合替代了XML,也非常容易閱讀和編輯,Java對象也可以序列化為JSON格式,且與其他語言進行交互。

XML和JSON都是文本格式,人容易閱讀,但占用的空間相對大一些,在只用於網路遠程調用的情況下,有很多流行的、跨語言的、精簡且高效的對象序列化機制,如ProtoBuf, Thrift, MessagePack等。MessagePack是二進位形式的JSON,更小更快。

章節安排

文件看起來是一件非常簡單的事情,但實際卻沒有那麼簡單,Java的設計也不是太完美,包含了大量的類,這使得對於文件的理解變得困難。

為便於理解,我們將採用以下思路在接下來的章節中進行探討。

首先,我們介紹如何處理二進位文件,或者將所有文件看做二進位,介紹如何操作,對於常見操作,我們會封裝,提供一些簡單易用的方法。

下一步,我們介紹如何處理文本文件,我們會考慮編碼、按行處理等,同樣,對於常見操作,我們會封裝,提供簡單易用的方法。

接下來,我們介紹文件本身和目錄操作File類,我們也會封裝常見操作。

我們也會介紹比較底層的對文件的操作RandomAccessFile類,以及記憶體映射文件,我們會介紹它們的使用及應用。

實際處理文件時,經常針對的是具體的文件類型,我們會介紹一些常見類型的處理,比如CSV文件、Excel文件,圖片、HTML文件、壓縮文件等。

最後,對於序列化,除了介紹Java的預設序列化機制,我們還會介紹XML, JSON以及MessagePack。

小結

本節介紹了關於文件的一些基本概念和常識,Java中處理文件的基本思路和類結構,最後我們總結了接下來的章節安排思路。

文件看上去應該很簡單,但實際卻包含很多內容,讓我們耐住性子,下一節,先從二進位開始吧。

----------------

未完待續,查看最新文章,敬請關註微信公眾號“老馬說編程”(掃描下方二維碼),從入門到高級,深入淺出,老馬和你一起探索Java編程及電腦技術的本質。用心原創,保留所有版權。


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

-Advertisement-
Play Games
更多相關文章
  • 很多人認為關閉應用程式應該很簡單,例如WindowsForm里一個Application.Exit();方法就可以解決問題,但在WPF裡面可別濫用,因為WPF里Application類沒有該方法,倒是有一個Exit的事件驅動,在WPF應用程式裡面關閉程式講究很多: 在WPF應用程式的關閉是有Shut ...
  • using (Image image = Image.FromFile(arrFiles[0])) { MemoryStream stream = new MemoryStream(); image.Save(stream, System.Drawing.Imaging.ImageFormat.Pn ...
  • 親身經歷記錄下來,以備後用。也希望能夠幫助到有需要的朋友們! 1、安裝之前首先下載VS2015,下載地址: 【VS2015社區版官方中文版下載】:http://download.microsoft.com/download/B/4/8/B4870509-05CB-447C-878F-2F80E4CB ...
  • 什麼是VBA?它有什麼作用? A.實現Excel中沒有實現的功能。 B.提高運行速度。 C.編寫自定義函數。 D.實現自動化功能。 E.通過插入窗體做小型管理軟體。 VBA在哪裡存放的?怎麼運行? A.模塊中 在Excel 2010中若沒有“開發工具”項,通過“文件”——“選項”——“自定義功能區” ...
  • 安裝IIS後訪問localhost頁面, 提示The remote procedure call failed and did not execute,再刷新變為:-2147467259 (0x80004005), 再刷新就變回去了,就在那兩句錯誤中不斷的重覆。 然後,百度。 解決方法: 在運行中運 ...
  • 這次來看一看this關鍵字的第二個用法:將對象作為參數傳遞到其他方法 41行代碼 Convert .ConvertedScore(this) 裡面的this也便就是“折算後的分數” 說實話,我對this關鍵字的這個用法理解的並不是太透徹,用的時候也是雲里霧裡的,所以希望網友們能夠積極的給我評論,給予 ...
  • 今日問題: 請問主程式輸出結果是什麼?(點擊以下“【Java每日一題】20161205”查看20161202問題解析) 題目原發佈於公眾號、簡書:【Java每日一題】20161205,【Java每日一題】20161205 ...
  • 圖片轉字元格式 引言 前幾天看到一幅用字元(準確的說是ascii碼)繪製的doge圖像,覺得挺有意思 將他放到代碼的註釋部分,加上了 的字樣,放在了代碼的註釋部分(新建腳本的時候自動添加),同事看到了覺得挺有意思,問我怎麼搞得。 後來我仔細看了下這幅圖,分析了下,都是用ascii字元繪製的,通過as ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...