Java的I/O是一個(gè)龐大的文件操作系統(tǒng),初學(xué)者往往對(duì)I/O的使用比較迷茫,優(yōu)點(diǎn)丈二和尚摸不著頭腦的感覺(jué)。即便是使用java I/O處理了自己的實(shí)際需求仍然不知其所以然。當(dāng)然我也是這樣,所以幾天以前我決定好好地看看java的I/O系統(tǒng),到現(xiàn)在感覺(jué)還行,當(dāng)然用好不敢自夸,但是對(duì)于I/O的那個(gè)套路差不多已經(jīng)走通了,并不像以前那樣云里霧里不知所云了。我學(xué)習(xí)的資料是《java編程思想》,這個(gè)總結(jié)并沒(méi)有多少我自己的東西,因?yàn)槲业乃接邢蓿故窍胱约涸煲粋€(gè)輪子但是畢竟能力有限嗎。
好了廢話不多說(shuō)了,我下面說(shuō)一下我的學(xué)習(xí)思路,只是一個(gè)思路,當(dāng)然我已經(jīng)按照這個(gè)路子能比較清楚的使用java的I/O了,所以當(dāng)大家發(fā)現(xiàn)這都是我摘抄的編程思想的內(nèi)容是大家不要詫異,歡迎拍磚...
先來(lái)說(shuō)一下File類(lèi)。File類(lèi)一個(gè)容易讓我們"顧名思義"的類(lèi)。通常我們看到這個(gè)類(lèi),就會(huì)想到這個(gè)類(lèi)可能指代的是一個(gè)文件。但是實(shí)際上不是這樣子的,他指的是一組文件名或單個(gè)文件名。Java API上面說(shuō):"文件和目錄路徑名的抽象表示形式"。我們先來(lái)看一段簡(jiǎn)單的代碼就可以一目了然了,心中的疑惑就煙消云散了:
package review; import java.io.File; public class TestFile { public static void main(String[] args) { File file = new File("D://test.txt"); System.out.println(file); System.out.println(file.isFile()); } } Result: D:\test.txt true
讓我們看一下輸出地結(jié)果,當(dāng)我們打印file對(duì)象的時(shí)候輸出了一個(gè)window下的文件路徑,第二個(gè)打印結(jié)果是true。當(dāng)然我提前已經(jīng)在D盤(pán)創(chuàng)建了這個(gè)test.txt文件,第二個(gè)輸出地意思是這個(gè)test.txt對(duì)象是不是在D盤(pán)下面。我們稍微看一下File類(lèi)的源碼也可以看到設(shè)計(jì)者設(shè)計(jì)File類(lèi)的意圖:
public class File implements Serializable, Comparable<File> { ... private String path; ... /** * Creates a new <code>File</code> instance by converting the given * pathname string into an abstract pathname. If the given string is * the empty string, then the result is the empty abstract pathname. * * @param pathname A pathname string * @throws NullPointerException * If the <code>pathname</code> argument is <code>null</code> */ public File(String pathname) { if (pathname == null) { throw new NullPointerException(); } this.path = fs.normalize(pathname); this.prefixLength = fs.prefixLength(this.path); } /** * Converts this abstract pathname into a pathname string. The resulting * string uses the {@link #separator default name-separator character} to * separate the names in the name sequence. * * @return The string form of this abstract pathname */ public String getPath() { return path; } /** * Returns the pathname string of this abstract pathname. This is just the * string returned by the <code>{@link #getPath}</code> method. * * @return The string form of this abstract pathname */ public String toString() { return getPath(); }
看了這樣的一部分源碼我們對(duì)File類(lèi)的大概用途差不多已經(jīng)知道了。首先聲明了一個(gè)String類(lèi)型的path對(duì)象,在File的構(gòu)造方法中有對(duì)path的賦值,toString方法也是返回了getPath()取得path值,FIle類(lèi)一直在和String類(lèi)型的path對(duì)象打交道,所以,這個(gè)File類(lèi)的功能我們也大概知道了。至于具體的File如何使用,如何創(chuàng)建一個(gè)目錄,如何打印一個(gè)目錄就沒(méi)必要舉例了吧!自己對(duì)照著API實(shí)驗(yàn)吧。不過(guò)我們要注意一下FilenameFiter這個(gè)類(lèi),他的用法下面舉了一個(gè)例子:
package org.wk.io; import java.io.File; import java.io.FilenameFilter; import java.util.Arrays; import java.util.regex.Pattern; /** * 作用:初探File類(lèi),獲取文件名稱的小工具類(lèi),并檢查某個(gè)文件名稱是否在此文件集合中 */ public class DirectoryList { public static void main(String[] args) { File path = new File("."); String list[] = null; if (args.length == 0) { list = path.list(); } else list = path.list(new DirFilter(args[0])); Arrays.sort(list, String.CASE_INSENSITIVE_ORDER); for(String str : list) { System.out.println("File Name: " + str); } } } class DirFilter implements FilenameFilter { private Pattern pattern; public DirFilter(String regex) { this.pattern = Pattern.compile(regex); } @Override public boolean accept(File dir, String name) { return pattern.matcher(name).matches(); } }
下面是比較核心的部分,也是為什么我們?cè)谑褂?span style="font-family: Courier New;">java I/O的時(shí)候比較迷惑的地方---java的輸入輸出流。
關(guān)于流的概念:它代表任何有能力產(chǎn)出數(shù)據(jù)的數(shù)據(jù)源對(duì)象或者有能力接受數(shù)據(jù)的接收端對(duì)象,"流"屏蔽了實(shí)際I/O設(shè)備中處理數(shù)據(jù)的細(xì)節(jié)。實(shí)際上是為用戶提供了接口,用戶只需關(guān)心使用"流"就行了,不必在操心數(shù)據(jù)的處理。
Java的I/O流分成輸入和輸出兩大部分,但是輸入和輸出又有基于字符和字節(jié)之分,那我們就具體展開(kāi)來(lái)看一下其中的細(xì)節(jié)。
基于字符的輸入和輸出:
輸入流:任何由InputStream派生的類(lèi),他們均包含read()方法。
輸出流:任何有OutputStream派生的類(lèi),他們均包含write()方法。
FileInputStream類(lèi)型
類(lèi) |
功能 |
構(gòu)造器 |
如何使用 |
||
ByteArrayInputStream |
允許將內(nèi)存的緩沖區(qū)當(dāng)做InputStream使用。關(guān)閉 ByteArrayInputStream 無(wú)效,此類(lèi)中的方法在關(guān)閉此流后仍可被調(diào)用,而不會(huì)產(chǎn)生任何 IOException。 |
緩沖區(qū),字節(jié)將從中取出作為數(shù)據(jù)源;配合FilterInputStream對(duì)象使用提供有用的接口。 |
StringBufferInputStream |
將String對(duì)象轉(zhuǎn)換成InputStream |
字符串,底層實(shí)現(xiàn)實(shí)際使用StringBuffer作為數(shù)據(jù)源;配合FilterInputStream對(duì)象使用提供有用的接口。 |
FileInputStream |
用于從文件中讀取信息 |
字符串,表示文件名,或FileDescriptor對(duì)象作為一種數(shù)據(jù)源;配合FilterInputStream對(duì)象使用提供有用的接口。 |
PipedInputStream |
產(chǎn)生用于寫(xiě)入相關(guān)PipedOutputStream的數(shù)據(jù)。實(shí)現(xiàn)了"管道化"的概念。 |
PipedOutputStream作為多線程中數(shù)據(jù)源;配合FilterInputStream對(duì)象使用提供有用的接口。 |
SequenceInputStream |
將兩個(gè)或多個(gè)InputStream對(duì)象轉(zhuǎn)換成為一個(gè)InputStream。 |
兩個(gè)InputStream或者一個(gè)容納InputStream的Enumeration作為一種數(shù)據(jù)源;配合FilterInputStream對(duì)象使用提供有用的接口。 |
FilterInputStream |
抽象類(lèi),作為裝飾器的接口。其中為其他的InputStream提供有用的接口。 |
詳細(xì)建見(jiàn)表"FileterInputStream類(lèi)型"。 |
OutputStream類(lèi)型
類(lèi) |
功能 |
構(gòu)造器 |
如何使用 |
||
ByteArrayOutputStream |
在內(nèi)存中創(chuàng)建緩沖區(qū),所有送往"流"的數(shù)據(jù)都要放置在此緩沖區(qū)中。 |
緩沖區(qū)初始化大小(可選),用于指定數(shù)據(jù)的目的地;配合FilterOutputStream對(duì)象使用提供有用的接口。 |
FileOutputStream |
用于將信息寫(xiě)至文件 |
字符串,表示文件名,文件或FileDescriptor對(duì)象指定數(shù)據(jù)的目的地;配合FilterOutputStream對(duì)象使用提供有用的接口。 |
PipedOutputStream |
任何寫(xiě)入其中的信息都會(huì)自動(dòng)作為相關(guān)PipedInputStream的輸出。實(shí)現(xiàn)"管道化"概念。 |
PipedOutputStream指定用于多線程的數(shù)據(jù)目的地;配合FilterOutputStream對(duì)象使用提供有用的接口。 |
FilterOutputStream |
抽象類(lèi),作為裝飾器的接口。其中為其他的OutputStream提供有用的接口。 |
見(jiàn)表"FilterOutputStream類(lèi)型"。 |
前面的表中有好多的地方在賣(mài)關(guān)子,什么裝飾器類(lèi)的接口,什么配合FilterInputStream提供有用的接口。裝飾器類(lèi)式設(shè)計(jì)模式中比較重要和常用的一種設(shè)計(jì)模式,可以上網(wǎng)上搜一下了解一下裝飾器模式。裝飾器模式的特點(diǎn)之一是比較靈活這也導(dǎo)致了我們?cè)谑褂?/span>java I/O的時(shí)候比較困惑。BufferedInputStream buffer = new BufferedInputStream(...);里面的內(nèi)容我們到底如何決定呢,正是裝飾器模式給我們帶來(lái)了這種麻煩。那我們看看這兩個(gè)裝飾器類(lèi)的子類(lèi)到底有什么也方便我們的選擇。
FileterInputStream類(lèi)型
類(lèi) |
功能 |
構(gòu)造器參數(shù) |
如何使用 |
||
DataInputStream |
與DataOutputStream搭配之用,因此我們可以按照可移植的方式讀取基本類(lèi)型。 |
InputStream 包含用于讀取基本數(shù)據(jù)類(lèi)型的全部接口。 |
BufferInputStream |
使用它可以防止每次讀取都進(jìn)行實(shí)際寫(xiě)操作,代表使用緩沖區(qū)。 |
InputStream可以指定緩沖區(qū)大小 本質(zhì)上不提供接口只不過(guò)是向進(jìn)程中添加緩沖區(qū)是必需的。與接口對(duì)象搭配。 |
LineNumInputStream |
跟蹤輸入流中的行號(hào);可調(diào)用getLineNum(),setLineNum()。 |
InputStream 僅增加了行號(hào),與接口對(duì)象搭配之用。 |
PushBackInputStream |
具有"能彈出一個(gè)子節(jié)的緩沖區(qū)"的功能,因此可以將讀到的最后一個(gè)字符回退。 |
inputStream 通常最為編譯器的掃描器,之所以包含在內(nèi)是因?yàn)?span style="font-family: Courier New;">java編譯器的需要,我們可能不會(huì)用到。 |
所以,如果我們通常這樣寫(xiě)程序
BufferedInputStream b = new BufferedInputStream(new DataInputStream(new FileInputStream("xxx")));
如果對(duì)裝飾器模式稍微有點(diǎn)了解的話,就會(huì)很容易理解這段程序。首先new FileInputStream("xxx") 作為DataInputStream 的構(gòu)造器參數(shù)new DataInputStream(new FileInputStream("xxx")) 又作為BufferedInputStream 的構(gòu)造參數(shù),非常明顯的裝飾器模式。
我們?cè)賮?lái)看一下FilterOutputStream的實(shí)現(xiàn)類(lèi)
FilterOutputStream類(lèi)型
類(lèi) |
功能 |
構(gòu)造器參數(shù) |
如何使用 |
||
DataOutputStream |
與DataIntputStream搭配之用,因此我們可以按照可移植的方式讀取基本類(lèi)型。 |
OutputStream 包含用于讀取基本數(shù)據(jù)類(lèi)型的全部接口。 |
PrintStream |
用于產(chǎn)生格式化輸出。其中DataOutputStream處理存儲(chǔ),PrintStream處理顯示。 |
OutputStream,可以用boolean值表示在每次換行的時(shí)候是否清空緩沖區(qū) |
BufferedOutputStream |
使用它可以防止每次讀取都進(jìn)行實(shí)際寫(xiě)操作,代表使用緩沖區(qū)。代表使用緩沖區(qū),可以用flush()清空緩沖區(qū) |
OutputStream,指定緩沖區(qū)大小 本質(zhì)上不是接口,只不過(guò)是向進(jìn)程中添加緩沖區(qū)所必需的。與接口搭配之用。 |
所以,類(lèi)比輸入流的程序我們可以這樣寫(xiě):
DataOutputStream d = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("...")));
道理與輸入流一樣,所以兩者掌握一個(gè)另一個(gè)也就可以掌握了,但是并不是所有的輸入和輸出流的API都是有對(duì)應(yīng)的,靈活掌握才是硬道理。
基于字節(jié)流的輸入輸出:
Reader和Writer兩個(gè)類(lèi)的操作是基于字符,提供兼容Unicode的功能,同時(shí)也起到國(guó)際化的作用。但是這兩個(gè)類(lèi)又不是完全沒(méi)有關(guān)聯(lián)的,我們可以通過(guò)適配器模式將InputStream轉(zhuǎn)換成為Reader,這個(gè)適配器類(lèi)就是InputStreamReader;同樣我們可以通過(guò)適配器類(lèi)OutputStreamWriter將OutputStream轉(zhuǎn)換成為Writer。
這樣我們?cè)诓僮魑募臅r(shí)候就擁有了兩個(gè)利器,一個(gè)是面向字節(jié)的InputStream,OutputStream;和面向字符的Reader,Writer。那么究竟什么情況下使用Reader,Writer,什么情況下使用InputStream,OutputStream呢?由于Reader和Writer是后來(lái)添加上的類(lèi),所以它的操作效率比InputStream和OutputStream高,于是在編程思想中給出了這樣的結(jié)論:盡量常識(shí)使用Reader和Writer,一旦程序無(wú)法成功的編譯,那是在選擇InputStream和OutputStream。
下面我們看一下這兩個(gè)類(lèi)層次結(jié)構(gòu)的關(guān)系:
面向字節(jié) |
面向字符 |
適配器 |
InputStream |
Reader |
InputStreamReader |
OutputStream |
Writer |
OutputStreamWriter |
FileInputStream |
FileReader |
|
FileOutputStream |
FileWriter |
|
StringBufferInputStream |
StringReader |
|
無(wú) |
StringWriter |
|
ByteArrayInputStream |
CharArrayReader |
|
ByteArrayOutputStream |
CharArrayWriter |
|
PipedInputStream |
PipedReader |
|
PipedOutputStream |
PipedWriter |
所以剛才寫(xiě)的兩段代碼就可以這樣來(lái)通過(guò)Reader和Writer來(lái)改寫(xiě):
BufferedInputStream b = new BufferedInputStream(new DataInputStream(new FileInputStream("xxx"))); BufferedReader reader = new BufferedReader(new FileReader("...")); DataOutputStream d = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("..."))); BufferedWriter writer = new BufferedWriter(new FileWriter("..."));
下面我們來(lái)看兩種典型的應(yīng)用方式,以后我們?cè)谶M(jìn)行I/O操作是就可以直接使用代碼,或者直接套用這樣的格式。
應(yīng)用一:
package typical.usage; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; /** * @author Administrator 一個(gè)讀寫(xiě)文件的小工具 */ public class TextFile extends ArrayList<String> { private static final long serialVersionUID = 1L; // 讀文件 public static String read(String file) { StringBuilder builder = new StringBuilder(); try { BufferedReader bf = new BufferedReader(new FileReader( new File(file).getAbsoluteFile())); try { String s = null; while ((s = bf.readLine()) != null) { builder = builder.append(s); builder.append("\n"); } } catch (IOException e) { e.printStackTrace(); } finally { try { bf.close(); } catch (IOException e) { e.printStackTrace(); } } } catch (FileNotFoundException e) { e.printStackTrace(); } return builder.toString(); } // 寫(xiě)文件 public static void write(String file, String content) { try { PrintWriter pr = new PrintWriter(new File(file).getAbsoluteFile()); try { pr.print(content); } finally { pr.close(); } } catch (FileNotFoundException e) { e.printStackTrace(); } } // 根據(jù)指定的正則表達(dá)式拆分文件 public TextFile(String file, String regex) { super(Arrays.asList(read(file).split(regex))); if (get(0).equals("")) remove(0); } // 讀文件 public TextFile(String file) { this(file, "\n"); } // 寫(xiě)文件 public void write(String file) { try { PrintWriter pw = new PrintWriter(new File(file).getAbsoluteFile()); try { for (String item : this) { pw.print(item); } } finally { pw.close(); } } catch (FileNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args) { String file = read("D://IAReg.txt"); System.out.println(file); write("D://b.txt", file); TextFile tf = new TextFile("D://b.txt"); tf.write("D://c.txt"); TextFile tf2 = new TextFile("D://b.txt", "\\W+"); System.out.println(tf2); } }
這個(gè)例子展示了讀寫(xiě)文件的基本方法,同時(shí)還實(shí)現(xiàn)了一個(gè)文件的過(guò)濾器。
應(yīng)用二:
package typical.usage; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; /** * * @author wangkang * 打開(kāi)一個(gè)文件用于字符輸入,同時(shí)為了提高速度運(yùn)用了緩沖。 */ public class BufferedInputFile { public static String read(String fileName) throws IOException { BufferedReader bf = new BufferedReader(new FileReader(fileName)); String s = null; StringBuilder bs = new StringBuilder(); while ((s = bf.readLine()) != null) { bs = bs.append(s + "\n"); } //關(guān)閉文件輸入流 bf.close(); return bs.toString(); } public static void main(String[] args) { try { System.out.println(read("D:\\IAReg.txt")); } catch (IOException e) { e.printStackTrace(); } } }
這個(gè)示例展示了利用緩沖打開(kāi)一個(gè)文件,并將其內(nèi)容防止字符串對(duì)象中,當(dāng)然我們獲取了文件內(nèi)容以后操作是根據(jù)我們的實(shí)際需求來(lái)。
應(yīng)用三:
package typical.usage; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; public class FormattedMemoryInputStream2 { public static void main(String[] args) throws IOException { DataInputStream in = new DataInputStream(new ByteArrayInputStream( BufferedInputFile.read("D:\\IAReg.txt").getBytes())); while (in.available() != 0) { System.out.println(in.readByte()); } } }
這個(gè)示例展示了格式化的內(nèi)存輸入,new ByteArrayInputStream(
BufferedInputFile.read("D:\\IAReg.txt").getBytes())產(chǎn)生了一個(gè)格式化的字節(jié)數(shù)組共DataInputStream使用。
應(yīng)用四:
//基本的本件輸出 package typical.usage; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; public class BasicFileOutput { public static void main(String[] args) throws IOException { String file = "D:\\test.out"; BufferedReader in = new BufferedReader(new StringReader( BufferedInputFile.read("D:\\IAReg.txt"))); PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter( file))); int lineNum = 1; String s = null; while ((s = in.readLine()) != null) { lineNum++; System.out.println(lineNum + ":" + s); //將字符串的內(nèi)容寫(xiě)進(jìn)文件 out.write(s); } in.close(); out.close(); } } //文件輸出的快捷方式 package typical.usage; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; public class ShortCutFileOutput { public static void main(String[] args) throws IOException { String file = "D:\\test.out"; BufferedReader in = new BufferedReader(new StringReader( BufferedInputFile.read("D:\\IAReg.txt"))); PrintWriter out = new PrintWriter(file); int lineNum = 1; String s = null; while ((s = in.readLine()) != null) { lineNum++; // 將字符串的內(nèi)容寫(xiě)進(jìn)文件 out.write(lineNum + ":" + s); } in.close(); out.close(); System.out.println(BufferedInputFile.read("D:\\test.out")); } }
應(yīng)用五:
package typical.usage; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class StoringAndRecoveringData { public static void main(String[] args) throws IOException { DataOutputStream out = new DataOutputStream(new BufferedOutputStream( new FileOutputStream("D://a.java"))); out.writeInt(12345); out.writeDouble(12345.000); out.writeUTF("上面的是一個(gè)整數(shù)和一個(gè)浮點(diǎn)數(shù)!!"); out.writeChars("上面的是一個(gè)整數(shù)和一個(gè)浮點(diǎn)數(shù)!!"); out.close(); DataInputStream in = new DataInputStream(new BufferedInputStream( new FileInputStream("D://a.java"))); System.out.println(in.readInt()); System.out.println(in.readDouble()); System.out.println(in.readUTF()); System.out.println(in.readChar()); in.close(); } }
這個(gè)示例展示了如何存儲(chǔ)和恢復(fù)數(shù)據(jù)。我們?cè)诔绦蛑惺褂?span style="font-family: Courier New;">DataOutputStream來(lái)存儲(chǔ)數(shù)據(jù),用DataInputStream來(lái)讀取恢復(fù)數(shù)據(jù)。我們需要知道,只要是DataOutputStream寫(xiě)入的數(shù)據(jù)都何以利用DataInputStream來(lái)準(zhǔn)確的得到數(shù)據(jù),盡管是在跨平臺(tái)應(yīng)用中,這也體現(xiàn)了java的跨平臺(tái)性。writeUTF()以與機(jī)器無(wú)關(guān)方式使用 UTF-8 修改版編碼將一個(gè)字符串寫(xiě)入基礎(chǔ)輸出流,所以我們可以將字符串和其他數(shù)據(jù)類(lèi)型相混合。
應(yīng)用六:
package typical.usage; import java.io.IOException; import java.io.RandomAccessFile; public class UsingRandomAccessingFile { static String file = "D://IAReg.txt"; static void play() throws IOException { RandomAccessFile rf = new RandomAccessFile(file, "r"); for(int i = 0; i < 11; i ++) { System.out.println(rf.readLine()); } rf.close(); } public static void main(String[] args) throws IOException { //讀取數(shù)據(jù) play(); RandomAccessFile rf = new RandomAccessFile(file, "rw"); for(int i = 0; i < 3; i ++) { rf.writeUTF("哈哈哈"); } rf.close(); //測(cè)試修改結(jié)果 play(); } }
這個(gè)示例展示了如何讀寫(xiě)隨機(jī)訪問(wèn)文件。我們是通過(guò)RandomAccessFile來(lái)實(shí)現(xiàn)這樣的功能的,RandomAccessFile適用于大小已知的記錄組成的文件。這個(gè)類(lèi)的構(gòu)造器的參數(shù)需要一個(gè)特別的String字符串,用來(lái)指定"隨機(jī)讀----r"或者"既讀又寫(xiě)-----rw"
到這里就告一段落吧,說(shuō)的都是一些皮毛的東西,至于一些高級(jí)的應(yīng)用,我也是不太明白。但是只要入門(mén)了一切都好說(shuō)了,面包會(huì)有的一切會(huì)有的。這一章我們必須要掌握裝飾器和適配器模式,這樣我們才能理解類(lèi)之間紛繁復(fù)雜的配合使用。