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類。File類一個(gè)容易讓我們"顧名思義"的類。通常我們看到這個(gè)類,就會(huì)想到這個(gè)類可能指代的是一個(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盤創(chuàng)建了這個(gè)test.txt文件,第二個(gè)輸出地意思是這個(gè)test.txt對(duì)象是不是在D盤下面。我們稍微看一下File類的源碼也可以看到設(shè)計(jì)者設(shè)計(jì)File類的意圖:
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類的大概用途差不多已經(jīng)知道了。首先聲明了一個(gè)String類型的path對(duì)象,在File的構(gòu)造方法中有對(duì)path的賦值,toString方法也是返回了getPath()取得path值,FIle類一直在和String類型的path對(duì)象打交道,所以,這個(gè)File類的功能我們也大概知道了。至于具體的File如何使用,如何創(chuàng)建一個(gè)目錄,如何打印一個(gè)目錄就沒(méi)必要舉例了吧!自己對(duì)照著API實(shí)驗(yàn)吧。不過(guò)我們要注意一下FilenameFiter這個(gè)類,他的用法下面舉了一個(gè)例子:
package org.wk.io;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.regex.Pattern;
/**
* 作用:初探File類,獲取文件名稱的小工具類,并檢查某個(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é)之分,那我們就具體展開來(lái)看一下其中的細(xì)節(jié)。
基于字符的輸入和輸出:
輸入流:任何由InputStream派生的類,他們均包含read()方法。
輸出流:任何有OutputStream派生的類,他們均包含write()方法。
FileInputStream類型
類
|
功能
|
構(gòu)造器
|
如何使用
|
ByteArrayInputStream
|
允許將內(nèi)存的緩沖區(qū)當(dāng)做InputStream使用。關(guān)閉 ByteArrayInputStream 無(wú)效,此類中的方法在關(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)生用于寫入相關(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
|
抽象類,作為裝飾器的接口。其中為其他的InputStream提供有用的接口。
|
詳細(xì)建見(jiàn)表"FileterInputStream類型"。
|
OutputStream類型
類
|
功能
|
構(gòu)造器
|
如何使用
|
ByteArrayOutputStream
|
在內(nèi)存中創(chuàng)建緩沖區(qū),所有送往"流"的數(shù)據(jù)都要放置在此緩沖區(qū)中。
|
緩沖區(qū)初始化大小(可選),用于指定數(shù)據(jù)的目的地;配合FilterOutputStream對(duì)象使用提供有用的接口。
|
FileOutputStream
|
用于將信息寫至文件
|
字符串,表示文件名,文件或FileDescriptor對(duì)象指定數(shù)據(jù)的目的地;配合FilterOutputStream對(duì)象使用提供有用的接口。
|
PipedOutputStream
|
任何寫入其中的信息都會(huì)自動(dòng)作為相關(guān)PipedInputStream的輸出。實(shí)現(xiàn)"管道化"概念。
|
PipedOutputStream指定用于多線程的數(shù)據(jù)目的地;配合FilterOutputStream對(duì)象使用提供有用的接口。
|
FilterOutputStream
|
抽象類,作為裝飾器的接口。其中為其他的OutputStream提供有用的接口。
|
見(jiàn)表"FilterOutputStream類型"。
|
前面的表中有好多的地方在賣關(guān)子,什么裝飾器類的接口,什么配合FilterInputStream提供有用的接口。裝飾器類式設(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è)裝飾器類的子類到底有什么也方便我們的選擇。
FileterInputStream類型
類
|
功能
|
構(gòu)造器參數(shù)
|
如何使用
|
DataInputStream
|
與DataOutputStream搭配之用,因此我們可以按照可移植的方式讀取基本類型。
|
InputStream
包含用于讀取基本數(shù)據(jù)類型的全部接口。
|
BufferInputStream
|
使用它可以防止每次讀取都進(jìn)行實(shí)際寫操作,代表使用緩沖區(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ì)用到。
|
所以,如果我們通常這樣寫程序
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)類
FilterOutputStream類型
類
|
功能
|
構(gòu)造器參數(shù)
|
如何使用
|
DataOutputStream
|
與DataIntputStream搭配之用,因此我們可以按照可移植的方式讀取基本類型。
|
OutputStream
包含用于讀取基本數(shù)據(jù)類型的全部接口。
|
PrintStream
|
用于產(chǎn)生格式化輸出。其中DataOutputStream處理存儲(chǔ),PrintStream處理顯示。
|
OutputStream,可以用boolean值表示在每次換行的時(shí)候是否清空緩沖區(qū)
|
BufferedOutputStream
|
使用它可以防止每次讀取都進(jìn)行實(shí)際寫操作,代表使用緩沖區(qū)。代表使用緩沖區(qū),可以用flush()清空緩沖區(qū)
|
OutputStream,指定緩沖區(qū)大小
本質(zhì)上不是接口,只不過(guò)是向進(jìn)程中添加緩沖區(qū)所必需的。與接口搭配之用。
|
所以,類比輸入流的程序我們可以這樣寫:
DataOutputStream d = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("...")));
道理與輸入流一樣,所以兩者掌握一個(gè)另一個(gè)也就可以掌握了,但是并不是所有的輸入和輸出流的API都是有對(duì)應(yīng)的,靈活掌握才是硬道理。
基于字節(jié)流的輸入輸出:
Reader和Writer兩個(gè)類的操作是基于字符,提供兼容Unicode的功能,同時(shí)也起到國(guó)際化的作用。但是這兩個(gè)類又不是完全沒(méi)有關(guān)聯(lián)的,我們可以通過(guò)適配器模式將InputStream轉(zhuǎn)換成為Reader,這個(gè)適配器類就是InputStreamReader;同樣我們可以通過(guò)適配器類OutputStreamWriter將OutputStream轉(zhuǎn)換成為Writer。
這樣我們?cè)诓僮魑募臅r(shí)候就擁有了兩個(gè)利器,一個(gè)是面向字節(jié)的InputStream,OutputStream;和面向字符的Reader,Writer。那么究竟什么情況下使用Reader,Writer,什么情況下使用InputStream,OutputStream呢?由于Reader和Writer是后來(lái)添加上的類,所以它的操作效率比InputStream和OutputStream高,于是在編程思想中給出了這樣的結(jié)論:盡量常識(shí)使用Reader和Writer,一旦程序無(wú)法成功的編譯,那是在選擇InputStream和OutputStream。
下面我們看一下這兩個(gè)類層次結(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
|
所以剛才寫的兩段代碼就可以這樣來(lái)通過(guò)Reader和Writer來(lái)改寫:
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è)讀寫文件的小工具
*/
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();
}
// 寫文件
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");
}
// 寫文件
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è)例子展示了讀寫文件的基本方法,同時(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
* 打開一個(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è)示例展示了利用緩沖打開一個(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)容寫進(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)容寫進(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寫入的數(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è)字符串寫入基礎(chǔ)輸出流,所以我們可以將字符串和其他數(shù)據(jù)類型相混合。
應(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è)示例展示了如何讀寫隨機(jī)訪問(wèn)文件。我們是通過(guò)RandomAccessFile來(lái)實(shí)現(xiàn)這樣的功能的,RandomAccessFile適用于大小已知的記錄組成的文件。這個(gè)類的構(gòu)造器的參數(shù)需要一個(gè)特別的String字符串,用來(lái)指定"隨機(jī)讀----r"或者"既讀又寫-----rw"
到這里就告一段落吧,說(shuō)的都是一些皮毛的東西,至于一些高級(jí)的應(yīng)用,我也是不太明白。但是只要入門了一切都好說(shuō)了,面包會(huì)有的一切會(huì)有的。這一章我們必須要掌握裝飾器和適配器模式,這樣我們才能理解類之間紛繁復(fù)雜的配合使用。