本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》(馬俊昌著),由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買:京東自營連接 html
咱們在平常電腦操做中,接觸和處理最多的,除了上網,大概就是各類各樣的文件了,從本節開始,咱們就來探討文件處理,本節主要介紹文件有關的一些基本概念和常識,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文件類型就定義了這些內容和二進制表示之間的映射關係。有的文件類型的格式是公開的,有的多是私有的,咱們也能夠定義本身私有的文件格式。bash
對於一種文件類型,每每有一種或多種應用程序能夠解讀它,進行查看和編輯,一個應用程序每每能夠解讀一種或多種文件類型。微信
在操做系統中,一種後綴名每每關聯一個應用程序,好比.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中(不少其餘語言也相似),文件通常不是單獨處理的,而是視爲輸入輸出(IO - Input/Output)設備的一種。Java使用基本統一的概念處理全部的IO,包括鍵盤、顯示終端、網絡等。
這個統一的概念是流,流有輸入流和輸出流,輸入流就是能夠從中獲取數據,輸入流的實際提供者能夠是鍵盤、文件、網絡等,輸出流就是能夠向其中寫入數據,輸出流的實際目的地能夠是顯示終端、文件、網絡等。
Java IO的基本類大多位於包java.io中,類InputStream表示輸入流,OutputStream表示輸出流,而FileInputStream表示文件輸入流,FileOutputStream表示文件輸出流。
有了流的概念,就有了不少面向流的代碼,好比對流作加密、壓縮、計算信息摘要、計算檢驗和等,這些代碼接受的參數和返回結果都是抽象的流,它們構成了一個協做體系,這相似於以前介紹的接口概念、面向接口的編程、以及容器類協做體系。一些實際上不是IO的數據源和目的地也轉換爲了流,以方便參與這種協做,好比字節數組,也包裝爲了流ByteArrayInputStream和ByteArrayOutputStream。
基本的流按字節讀寫,沒有緩衝區,這不方便使用,Java解決這個問題的方法是使用裝飾器設計模式,引入了不少裝飾類,對基本的流增長功能,以方便使用,通常一個類只關注一個方面,實際使用時,常常會須要多個裝飾類。
Java中有不少裝飾類,有兩個基類,過濾器輸入流FilterInputStream和過濾器輸出流FilterOutputStream,所謂過濾,就相似於自來水管道,流入的是水,流出的也是水,功能不變,或者只是增長功能,它有不少子類,這裏列舉一些:
衆多的裝飾類,使得整個類結構變的比較複雜,完成基本的操做也須要比較多的代碼,但優勢是很是靈活,在解決某些問題時也很優雅。
以InputStream/OutputStream爲基類的流基本都是以二進制形式處理數據的,不可以方便的處理文本文件,沒有編碼的概念,可以方便的按字符處理文本數據的基類是Reader和Writer,它也有不少子類:
大部分狀況下,使用流或Reader/Writer讀寫文件內容,但Java提供了一個獨立的能夠隨機讀寫文件的類RandomAccessFile,適用於大小已知的記錄組成的文件,咱們平常應用開發中用的會比較少,但在一些系統程序中用到的會比較多。
上面介紹的都是操做數據自己,而關於文件路徑、文件元數據、文件目錄、臨時文件、訪問權限管理等,Java使用File這個類來表示。
以上介紹的類基本都位於包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編程及計算機技術的本質。用心原創,保留全部版權。