一.理論準備
流是個抽象的概念,是對輸入輸出裝置的抽象,Java程式中,對於資料的輸入/輸出操作都是以“流”的方式進行,裝置可以是檔案、網路、記憶體等。流具有方向性,至於是輸入資料流還是輸出資料流則是一個相對的概念,一般以程式(小馬哥說的是機器)為參考,如果資料的流向是程式至裝置,我們成為輸出資料流,反之我們稱為輸入資料流,可以將流想象成一個“水流管道”(很多資料都這麼講的),自然就出現了方向的概念。
流把I/O裝置內部的具體操作給隱藏起來了。所有InputStream和Reader的衍生類別都有一個基本的,繼承下來的,能讀取單個或byte數組的read( )方法。
Java分為位元組流(Stream結尾)和字元流(Reader、Write結尾),再分為輸入資料流(InputStream、Reader)和輸出資料流(OutputStream、Write),輸入輸出相對於記憶體而言。在讀字元的時候用字元流,如文字檔、XML(我想xml明明是字母字元組成的,屬於ASCII檔案,為何不用stream讀取呢?)等。在讀二進位檔案時候用位元組流,如RAR、EXE等不是文本以外的檔案(圖片)。Buffered開頭的流只是加了緩衝區,為了讀寫提高效率。字元流不能直接輸出,需要轉換成位元組流才能輸出(這個確實是剛知道的)!
Java 2 SDK中有三種基本類型的節點:檔案(file)、記憶體(memory)、管道(pipe)。
下面來看鄭莉教材上IO章節的那個經典圖片。
繼承自InputStream/OutputStream的流都是用於向程式中輸入/輸出資料,且資料的單位都是位元組(byte=8bit),如圖,深色的為節點流,淺色的為處理流。
![]()
繼承自Reader/Writer的流都是用於向程式中輸入/輸出資料,且資料的單位都是字元(2byte=16bit),如圖,深色的為節點流,淺色的為處理流。
![]()
二.用法分析
Java IO的一般使用原則(部分來自百度文庫):
(1) 按資料來源(去向)分類:
是檔案: FileInputStream, FileOutputStream, FileReader, FileWriter
是byte[]:ByteArrayInputStream, ByteArrayOutputStream
是Char[]: CharArrayReader, CharArrayWriter
是String: StringBufferInputStream, StringReader, StringWriter
網路資料流:InputStream, OutputStream, Reader, Writer
(2) 按是否格式化輸出分:
要格式化輸出:PrintStream, PrintWriter
(3) 按是否要緩衝分:
要緩衝:BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter。
(4) 按資料格式分:
二進位格式(只要不能確定是純文字的): InputStream, OutputStream及其所有帶Stream結束的子類
純文字格式(含純英文與漢字或其他編碼方式);Reader, Writer及其所有帶Reader, Writer的子類
(5) 按輸入輸出分:
輸入:Reader, InputStream類型的子類;輸出:Writer, OutputStream類型的子類
(6) 特殊需要:
從Stream到Reader,Writer的轉換類:InputStreamReader, OutputStreamWriter
對象輸入輸出:ObjectInputStream, ObjectOutputStream
處理序間通訊:PipeInputStream, PipeOutputStream, PipeReader, PipeWriter
合并輸入:SequenceInputStream
更特殊的需要:PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader
(7) 決定使用哪個類以及它的構造進程的一般準則如下(不考慮特殊需要):
考慮最原始的資料格式是什麼:是否為文本?是輸入還是輸出?是否需要轉換流:InputStreamReader, OutputStreamWriter?資料來源(去向)是什麼:檔案?記憶體?網路?是否要緩衝:bufferedReader (特別註明:一定要注意的是readLine()是否有定義,有什麼比read, write更特殊的輸入或輸出方法)是否要格式化輸出:print。
三.若干執行個體
還是寒假時候寫的,權當複習了,摺疊代碼的外掛程式找不到了,先看著吧。
1.System.in
複製代碼 代碼如下:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/*
* System.in是InputStream static final的,包含了多態,叫同步式或者阻塞式
* 讀取ASCII和二進位檔案(圖片),而字母就是ASCII字元(個人理解)。
*/
public class TestSystemIn {
public static void main(String[] args) {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);//有readline
String s = null;
try {
s = br.readLine();
while(s!=null) {
if(s.equalsIgnoreCase("exit")) {
break;
}
System.out.println(s.toUpperCase());
s = br.readLine();
}
br.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
2.buffer
複製代碼 代碼如下:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class TestBuffer {
public static void main(String[] args) {
try {
//查看修改日就可以判斷檔案是否是建立的了
BufferedWriter bw = new BufferedWriter(new FileWriter("d:/java.txt"));
BufferedReader br = new BufferedReader(new FileReader("d:/java.txt"));
String s = null;
for(int i=1; i<100; i++) {
s = String.valueOf(Math.random());
bw.write(s);
bw.newLine();//換行
}
//重新整理該流的緩衝,br沒有該方法
bw.flush();
while((s=br.readLine())!=null) {
System.out.println(s);
}
bw.close();
br.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
3.FileInputStream
複製代碼 代碼如下:
import java.io.*;
public class TestFileInputStream {
public static void main(String[] args) {
FileInputStream in = null;
try {
in = new FileInputStream("e:/1.txt");
}catch(FileNotFoundException e) {
System.out.println("找不到檔案");
System.exit(-1);
}
//下面表示找到了檔案
int tag = 0;
try {
long num = 0;
while((tag = in.read())!=-1) {
//read是位元組流,若是有漢字就顯示不正常了,使用reader就解決了
System.out.print((char)tag);
num++;
}
in.close();
System.out.println();
System.out.println("共讀取了" + num + "字元");
}catch(IOException e1) {//read和close都會拋出IOException
System.out.println("檔案讀取錯誤");
System.exit(-1);
}
}
}
4.FileOutputStream實現複製功能
複製代碼 代碼如下:
import java.io.*;
/*
* 實現複製功能
*/
public class TestFileOutputStream {
public static void main(String[] args) {
int b = 0;
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("d:/java.txt");
//下面的若是不存在的話會自動建立
out = new FileOutputStream("d:/my_java.txt");
while((b=in.read())!=-1) {
out.write(b);
}
in.close();
out.close();
}catch(FileNotFoundException e) {
System.out.println("找不到指定檔案");
System.exit(-1);
}catch(IOException e1) {
System.out.println("檔案複製錯誤");
System.exit(-1);
}
System.out.println("檔案已複製");
}
}
5.ObjectOutputStream與Serializable
複製代碼 代碼如下:
import java.io.*;
/*
* transient(透明的),可以用來修飾成員變數,
* 當進行序列化時不予考慮,修飾int 的話,不管原來的值是多少
* 輸出的就是0
*/
public class TestObjectIO {
public static void main(String[] args) throws Exception {
T t = new T();
t.k = 8;
FileOutputStream fos = new FileOutputStream("d:/1.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(t);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("d:/1.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
T tRead = (T)ois.readObject();
System.out.println(tRead.i + " " + tRead.j + " " + tRead.k);
}
}
class T implements Serializable {
int i = 10;
int j = 9;
double d = 2.3;
int k = 15;
}
6.轉換編碼方式
複製代碼 代碼如下:
import java.io.*;
/*
* 中文windows預設GBK編碼方式
* 追加的內容顯示為問號,不知道咋回事
*/
public class TestTransForm {
public static void main(String[] args) {
try {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d:/java.txt"));
osw.write("你好123");//可以直接寫入字串,包括中文,因為外邊的是字元流
System.out.println("編碼方式:" + osw.getEncoding());//ISO8859_1是西歐語言,又叫latin-1,此時未考慮東方人,國標(ISO)為Unicode
osw.close();
osw = new OutputStreamWriter(new FileOutputStream("d:/java.txt",true),"ISO8859_1");//true表示追加
osw.write("這是追加的內容");
System.out.println("編碼方式:" + osw.getEncoding());
osw.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
7.輸出重新導向
[code]
import java.io.*;
/*
* Print流屬於輸出資料流,提供了重載的方法很多,
* PrintWriter和PrintStream不會拋異常,使用者通過檢測錯誤狀態擷取資訊,
* 包含自動flush功能,有什麼用呢,在jsp裡也要輸出一些東西,
* 但不必每次拋異常。
*/
public class TestPrintStream {
public static void main(String[] args) {
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream("d:/java.txt");
ps = new PrintStream(fos);
}catch (IOException e) {
e.printStackTrace();
}
if(ps!=null) {
System.setOut(ps);//輸出重新導向
}
int ln = 0;
for(char c=0; c<65535; c++) {
System.out.print(c + " ");
if(ln++>100) {
System.out.println();
ln = 0;
}
}
}
}
8.DataStream
複製代碼 代碼如下:
import java.io.*;
public class TestDataStream {
public static void main(String[] args) {
//先在記憶體裡分配一個位元組數組,再有一個 OutputStream,再加上一個資料流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
try {//寫出讀入
dos.writeDouble(Math.random());
dos.writeBoolean(true);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
System.out.println(bais.available());//共幾個位元組可用
DataInputStream dis = new DataInputStream(bais);
////先寫的先讀(隊列),下面這兩個輸出不可以調換,否則就先輸出了double裡的一個位元組
System.out.println(dis.readDouble());
System.out.println(dis.readBoolean());
dos.close();
dis.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
四.小問題
為什麼Writer/Reader不繼承自Stream呢?字元最終也要轉換成二進位呀。Writer/Readre繼承OutputStream/InputStream,這樣的繼承層次不是更好,為什麼要單獨做一個呢,而且Stream也有些子類能夠實現字串的讀寫。大神回答:單一職責。太牽強了。