最近再看I/O這一塊,故作為總結記錄於此。JDK1.4引入NIO後,原來的I/O方法都基於NIO進行了優化,提高了性能。I/O操作類都在java.io下,大概將近80個,大致可以分為4類: 基於位元組操作的I/O介面:以InputStream和OutputStream為基類,也是I/O操作的基礎。 基 ...
最近再看I/O這一塊,故作為總結記錄於此。JDK1.4引入NIO後,原來的I/O方法都基於NIO進行了優化,提高了性能。I/O操作類都在java.io下,大概將近80個,大致可以分為4類:
- 基於位元組操作的I/O介面:以InputStream和OutputStream為基類,也是I/O操作的基礎。
- 基於字元操作的I/O介面:以Reader和Writer為基類,字元的讀寫是基於位元組進行的,中間進行了轉換。
- 基於磁碟操作的I/O介面:主要是File,代表目錄下的所有文件。
- 基於網路操作的I/O介面:主要是Socket,實現網路數據的傳輸。
本文大致總結一下基於位元組和字元的I/O操作,主要理清JAVA I/O中的類關係。
摘自《Java編程思想》:類庫中常使用“流”這個抽象的概念,代表任何有能力產出數據的數據源對象或有能力接收數據的接收端對象。其屏蔽了I/O設備中數據處理的細節。I/O類分為輸入和輸出兩類。通過 繼承,任何InputStream或Reader的派生類都含有read()方法,用於讀取單個位元組或字元,任何OutputStream或Writer的派生類都含有write()方法,用於寫單個位元組或字元。通常不會使用單一的類創建流對象,而是通過疊合多個對象提供期望的功能(即採用裝飾器模式)。
一、基於位元組的I/O操作
1. InputStream類型
InputStream的作用表示那些從不同數據源產生輸入的類,即其派生類多是不同數據源對應的流對象。如下:
ByteArrayInputStream:從記憶體緩衝區讀取位元組數組
FileInputStream:從文件中讀取位元組,其構造參數可以是文件名、File對象或FileDescriptor
ObjectInputStream:主要用於反序列化,讀取基本數據類型或對象
PipedInputStream:產生用於寫入相關PipedOutputStream的數據,實現“管道化”概念,多用於多線程中。
FilterInputStream:作為裝飾器類,其子類與上述不同流對象疊合使用,以控制特定輸入流。
其中,FilterInputStream的子類通過添加屬性或有用的介面控制位元組輸入流,其構造函數為InputStream,常見的幾個如下:
DataInputStream:與DataOutputStream搭配使用,讀取基本類型數據及String對象。
BufferdInputStream:使用緩衝區的概念,避免每次都進行實際讀操作,提升I/O性能。
InflaterInputStream:其子類GZIPInputStream和ZipInputStream可以讀取GZIP和ZIP格式的數據。
2. OutputStream類型
與InputStream相對應,OutputStream的作用表示將數據寫入不同的數據源,常用的輸出流對象如下:
ByteArrayOutputStream:在記憶體中創建緩衝區,寫入位元組數組
FileOutputStream:將位元組數據寫入文件中,其構造參數可以是文件名、File對象或FileDescriptor
ObjectOutputStream:主要用於序列化,作用於基本數據類型或對象
PipedOutputStream:任何寫入其中的數據,都會自動作為相關PipedInputStream的輸出,實現“管道化”概念,多用於多線程中。
FilterOutputStream:作為裝飾器類,其子類與上述不同流對象疊合使用,以控制特定輸出流。
其中,FilterOutputStream的子類通過添加屬性或有用的介面控制位元組輸入流,其構造函數為InputStream,常見的幾個如下:
DataOutputStream:與DataInputStream搭配使用,寫入基本類型數據及String對象。
PrintStream:用於格式化輸出顯示。
BufferdOutputStream:使用緩衝區的概念,避免每次都進行實際寫操作,提升I/O性能。
DeflaterOutputStream:其子類GZIPOutputStream和ZipOutputStream可以寫GZIP和ZIP格式的數據。
二、基於字元的I/O操作
不管是磁碟還是網路傳輸,數據處理的最小單元都是位元組,而不是字元。故所有I/O操作的都是位元組而不是字元。為了方便引入了字元操作,其中涉及位元組到字元的轉換適配,InputStreamReader可以把InputStream轉為Reader,OutputStreamWriter可以把OutputStream轉為Writer。對上述按位元組操作的流對象,可以採用FilterInputStream和FilterOutputStream的裝飾器子類控制流。Reader和Writer沿用相似的思想,但不完全相同。
1. Reader類型
繼承自Reader類的,字元型數據來源常用類,如下:
InputStreamReader:位元組與字元適配器,子類包含FileReader(以字元形式讀取文件)
CharArrayReader:讀取字元數組
StringReader:數據源是字元串
BufferedReader:讀取字元輸入流,併進行緩存,常用法:BufferedReader in = new BufferedReader(new FileReader("foo.in")); 表示採用緩存的方式從文件讀取數據
PipedReader:管道形式讀取字元
FilterReader:對Reader裝飾,直接使用的不多,如PushbackReader
2. Writer類型
繼承自Writer類的,字元型數據來源常用類,如下:
OutputStreamReader:位元組與字元適配器,子類包含FileWriter(以字元形式寫文件)
CharArrayWriter:寫字元數組
StringWriter:內部有StringBuffer,用於緩存構造字元串
BufferedWriter:字元輸出流,常用法:PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("foo.out"))); 表示將數據格式化並用緩存的方式寫入文件
PipedWriter:管道形式輸出字元
FilterWriter:對Writer裝飾,如XMLWriter
三、自我獨立的類RandomAccessFile
該類可以隨機訪問文件,實現了DataOutput, DataInput,不是InputStream或OutputStream繼承層次結構的一部分。與其他I/O類本質有所不同,可以在一個文件內向前或向後移動。
工作方式類似與DataOutputStream和 DataInputStream,用法如下:
RandomAccessFile randomAccessFile = new RandomAccessFile("data.dat", "rw")
其中,r代表讀,w代表寫。
四、常用實例
1. 緩存輸入文件
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; /** * 緩存輸入文件,防止頻繁與文件交互 * 1.採用裝飾器模式,BufferedReader從FileReader中讀取字元,FileReader為字元數據源 * 2.FileReader繼承InputStreamReader,實例化一個FileInputStream對象作為位元組數據源, * 3.InputStreamReader繼承Reader,包含StreamDecoder,將位元組數據轉換為字元;編碼格式沒有指定時採用預設編碼。 * 4.Reader可以實現對FileInputStream加鎖*/ public class BufferedInputFile { public static String read(String filename) throws IOException { BufferedReader bufferedReader = new BufferedReader(new FileReader(filename)); String s; StringBuilder sb = new StringBuilder(); while((s = bufferedReader.readLine()) != null) { sb.append(s + "\n"); } bufferedReader.close(); return sb.toString(); } public static void main(String[] args) throws IOException { System.out.println(read("src/com/test/io/BufferedInputFile.java")); } }
//輸出類文件到控制台
2.從記憶體輸入
import java.io.IOException; import java.io.StringReader; /** * 將文件讀入記憶體 * 具體形式:new StringReader(new BufferdReader(new FileReader(filename))) * 通過緩存讀文件,防止每讀一個位元組,都與文件直接交互*/ public class MemoryINput { static String filename = "src/com/test/io/BufferedInputFile.java"; public static void main(String[] args) throws IOException{ StringReader in = new StringReader(BufferedInputFile.read(filename)); int c; while((c = in.read()) != -1) { System.out.println((char)c); } } }
3.格式化記憶體輸入
import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; /** * 格式化的記憶體輸入 * 1.in.readByte()讀取位元組,無法判斷位元組是否有效合法,因此無法判斷結尾,報java.io.EOFException * 2.採用available()方法預估還有多少位元組可存取*/ public class FormattedMemoryInput { public static void main(String[] args) throws IOException { DataInputStream in = new DataInputStream( new ByteArrayInputStream(BufferedInputFile.read("src/com/test/io/BufferedInputFile.java").getBytes())); // byte c; // while((c = in.readByte()) !=-1) { // System.out.print((char) c); // } while(in.available() != 0) { System.out.print((char) in.readByte()); } } }
4.基本文件輸出
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; /** * 基本文件輸出 * 1.先利用BufferedInputFile和StringReader將數據讀到記憶體,記住輸入流已經關閉 * 2.new PrintWriter(new BufferedWriter(new FileWriter(outfile)))輸出字元到文件 * 註意,此處使用BufferedWriter進行緩衝,防止每個位元組都與文件交互 * 3.文本文件輸出快捷方式 PrintWriter out = new PrintWriter(outfile); * 底層實現了緩存new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName)))*/ public class BasicFIleOutput { static String filename = "src/com/test/io/BufferedInputFile.java"; static String outfile = "BasicFIleOutput.out"; public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader(new StringReader(BufferedInputFile.read(filename))); // PrintWriter out = new PrintWriter(new FileWriter(outfile)); // PrintWriter out = new PrintWriter(outfile); PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(outfile))); int lineCount = 1; String s; while((s = in.readLine()) != null) { out.println(lineCount++ + ":" +s); } out.close(); } }
5.存儲與恢複數據
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; /** * 1.DataInputStream能從DataOutputStream中準確讀取數據 * 2.讀數據時,必須知道數據精確的位置,否則會報錯 * 3.writeUTF與readUTF採用UTF-8的變體進行編碼 * */ public class StoringAndRecoveringData { public static void main(String[] args) throws IOException { DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("Data.txt"))); out.writeDouble(3.12159); out.writeUTF("this is pi"); out.writeDouble(1.4414); out.writeUTF("this is root of 2"); out.close(); DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("Data.txt"))); System.out.println(in.readDouble()); System.out.println(in.readUTF()); System.out.println(in.readDouble()); in.close(); } }
五、總結
1. I/O操作本質是基於位元組流的操作,InputStream和OutputStream對輸入和輸出源進行了抽象,其子類代表不同的數據源。
2. FilterInputStream和FilterOutputStream採用裝飾器模式,對輸入和輸出流進行控制,如採用緩衝器、讀基本數據類型等。
3. Reader和Writer代表基於字元的操作,底層是基於位元組操作,經過InputStreamReader和OutputStreamWriter,採用StreamEncoder和StreamDecoder,將輸入輸出流,按Charset進行轉換
4. 所有基於位元組或字元的操作,基本都採用疊合的方式。如輸入流採用緩存的方式從文件中讀取,輸出流採用緩存的方式按格式輸出到文件。
5. 理清他們之間的關係,有利於瞭解I/O的操作過程。