主題 | 概要 |
---|---|
JAVA基礎 | JAVA的I/O部分概括、總結 |
編輯 | 時間 |
新建 | 20160730 |
序號 | 參考資料 |
1 | JAVA編程思想 |
2 | http://docs.oracle.com/javase/8/docs/api/ |
編程語言的I/O類庫中常用流這個抽象概念,它代表任何有能力產生數據的數據源對象或者是有能力接收數據的接收端對象。“流”屏蔽了實際的I/O設備中處理數據的細節。
JAVA類庫中的I/O分成輸入和輸出兩部分,可以在JDK文檔裏的類層次結構中查看到。通過繼承,任何自Inputstream或Reader派生而來的類都含有名爲read()的基本方法,用於讀取當個字節或者字節數組。同樣,任何自Outputstream或Writer派生而來的類都含有write()的基本方法,用於寫單個字節或字節數組。我們很少使用單一的類來創建流對象,而是通過疊合多個對象來提供所期望的功能。實際上,Java中“流”類庫讓人迷惑的主要原因就在於:創建單一的結果流,卻需要創建多個對象。
InputStream與OutputStream
在JDK1.0中,類庫的設計者首先限定與輸入有關的所有類都應該從InputStream繼承,而與輸出有關的所有類都由OutputStream繼承。
InputStream類型
InputStream的作用是用來表示那些從不同數據源產生輸入的類,如下圖所示,這些數據源包括:
1)字節數組
2)String對象
3)文件
4)“管道”,工作方式與實際管道相似,即從一端輸入,從另一端輸出
5)一個由其他種類的流組成的序列,以便我們可以將它們收集合併到一個流內。
6)其他數據源,如Internet連接等。
另外,FilterInputStream也屬於一種InputStream,爲“裝飾器”(decorator)類提供基類,其中“裝飾器”類可以把屬性或有用的接口與輸入流連接在一起。
OutputStream類型
如下表所示,該類型的類決定了輸出所要去往的目標:字節數組(但不是String,不過可以用字節數組自己創建),文件或管道。
另外,FilterOutputStream也屬於一種OutputStream,爲“裝飾器”(decorator)類提供基類,其中“裝飾器”類可以把屬性或有用的接口與輸出流連接在一起。
添加屬性和有用的接口
JAVA I/O類庫需要多種不同功能的組合,這正是使用裝飾器模式的理由所在。在編寫程序時,它給我們提供了相當多的靈活性(因爲我們可以很容易的混合和匹配屬性),但是它同時也增加了代碼的複雜性。JAVA I/O類庫操作不便的原因在於:我們必須創建許多類—“核心”I/O類型加上所有裝飾器,才能得到我們所希望的單個I/O對象。
通過FilterInputStream從InputStream讀取數據
FilterInputStream,抽象類,作爲“裝飾器”接口。主要使用派生類來完成兩件完全不同的事情,DataInputStream允許我們讀取不同的基本類型數據以及String對象。
其他FilterInputStream類則在內部修改InputStream的行爲方式,是否緩衝,是否保留它所讀過的行等等。
我們幾乎每次都要對輸入進行緩衝,不管連接的是什麼I/O設備。
通過FilterOutputStream向OutputStream寫入
同樣,使用FilterOutputStream的派生類,來改變輸出的行爲。
DataOutputStream可以將各種數據類型以及String對象格式化輸出到“流”中,所有方法都以write開頭,如writeByte(),writeFloat()等等。
PrintStream是爲了可以格式化打印所有的基本數據類型及String對象。
BufferedOutputStream對數據流使用緩衝技術,當每次向流寫入時,不必每次都進行實際的物理寫操作。
Reader與Writer
JDK1.1對基本的I/O流類庫進行了重大的修改,當我們初次看到Reader和Writer類時,可能會以爲這是兩個用來替代InputStream和OutputStream的類,儘管一些原始的“流”類庫不再被使用(如果使用它們,則會收到編譯器的警告信息),使用InputStream和OutputStream在以面向字節形式的I/O中仍可提供極有價值的功能,Reader和Writer則提供兼容Unicode與面向字符的I/O功能。
有時候我們必須把來自於“字節”層次結構中的類與“字符”層次結構中的類結合起來使用,爲了實現這個目的,要用到“適配器”(adapter)類,InputStreamReader可以把InputStream轉換成Reader, OutputStreamWriter可以把OutputStream轉換成Writer。
數據的來源和去處
幾乎所有原始的Java I/O流類都有相應的Reader類和Writer類來提供天然的Unicode操作。
更改流的行爲
對於InputStream和OutputStream來說,會使用FilterInputStream和FilterOutputStream的裝飾器子類來修改“流”以滿足特殊需求。Reader和Writer的類繼承層次結構繼續沿用相同的思想,但是並不完全相同。
有一點很清楚,無論我們何時使用readLine(),都不應該使用DataInputStream(這會遭到編譯器的強烈反對),而應該使用BufferedReader。除了這一點外,DataInputStream仍是I/O類庫的首選成員。
未發生變化的類
特別是DataOutputStream,在使用時沒有任何變化;因此如果想以“可傳輸的”格式存儲和檢索數據,可以使用InputStream和OutputStream繼承層次結構。
自我獨立的類:RandomAccessFile
RandomAccessFile適用於大小已知的記錄組成的文件,所以我們可以使用seek()將記錄從一處轉移到另一處,然後讀取或者修改記錄。
RandomAccessFile是一個完全獨立的類,從頭開始編寫其所有的方法,與InputStream和OutputStream繼承結構沒有任何的關聯。在任何情況下,它都是自我獨立的,直接從Object對象派生而來。
典型使用方式
緩衝輸入文件
打開一個文件作用於字符輸入,可以使用String或File對象作爲文件名的FileInputReader,並用BufferedReader進行文件緩衝,應用BufferedReader的readline()進行讀取。當readline()返回爲NULL時,就達到了文件的末尾。
//: io/BufferedInputFile.java
import java.io.*;
public class BufferedInputFile {
// Throw exceptions to console:
public static String
read(String filename) throws IOException {
// Reading input by lines:
BufferedReader in = new BufferedReader(
new FileReader(filename));
String s;
StringBuilder sb = new StringBuilder();
while((s = in.readLine())!= null)
sb.append(s + "\n");
in.close();
return sb.toString();
}
public static void main(String[] args)
throws IOException {
System.out.print(read("BufferedInputFile.java"));
}
} /* (Execute to see output) *///:~
最後,需要調用 close()來顯示關閉文件。
從內存輸入
調用上面例子封裝的類,把數據緩存入內存中,再用緩存中的String結果來創建一個StringReader,然後調用read()每次讀取一個字符,並把它發送到控制檯。
//: io/MemoryInput.java
import java.io.*;
public class MemoryInput {
public static void main(String[] args)
throws IOException {
StringReader in = new StringReader(
BufferedInputFile.read("MemoryInput.java"));
int c;
while((c = in.read()) != -1)
System.out.print((char)c);
}
} /* (Execute to see output) *///:~
格式化的內存輸入
要讀取格式化數據,可以使用DataInputStream,它是一個面向字節的I/O類(不是面向字符的),因此必須使用InputStream類而不是reader類。
//: io/FormattedMemoryInput.java
import java.io.*;
public class FormattedMemoryInput {
public static void main(String[] args)
throws IOException {
try {
DataInputStream in = new DataInputStream(
new ByteArrayInputStream(
BufferedInputFile.read(
"FormattedMemoryInput.java").getBytes()));
while(true)
System.out.print((char)in.readByte());
} catch(EOFException e) {
System.err.println("End of stream");
}
}
} /* (Execute to see output) *///:~
基本的文件輸出
FileWriter可以向文件寫入數據,只需創建一個與指定文件連接的FileWriter。通常會用BufferedWriter將其包裝起來用以緩衝輸出,另外,爲了提供格式化機制,會將它裝飾成PrintWriter。
//: io/BasicFileOutput.java
import java.io.*;
public class BasicFileOutput {
static String file = "BasicFileOutput.out";
public static void main(String[] args)
throws IOException {
BufferedReader in = new BufferedReader(
new StringReader(
BufferedInputFile.read("BasicFileOutput.java")));
PrintWriter out = new PrintWriter(
new BufferedWriter(new FileWriter(file)));
int lineCount = 1;
String s;
while((s = in.readLine()) != null )
out.println(lineCount++ + ": " + s);
out.close();
// Show the stored file:
System.out.println(BufferedInputFile.read(file));
}
} /* (Execute to see output) *///:~
文本文件輸出的快捷方式:Java SE5在PrintWriter中添加了一個輔助構造器,使得你不必在每次希望創建文本文件,並向其中寫入時,都去執行所有的裝飾操作。
//: io/FileOutputShortcut.java
import java.io.*;
public class FileOutputShortcut {
static String file = "FileOutputShortcut.out";
public static void main(String[] args)
throws IOException {
BufferedReader in = new BufferedReader(
new StringReader(
BufferedInputFile.read("FileOutputShortcut.java")));
// Here's the shortcut:
PrintWriter out = new PrintWriter(file);
int lineCount = 1;
String s;
while((s = in.readLine()) != null )
out.println(lineCount++ + ": " + s);
out.close();
// Show the stored file:
System.out.println(BufferedInputFile.read(file));
}
} /* (Execute to see output) *///:~
存儲和恢復數據
爲了輸出可供另一個“流”恢復的數據,我們需要用DataOutputStream寫入數據,並用DataInputStream恢復數據,這些流可以是任何形式。需注意,DataInputStream和DataOutputStream是面向字節的,因此使用InputStream和OutputStream。
//: io/StoringAndRecoveringData.java
import java.io.*;
public class StoringAndRecoveringData {
public static void main(String[] args)
throws IOException {
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("Data.txt")));
out.writeDouble(3.14159);
out.writeUTF("That was pi");
out.writeDouble(1.41413);
out.writeUTF("Square root of 2");
out.close();
DataInputStream in = new DataInputStream(
new BufferedInputStream(
new FileInputStream("Data.txt")));
System.out.println(in.readDouble());
// Only readUTF() will recover the
// Java-UTF String properly:
System.out.println(in.readUTF());
System.out.println(in.readDouble());
System.out.println(in.readUTF());
}
} /* Output:
3.14159
That was pi
1.41413
Square root of 2
*///:~
要注意readUTF()和writeUTF()方法,UTF-8是一種多字節格式,其編碼長度會根據實際使用的字符集會有所變化。如果我們使用的只是ASCII碼或者幾乎都是ASCII字符(只佔7位),那麼就顯得浪費帶寬,所以UTF-8將ASCII字符編碼成單一字節的形式,而非ASCII字符則編碼成兩到三個字節的形式。
讀寫隨機訪問文件
使用RandomAccessFile,類似於組合使用了DataInputStream和DataOutputStream,另外,利用seek()可以在文件中到處移動,並修改文件中的某個值。
//: io/UsingRandomAccessFile.java
import java.io.*;
public class UsingRandomAccessFile {
static String file = "rtest.dat";
static void display() throws IOException {
RandomAccessFile rf = new RandomAccessFile(file, "r");
for(int i = 0; i < 7; i++)
System.out.println(
"Value " + i + ": " + rf.readDouble());
System.out.println(rf.readUTF());
rf.close();
}
public static void main(String[] args)
throws IOException {
RandomAccessFile rf = new RandomAccessFile(file, "rw");
for(int i = 0; i < 7; i++)
rf.writeDouble(i*1.414);
rf.writeUTF("The end of the file");
rf.close();
display();
rf = new RandomAccessFile(file, "rw");
rf.seek(5*8);
rf.writeDouble(47.0001);
rf.close();
display();
}
}
管道流
主要用於多線程中,進行任務之間的通信。