Java.io package
通過數(shù)據(jù)流,序列化和文件系統(tǒng)提供系統(tǒng)輸入和輸出。
File
概述
一種文件或目錄的路徑名抽象表示。
用戶界面和操作系統(tǒng)使用以來系統(tǒng)的路徑字符串來命名文件和目錄。
File類表達(dá)一種與系統(tǒng)無關(guān)的分層路徑名視圖抽象,這種抽象主要作用是以不依賴操作系統(tǒng)的方式處理很多文件和路徑名依賴操作系統(tǒng)的復(fù)雜問題。這種抽象路徑由兩部分組件:
- 一個可選的依賴系統(tǒng)的首字符串,例如磁盤驅(qū)動說明符,"/"Unix根目錄,"\\"表示W(wǎng)indows UNC路徑名;
- 一個空的或者多個"name"的序列。
在抽象路徑中
name可以表示以下幾種含義:第一個name可能是目錄名,或者是hostname(Windows UNC的一部分);后續(xù)的每一個name(除了最后一個)都表示目錄名;最后一個name表示目錄名或者文件名。
路徑分類
無論是字符串形式,還是File的抽象表示,路徑總是分為絕對路徑和相對路徑。
絕對路徑名是完整的,包含文件名和它的完整路徑以及磁盤驅(qū)動說明符,是依賴操作系統(tǒng)的。不需要其他信息來定位它所表示的文件。
而相對路徑必須根據(jù)從其他路徑獲取的信息來解釋。默認(rèn)情況java.io包下的類都是一句當(dāng)前用戶目錄來解決相對路徑。這個目錄時根據(jù)系統(tǒng)屬性user.dir來命名,而且通常是JVM被調(diào)用的目錄(執(zhí)行java className命令的目錄就是當(dāng)前目錄)。
使用絕對路徑不利于代碼平臺移植,所以盡量使用相對路徑。相對路徑目錄分隔符是斜杠(/)。
路徑中的首字符串
這個prefix概念是依賴系統(tǒng)的,例如表示Unix根目錄,Windows的磁盤驅(qū)動器說明符和根目錄...
在Unix系統(tǒng)中,"/"表示跟目錄;在Windows中,磁盤字母+:形式,如果是絕對路徑,后面還會跟"\"。
路徑字符串中的符號
分隔符
依賴系統(tǒng)的路徑字符串和File路徑抽象表示之間的轉(zhuǎn)換都涉及到分隔符。其作用是將路徑中各name分隔,形成分層。不同操作系統(tǒng)的分隔符都是不一樣的。
依賴系統(tǒng)特定的分隔符可以通過給System.getProperty()傳入關(guān)鍵詞file.separator獲取(更多關(guān)鍵詞可以去查看文檔)。
除了目錄分隔符還有路徑分隔符和行分隔符,它們都是依賴系統(tǒng)的(關(guān)鍵詞分別是:path.separator和line.separator)。
其他符號
| 符號 | 描述 |
|---|---|
| "." | 表示當(dāng)前目錄 |
| ".." | 表示上一層目錄 |
| "../../" | 表示上一層目錄的上一層目錄 |
| "/" | 表示根目錄 |
| "~/" | 表示用戶目錄的根目錄(表示當(dāng)前虛擬目錄下) |
注意表格中使用的分隔符(/)可以替換成任一系統(tǒng)特定的分隔符。
FileSystem Hierarchy
FileSystem Hierarchy的主要作用是規(guī)定了操作系統(tǒng)各層次目錄的作用。
好處在于軟件可以預(yù)測已安裝文件和文件夾的位置;用戶可以預(yù)測已安裝文件和文件夾的位置。這就好比MacOS目錄有/Library,/Application,/Users...一個Android項目也有相應(yīng)的路徑分別存放Java代碼和資源文件。
詳細(xì)規(guī)定可以看文件系統(tǒng)層次結(jié)構(gòu)。
empty abstract name
文檔中介紹File的一種抽象形式empty abstract name,它不包含任何prefix和name sequence。那么表示哪個文件或目錄呢?
public class TestPathCharacter {
public static void main(String[] args) {
File file = new File("");
if(!file.exists()) {
System.out.println("no such file or directory");
//打印了,說明無法定位到指定文件或目錄
}
//猜想表示當(dāng)前用戶目錄
//File f = new File(file, "/TestPathString.java"); //執(zhí)行后打印異常,無法找到相應(yīng)文件
File f = new File("./", "TestPathString.java");//執(zhí)行后正常打印,證明不需要使用絕對路徑
if(!f.exists()) {
System.out.println("no such file or directory");
}
Scanner input = null;
try {
input = new Scanner(f);
String line = null;
while(input.hasNextLine()) {
line = input.nextLine();
System.out.println(line);
}
}catch(IOException e) {
e.printStackTrace();
}finally{
input.close();
}
}
}
執(zhí)行結(jié)果可以看注釋,證明不表示任何文件或目錄。
Path文檔中empty path定位到的是file system默認(rèn)的目錄。
與java.nio.file互通性
java.nio.file包定義了讓JVM訪問文件,文件屬性和文件系統(tǒng)的接口和類。這些API克服了File類的限制。調(diào)用一個File對象的toPath()可以得到一個Path去定位相應(yīng)文件。返回得到的Path配合Files使用,提供了更高效和更廣泛的額外文件操作,文件屬性和有利于診斷文件操作時錯誤的I/O異常的訪問。
Path是一個可以用于在file system中定位文件得到類。它一般代表依賴系統(tǒng)的文件路徑。一個Path對象表示一個分層的,由被系統(tǒng)特定的分隔符分隔的目錄和文件名元素的序列組成。也有可能會包含一個標(biāo)示著系統(tǒng)層次的根組件。
FileSystem是一個提供file system的接口,同時是對象訪問file system中的文件和其他對象的工廠。通過FileSystems.getFileSystem()文檔介紹,自己對file system初步理解:
This method iterates over the installed providers to locate the provider that is identified by the URI scheme of the given URI.
給一個對象提供外部訪問接口,也就是提供程序??梢员茸鰽ndroid系統(tǒng)中為了安全考慮程序之間數(shù)據(jù)共享使用ContentProvider,根據(jù)特定的uri去定位文件。而這個ContentProvider就是該程序的file system。
java.nio.file包中還提供了幾個名稱類似的類,它們的對應(yīng)關(guān)系:
| 類名 | 描述 |
|---|---|
| Path | 一個可以用于在file system中定位文件得到類。它一般代表依賴系統(tǒng)的文件路徑。 |
| Paths | 只包含通過轉(zhuǎn)換路徑字符串或URI返回Path的靜態(tài)方法 |
| FileSystem | 一個提供file system的接口 |
| FileSystems | 是file system的工廠方法,用于獲取或者構(gòu)建file system |
| Files | 只包含對文件,目錄或其他類型文件操作的靜態(tài)方法 |
FileSystems.getDefault()返回的file system是指JVM可用的。而工作目錄就是當(dāng)前用戶目錄,被系統(tǒng)屬性命名(System.getProperty(user.dir)),這就允許了和java.io.File的互通性。
API理解
大部分File的方法直接看文檔就可以,一下幾個自己理解有誤,實驗了一下。
- mkdir()&mkdirs(),兩者都是創(chuàng)建目錄,注意不會創(chuàng)建文件。但是使用mkdir()時,F(xiàn)ile對象指示的路徑中有不存在的目錄,導(dǎo)致創(chuàng)建失敗。此時可以用mkdirs()。
- getParent(),返回File對象的父目錄字符串。
- list()&listFiles()&listRoots(),list()和listFiles()都是返回當(dāng)前目錄下所有文件和目錄,前者返回字符串?dāng)?shù)組,后者返回File數(shù)組;listRoots()返回系統(tǒng)可用的系統(tǒng)盤。
- getPath()&getAbsolutePath()&getCanonicalPath(),getPath()返回構(gòu)建File對象時路徑的字符串形式(完全不改動);getAbsolutePath()返回File對象指示的絕對路徑;getCanonicalPath()返回File對象的絕對路徑,但是不包括路徑中的符號(除了分隔符)。
FilenameFilter,用于過濾文件。
RandomAccessFile
文檔概述
RandomAccessFile實例支持隨機(jī)訪問文件。隨機(jī)訪問文件的行為類似存儲在file system中大字節(jié)數(shù)組。該隱含的數(shù)組中有一種光標(biāo)或索引,稱為文件指針。讀取操作是在指針處讀取開始讀取字節(jié),并且推進(jìn)指針至下一個字節(jié)。如果使用"rw"模式構(gòu)建的對象,寫操作一樣可用。寫入操作過程和讀取類似。一旦寫入數(shù)據(jù)超過隱含的數(shù)組長度后,數(shù)組被擴(kuò)展。文件指針可以被getFilePointer()獲取當(dāng)前位置,通過seek()修改位置。
作用
java.io包下的其它流都是只讀或只寫。而且它們的外部文件都是順序的,意思是讀操作時從文首到文末、寫操作時覆蓋或者從文末追加,無法更新文件(可以刪除重新創(chuàng)建)。而RandomAccessFile可以做到。
二進(jìn)制IO中的字符與字符串
DataOutput
該接口規(guī)定了從任何Java基本類型數(shù)據(jù)到一系列字節(jié)的轉(zhuǎn)換,然后將這些字節(jié)寫入二進(jìn)制流中。同時也提供字符串對象轉(zhuǎn)換成改進(jìn)版UTF-8格式,并且將這些字節(jié)寫出。
DataInput定義了讀取基本數(shù)據(jù)類型和字符串的方法。
- writeChar(int),將UTF-16字節(jié)寫入輸出流中;
- writeChars(String),將字符串中所有字符的UTF-16字節(jié)寫入輸出流中;
- writeBytes(String),將字符串中所有字符的UTF-16低字節(jié)寫入輸出流,高字節(jié)拋棄。該方法適用于純ASCII碼字符組成的字符串。
- writeUTF(String),將字符串中字符轉(zhuǎn)換成改進(jìn)版UTF-8格式,然后前兩個字節(jié)存儲轉(zhuǎn)換后的字節(jié)數(shù),后面接上轉(zhuǎn)換后的字節(jié)系列。
改進(jìn)版UTF-8編碼規(guī)則
改進(jìn)版的UTF-8方案采用一個字節(jié),二個字節(jié)或三個字節(jié)來存儲字符。
- 字符Unicode編碼小于或等于0x7F,該字符編碼大小為一個字節(jié),且首位為0標(biāo)示為一個字節(jié)存儲;
- 字符Unicode編碼大于0x7F且小于或等于0x7FF,該字符編碼大小為二個字節(jié),且前三位是110標(biāo)示為兩個字節(jié)中第一位;
- 字符unicode編碼大于0x7FF,該字符編碼大小為三個字節(jié),且前四位是1110標(biāo)示為三個字節(jié)中第一個。
實戰(zhàn)
public class TestDataOutput {
public static void main(String[] args) {
String s = "我是\uD835\uDD46";
String result = null;
System.out.println(s);
System.out.println("字符串字符個數(shù): " + s.length());
System.out.println("字符串code point個數(shù): " + s.codePointCount(0, s.length()));
RandomAccessFile file = null;
byte[] b = null;
try {
try {
file = new RandomAccessFile("../file/TestUTF8Stream.txt", "rw");
file.setLength(0);
file.writeUTF(s);
int count = (int)(file.length());
System.out.println("隨機(jī)訪問文件字節(jié)數(shù)有:" + count + "個.");
b = new byte[count];
file.seek(0);
file.read(b, 0, count);
for(byte i : b) {
System.out.println(i & 0xff);
}
file.seek(0);
result = file.readUTF();
System.out.println(result);
}finally {
file.close();
}
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
}
}
Console打印輸出:

上圖中打印了字符串,字符串代碼單元數(shù)量以及代碼點數(shù)量,寫入文件后轉(zhuǎn)換的字節(jié)系列個數(shù),分別打印該系列,最后使用readUTF()讀取寫入文件的字符串。
這里遇到三個誤區(qū):
第一,當(dāng)寫完數(shù)據(jù)后,直接從文件中獲取所有字節(jié)系列,是無法獲取到的。這是因為此時文本指針在文本末尾。同理,使用readUTF()前也需要調(diào)用seek(0)。
第二,當(dāng)文件關(guān)閉后對文件重新寫入數(shù)據(jù)(程序重啟),會覆蓋文本內(nèi)的內(nèi)容。這里需要注意了如果第二次輸入的數(shù)據(jù)字節(jié)長度小于第一次,只會覆蓋第二次輸入數(shù)量的字節(jié),剩下的會保留。為了避免誤操作,可以在每一次寫入時調(diào)用setLength(0)清空。
第三,使用RandomAccessFile構(gòu)造函數(shù)實例化對象,如果給定的是文件路徑字符串或者File,沒有該文件自動創(chuàng)建。但是給定的是包含未創(chuàng)建的目錄或者目錄路徑字符串或者File,文件/目錄不會自動創(chuàng)建,并且拋出FileNotFounException異常。
RandomAccessFile中讀寫操作調(diào)用需要對應(yīng)起來。也就是在文本寫入時,哪里調(diào)用了
writeUTF()就在讀取過程中的相應(yīng)順序調(diào)用readUTF()。
只支持文件,且沒有只寫模式
RandomAccessFile類只能夠為文件創(chuàng)建訪問流,目錄等其他類型對象不可以。
RandomAccessFile構(gòu)造器有兩個:
- RandomAccessFile(File file, String mode)
- RandomAccessFile(String name, String mode)
都會拋出FileNotFoundException異常,原因有以下幾種:
- 如果模式是"r",給定的文件不是固定存在,會拋出。
- 如果模式是"rw"開頭,給定的文件不是一個存在且可寫的固定文件,若不存在又無法創(chuàng)建或者創(chuàng)建,打開文件時出現(xiàn)錯誤,會拋出。
File類可以抽象的表示文件或者目錄路徑,操作時無需處理系統(tǒng)依賴問題。FileSystem是file system接口,且提供了對象訪問file system中文件或其他對象方法(不僅僅只有文件和目錄)。而Path則是FileSystem中定位文件或其他對象的路徑,與系統(tǒng)相關(guān)。
模式
RandomAccessFile模式常用的有兩種:
- "r",只讀模式。如果使用該模式實例去調(diào)用任何write重載方法會拋出IOException異常。
- "rw",讀寫模式。如果文件不存在,會去嘗試創(chuàng)建一個新文件。
還有兩種模式,看文檔不是很理解:
- "rws"
- "rwd"
同樣是支持讀寫操作,還有別的作用,不太理解。
注意不支持只寫模式。
方法
RandomAccessFile的方法主要是向文本記錄數(shù)據(jù)。每一條數(shù)據(jù)大小不必相等,但是它們大小和位置必須可知。這樣做的目的,一方面為了后期讀取時,必須按照寫入時順序調(diào)用相應(yīng)方法;另一方面可以通過seek()方法準(zhǔn)確定位到想要的數(shù)據(jù)。所以,她只能操作文件。
RandomAccessFile雖然是二進(jìn)制流,但是和InputStream和OutputStream沒有繼承關(guān)系。它直接繼承Object,實現(xiàn)DataOutput和DataInput接口的方法。除了這兩個接口的讀寫方法,還實現(xiàn)了特有方法。
- getFilePointer(),返回從文本頭部到指針處(下一個可讀字節(jié))字節(jié)偏移量。
- seek(),設(shè)置文件指針的偏移量,到下一個可讀或?qū)懙淖止?jié)。偏移量從文首開始計算。偏移量可以超出文末,但是不會改變文本長度屬性。
- length(),返回文本長度,即字節(jié)數(shù)。
- setLength(),設(shè)置文本長度,一般傳入0可以用于清空文本。
效率
RandomAccessFile的讀寫方法都有native關(guān)鍵詞。說明每調(diào)用一次讀或?qū)懛椒?,都需要與磁盤進(jìn)行一次I/O操作。如果讀取大文件,效率非常低。
在文檔中介紹RandomAccessFile行為好比內(nèi)存中有一個巨大數(shù)組存儲文件字節(jié),其實并沒有。
在java.io包中解決辦法是給流添加一個緩沖區(qū),減少與磁盤I/O操作次數(shù),從而提高效率。例如BufferedInputStream。
但是這樣沒有了RandomAccessFile的特性-隨機(jī)訪問文件??梢允褂胘dk 1.4 nio中的內(nèi)存映射替換RandomAccessFile,或者擴(kuò)展RandomAccessFile實現(xiàn)帶有Buffer的RandomAccessFile。
內(nèi)存映射文件概念:由于文件過大,無法直接放入內(nèi)存中進(jìn)行I/O操作,所以一般都是通過緩沖區(qū)來提高效率。而有了內(nèi)存映射概念,可以認(rèn)為通過一次與磁盤的I/O操作,把文件放入內(nèi)存,存儲在一個數(shù)組中進(jìn)行訪問,大大提高了效率。
問題
boolean類型數(shù)據(jù)占幾個字節(jié),值是多少?
boolean類型數(shù)據(jù)占一個字節(jié),true的字節(jié)值為1,false的字節(jié)值為0。