一、流的概念
1、定義:流是通過一定的傳播路徑從源傳遞到目的地字節序列。
java以流的形式處理所有的輸入和輸出。
2、流的分類:
輸入流:在java中,可從中讀出一系列字節的對象稱爲“輸入流”。
輸出流:能向其中寫入一系列字節的對象稱爲“輸出流”。
我們將向輸出流寫入數據信息的叫數據源;我們將從輸入流讀取數據信息的叫目的地。
數據源和目的地可以是(而且經常)文件,但它們也可能是一個網絡連接(),
甚至一個內存塊。所有數據最終都要保存爲一系列字節。
java中以抽象類InputStream 和OutputStreaml來實現單字節的字符。
以抽象類Reader和Writer(它們的子類)來實現來專門處理採用Unicode(每個字符都使用了雙字節)格式保存的信息。
3、流的抽象類分析
InputStream類
InputStream類提供了一個抽象方法:
- public abstract int read() throws IOException
方法說明:返回可讀取一個字節,並將它返回;假如它遇到輸入源的結尾,就會返回一個-1。
通常覆蓋這個方法,以便提供更有用的功能。例如:在FileInputStream類中,這個方法會從一個文件中讀入一個字節。System.in是InputStream的一個字類預先定義好的對象,允許我們從鍵盤讀取信息。
InputStream類也提供了非抽象方法,用於讀取一個字節數組,或者跳過一定數量的字節。這些方法會調用抽象的read方法,所以子類只需覆蓋一個方法。
OutputStream類
OutputStream類定義了下述抽象方法:
- public abstract void write(int b) throws IOException
方法說明:它可將一個字節寫到指定的輸出位置。
相關知識聯繫:
無論read還是write方法都可能造成一個線程的延誤,直到字節被實際讀出或寫入爲止。換言之,假如字節不能馬上讀出或寫入(通常是由於繁忙的網絡連接),java就會暫停(掛起)包含了此次調用的那個線程。這樣一來,其它線程就有機會在方法等待期間,做一些更有用的事情。
重點提示:
完成了對數據流的讀取或寫入操作,請記住用恰當的close方法將其關閉,這是由於流會佔用操作系統有限的資源。特別指出的是,假如你沒有關閉一個文件,最後一個字節包便可能永遠都不會投遞出去。也可用flush方法來人工刷新(清空)輸出緩衝。
由於程序很少需要讀寫字節流,更多的是對數字、字串以及對象等等。JAVA爲我們提供了多種流類,均是從基本的InputStream和OutputStream類衍生出來的。利用這些類,便可直接對常見格式的數據進行操作;而不必操作那些原始的字節流。
二、文件流
1、定義:FileInputStream和FileOutputStream(文件輸入/輸出流)是我們能對一個磁盤文件涉及的數據以流的形式流進行輸入及輸出處理。
2、構造一個文件流:
構造方式一:在構件器中指定文件名,或者完整的路徑名。
- FileInputStream fin=new FileInputStream("employee.dat");
提示:由於對java.io中的所有類來說,它們對相對路徑名進行解釋時,都以當前的工作目錄爲準,所以一般都需要提前瞭解當前的工作目錄是什麼。爲獲知這一信息,請調整System.getProperty("user.dir").
構造方式二:也可以使用一個File對象構造文件流。
- File f=new File("employee");
- FileInputStream fin=new FileInputStream(f);
前提知識:File 類
1)、File類的功能:用於訪問文件和目錄對象。它使用主操作系統的文件命名慣例。通過File類的方法可刪除、重命名文件。這些方法檢查文件的讀和寫權限。通過File類的目錄方法來創建、刪除、重命名和列出目錄。
2)、構造File類
- File(String pathname) //通過將給定的路徑名字轉換成一個抽象的路徑名來創建File類對象;
- File(String parent,String child) //由父路徑名和子路徑名的字符串創建出File類的對象;
- File(File parent,String child) //由父File類的對象和子路徑創建出File類的對象
演示代碼:
- File file;
- file=new File("javaFile") //路徑名;
- file=new File("//","javaFile") //父和子路徑名;
- File dir=new File("//");
- file=new File(dir,"javaFile"); //File對象和路徑名;
3)、File類的常用方法:參考API
4)、程序演示:
- //----------------------------* *----------------------------------------
- import java.io.*;
- import javax.swing.*;
- public class AA
- {
- public static void main(String args[])
- {
- try
- {
- File f=new File("c://mjp//","a.txt");
- boolean b=f.exists();
- System.out.println("getName() = "+f.getName());
- System.out.println("getParent() = "+f.getParent());
- System.out.println("exists() = "+String.valueOf(b));
- System.out.println("mkdir() = "+String.valueOf(f.mkdir()));
- System.out.println("List() = "+f.list());
- System.out.println("getPath() = "+f.getPath());
- }
- catch(Exception e){}
- }
- }
3、文件流代碼演示:
- -------------------* 向一個文件中輸入信息 *---------------------------
- import java.io.*;
- import java.lang.*;
- public class AA
- {
- public static void main(String args[])
- {
- try
- {
- FileInputStream fin=new FileInputStream("a.txt");
- byte[] b=new byte[100];
- fin.read(b);
- System.out.println("Read From file of a.txt is : "+b[0]);
- System.out.println("Read From file of a.txt is : "+new String(b));
- /*int a=fin.read();
- System.out.println("Read From file of a.txt is : "+ String.valueOf(a));*/
- }
- catch(Exception e)
- {}
- }
- }
- -------------------* 向一個文件輸入信息,然後從中讀出*-------------------
- import java.io.*;
- import java.lang.*;
- public class AA
- {
- public static void main(String args[])
- {
- try
- {
- //輸入信息部分
- FileOutputStream fin1=new FileOutputStream("a.txt");
- byte[] b=new byte[10];
- System.out.println("please input message:");
- System.in.read(b);
- fin1.write(b);
- //輸出信息部分
- FileInputStream fin2=new FileInputStream("a.txt");
- fin2.read(b);
- for(int i=0;i<b.length&&b[i]!=0;i++)
- System.out.println(""+b[i]);
- System.out.println("Read From file of a.txt is : "+new String(b));
- fin1.close();
- fin2.close();
- }
- catch(Exception e)
- {}
- }
- }
三、過濾流
抽象的InputStream和OutputStream類允許我們對字串及數字進行讀寫。爲達到這個目的,還需要功能更多的子類。例如:DateInputStream和DataOutputStream允許我們對所有基本的java類型進行讀寫。
文件流類與抽象的InputStream和OutputStream類相似,這些類也只支持字節級的讀寫操作。換言之,只能從fin對象中讀取字符和字節數組。byte b=fin.read(),他們沒有提供專門的數值類型,所以DataInputStream沒有辦法從一個文件中獲取數字。
解決方案:java給流職責分工,某些流(FileInputStream)負責從文件或另一些更特殊的地方讀入字節數據。而另一些流
(DataInputStream、PrintWriter)負責將字節“組裝”成更有用的數據類型。必須綜合運用這兩種流,將其合併成所謂的"過濾流(FilteredStreams)",方法是將一個現成的流傳遞給另一個流的構建器。
解決方案舉例:從一個文件中讀取數字
步驟:
1)創建一個FileInputStream;
2)將其傳遞給一個DataInputStream的構造函數;
代碼:
1)
- FileInputStream fin=new FileInputStream("a.txt");
2)
- DataInputStream din=new DataInputStream(fin);
- double s=din.readDouble();
演示程序:
- ---------------------------------* 從一個文件中讀取數字 *-------------------------------------
- import java.io.*;
- import java.lang.*;
- public class B
- {
- public static void main(String args[])
- {
- try
- {
- FileOutputStream fin1=new FileOutputStream("a.txt");
- DataOutputStream din1=new DataOutputStream(fin1);
- din1.writeDouble(102);
- FileInputStream fin2=new FileInputStream("a.txt");
- DataInputStream din2=new DataInputStream(fin2);
- Double d= new Double(din2.readDouble());
- System.out.println("read message is : "+d.toString());
- din2.close();
- fin2.close();
- din1.close();
- fin1.close();
- }
- catch(Exception e)
- {}
- }
- }
補充知識:
默認情況下,流不會進行緩衝。即每讀一次,都會要求操作系統提供一個字節。通過BufferedInputStream和
BufferedOutputStream對流構建器進行過濾分層,實現緩衝。
1、構造函數:
- BufferedInputStream(InputStream in)
- BufferedInputStream(InputStream in, int size) //size:緩衝區的大小
2、代碼演示:
- BufferedInputStream bis=new BufferedInputStream(System.in);
- BufferedInputStream bis=new BufferedInputStream(System.in ,100);
3、程序舉例:
- //-----------*對a.txt文件進行緩衝以及數據輸入操作*---------------
- import java.io.*;
- import java.util.*;
- import java.lang.*;
- public class BB
- {
- public static void main(String args[])
- {
- try
- {
- DataInputStream dis=new DataInputStream(new BufferedInputStream(new FileInputStream("a.txt")));
- byte[] c=new byte[10];
- while(dis.read(c)!=-1)
- {
- for(int i=0;i<10;i++)
- System.out.println("the message is "+String.valueOf(c[i]));}
- }catch(Exception e){}
- }
- }
四、隨機存取文件
上述演示中,對文件的讀寫,總是從頭開始,當向文件中存取信息,會產生原文信息被覆蓋的情況。同時希望可從文件的任意位置讀取。
RandomAccessFile(隨機存取文件)是一種特殊的流類,可用它查找或寫入文件的任何地方的數據。它同時實現了DataInput和DataOutput兩個接口。磁盤文件採用的是隨機存取方式,但來自一個網絡的數據流卻不是這樣。打開一個隨機文件後,要麼只對其進行讀操作。要麼需要同時進行讀寫。
1、構造一個隨機存取文件類
- RandomAccessFile(String name,String mode) //name:系統專用文件名
- mode:"r"代表只讀;"rw"代表可讀寫
- RandomAccessFile(File file ,String mode) //file:封裝了特殊系統專用文件名的一個File對象
- mode:"r"代表只讀;"rw"代表可讀寫
代碼演示:
1)
- RandomAccessFile raf=new RandomAccessFile("a.txt","rw");
2)
- File fin=new File("a.txt");
- RandomAccessFile raf=new RandomAccessFile(fin,"rw");
2、常用方法:
- long getFilePointer() //返回當前的文件指針位置;
- void seek(long pos) //將文件指針設爲自文件開頭,第pos個字節的位置;
- long length() //返回文件長度,以字節爲單位;
3、演示程序:
- //---------* 向一個a.txt文件中追加信息,然後讀取文件 *----------------
- import java.io.*;
- public class C
- {
- public static void main(String args[])
- {
- try
- {
- RandomAccessFile raf=new RandomAccessFile("a.txt","rw");
- raf.seek(raf.length()+1);
- System.out.println("the first pointer is "+raf.getFilePointer());
- raf.writeBytes("Hello jiang wei !");
- System.out.println("the second pointer is "+raf.getFilePointer());
- raf.seek(0);
- byte[] b=new byte[(int)raf.length()];
- raf.read(b);
- String s=new String(b);
- System.out.println("the input is " +s);
- System.out.println("the second pointer is "+raf.getFilePointer());
- raf.close();
- }
- catch(Exception e)
- {}
- }
- }
補充知識:
********* 文本流*********
以上討論的都是二進制輸入和輸出。儘管二進制I/O的速度非常快,效率也很高。但人眼無法識別。採用文本格式可以解決此問題。例如:1234用二進制保存,他會作爲一系列那變起意的字節寫入:00 00 04 D2(用十六進制),採用文本格式,保存的就是一個簡單的字串“1234”由於java採用了Unicode字符,但java目前運行的大多數環境中,他們使用的都是自己的一套字符編碼。可能是單字節、雙字節或者是可變字節方案。比如在Windows中,字串需要用ASCII格式寫入,亦即31 32 33 34 ,中間沒有附加的0值字節。如果將Unicode編碼寫進一個文本文件,那麼使用主機環境的各種工具,通常很難憑人眼辨別出結果文件的內容。爲解決此問題,就像早先指出的那樣,java現在提供了一套過濾流,可用來彌補Unicode編碼文字與本機操作系統採用的字符編碼間的裂縫。所有這些類都從抽象類Reader和Writer中衍生出來,而且名字與以前二進制數據採用的名字相同。
一具體流類分析
1、InputStreamReader:可將採用特殊字符編碼方案的、包含了字節的一個輸入流轉換成一個Reader,它產生的將是Unicode字符。
1) 構造函數:
- InputStreamReader(InputStream in) //採用主機系統默認的字符編碼方案。
- nputStreamReader(InputStream in, String enc)
- throws UnsupportedEncodingException //指定一種不同的編碼方案。
2)代碼實現:
- InputStreamReader in=new InputStreamReader(System.in);
- InputStreamReader in=new InputStreamReader(new FileInputStream("a.txt"),"GB2312");
2、OutputStreamWriter:可將一個Unicode字符流轉換成採用特殊字符編碼方案的字節流。
1)構造函數:
- OutputStreamWriter(OutputStream out) //採用主機系統默認的字符編碼方案。
- OutputStreamWriter(OutputStream out, String enc)
- throws UnsupportedEncodingException//指定一種不同的編碼方案。
2)代碼實現:
- OutputStreamWriter(new FileOutputStream("a.txt"));
- OutputStreamWriter(new FileOutputStream("a.txt"),"GB2312");
3、演示程序:
- //-------------*令一個reader從控制檯讀入按鍵,並將其自動轉換成Unicode*---------------------------
- import java.io.*;
- public class D
- {
- public static void main(String args[])
- {
- try{
- char[] c=new char[10];
- System.out.println("please input message : ");
- InputStreamReader isr=new InputStreamReader(System.in);
- isr.read(c,0,10 );
- System.out.println("str is "+String.valueOf(c));
- isr.close();
- }
- catch(IOException e){}
- }
- }
- //--------------------*文件信息“0123456789”輸入 ,然後輸出*-----------------------------------------------
- import java.io.*;
- public class E
- {
- public static void main(String args[])
- {
- try{
- char[] c=new char[100];
- OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("a.txt"));
- osw.write("0123456789",0,10);
- osw.close();
- InputStreamReader isr=new InputStreamReader(new FileInputStream("a.txt"));
- isr.read(c,0,100);
- for(int i=0;i<10;i++)
- {
- System.out.print("the char["+i+"]");
- System.out.println(c[i]);
- }
- System.out.print(c);
- System.out.println("the a.txt is : "+String.valueOf(c));
- isr.close();
- }
- catch(IOException e){}
- }
- }
注意:由於經常需要將一個Reader或Writer同文件聯繫在一起,所以java專門提供了兩個類:FileReader和FileWriter。
構造函數:
- FileWriter(File file)
- FileWriter(String fileName)
- FileWriter(String fileName, boolean append) //append :是否可追加信息
通常
- FileWriter out=new FileWriter("a.txt");
等價於
- OutputStreamWriter out=new OutputStreamWriter(new FileOutputStream("a.txt"));
二、文本輸出
進行文本輸出時,通常需要使用PrintWriter,它可通過文本格式打印(顯示)字串及數字。提供了有用的輸出方法,但卻沒有定義目的地。因此,一個PrintWriter必須同一個目標writer合併到一起。
構造函數:
- PrintWriter(Writer out) //新建一個PrintWriter,不自動進行行清空(刷新);
- PrintWriter(Writer out,boolean autoFlush) //autoFlush爲true,則Println()方法會將輸出緩衝清空;
- PrintWriter(OutputStream out) //不自動進行行清空(刷新),構造函數自動增加一個OutputStreamWriter,
以便將Unicode字符轉換成流內的字節;
- PrintWriter(OutputStream out,boolean autoFlush) //autoFlush爲true,則Println()方法會將輸出緩衝清空;
代碼演示:
- PrintWriter out=new PrintWriter(new FileWriter("a.txt"));
- PrintWriter out=new PrintWriter(new FileOutputStream("a.txt"));
爲了向一個PrintWriter進行寫操作,需要使用隨System.out使用的相同的Print及Println方法。可用這些方法顯示數字(包括 int,short ,long ,float和double等等)、字符、布爾值、字串以及對象。據題參考API(println()可將目標系統正確的換行字符添加到當前行。通過調用System.getProperty("line.separator")獲取;如果將writer設爲"自動清空模式",那麼一旦調用println(),緩衝內的所有字符都會發生至它們的目的地,缺省時“自動清空”爲false)
演示程序
- //-----------------------------------------*向a.txt中寫文本信息*-----------------------------------
- import java.io.*;
- public class F
- {
- public static void main(String args[])
- {
- try{
- PrintWriter pw=new PrintWriter(new FileWriter("a.txt"));
- pw.print("Hello World");
- pw.close();
- /*InputStreamReader pr=new InputStreamReader(new FileInputStream("a.txt"));
- char[] c=new char[10];
- pr.read(c,0,10);
- System.out.println("message is"+String.valueOf(c));
- pr.close();*/
- }catch(IOException e){}
- }
- }
小結:
想以二進制格式寫入數據,使用DataOutputStream;
想用文本格式輸出 ,使用PrintWriter;
三、讀文本輸入
在java中,唯一用來處理文字輸入的是BufferedReader方法。該方法又含了另一個方法,名爲readLine(),可用它讀取整行文本。
我們需要將一個BufferReader同一個輸入源合併起來。
1、構造函數:
- BufferedReader(Reader in)
- BufferedReader(Reader in, int sz) //sz:輸入緩衝的大小
2、代碼演示:
- BufferedReader in=new BufferedReader(new FileReader("a.txt"));//FileReader將字節轉換成Unicode字符。
對於其他的輸入源,需要使用InputStreamReader ,它和PrintWriter(自動增加一個OutputStreamWriter,以便將Unicode字符轉換成流內的字節)不同,它不自動提供轉換方法彌補字節與Unicode字符間的裂縫;
- BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
3、演示程序:
- //------------------*從a.txt中讀出文本信息*-----------------------------------------------
- import java.io.*;
- public class F
- {
- public static void main(String args[])
- {
- try{
- BufferedReader br=new BufferedReader(new FileReader("a.txt"));
- String s;
- while((s=br.readLine())!=null)
- {
- System.out.println("the message is :"+s);
- }
- }catch(Exception e){}
- }
- }
- 參考程序
- //---------------*從a.txt中讀單個數字 *------------------------------------------------------
- import java.io.*;
- import java.util.*;
- import java.lang.*;
- public class G
- {
- public static void main(String args[])
- {
- try{
- BufferedReader br=new BufferedReader(new FileReader("a.txt"));
- String s=br.readLine();
- String name;
- while(s!=null)
- {
- StringTokenizer st=new StringTokenizer(s,"/ :");
- while(st.countTokens()!=0)
- {
- name=st.nextToken();
- System.out.println("name is : " +Integer.valueOf(name));}
- System.out.println("the message is :"+s);
- s=br.readLine();
- }
- }catch(Exception e){}
- }
- }
四、對象流
如果需要保存相同類型的數據,使用長度固定的記錄格式無疑是個很好的選擇,
在面向對象的程序中創建的對象極少出現類型相同的情況。 例如:一個Employee類型的數組,
實際可能包含了Employee的各個子類。 如果想保存包含了類對象信息的文件,首先必須保存每個對象類型,
然後用來定義對象當前狀態的數據。需要將這種信息從一個文件讀出的時候,必須:
1)讀取對象類型
2)創建那種類型的一個空白對象
3)在其中填充我們保存在文件中的數據
java採用一種對象序列化的機制,實現上述步驟,具體過程
保存到磁盤的所有對象都獲得一個序列號(1,2,3等等)
將一個對象存盤時,調查是否以保存了與之相同的對象。
如果以前保存過,只需寫入“與以前保存的對象有相同的序列號x”標記;否則,保存它的所有數據
要讀回數據時,將上述過程簡單的逆轉即可。對於載入的每個對象,都要注意它的序列號,
並記住將它存放在內存的什麼位置。如果遇到“與以前保存的對象有相同的序列號x”標記,
就根據序列號x,檢查對象放在什麼位置,並設置對象引用,令其指向那個內存地址。
一、對象流類分析
1、ObjectOutputStream
構造函數:
- ObjectOutputStream()
- ObjectOutputStream(OutputStream out) //創建一個ObjectOutputStream,以便將對象寫入指定的輸出流。
常用方法:
- void writeObject(Object obj) //將指定對象寫入對象輸出流。對象的類、類的簽名以及爲標記得臨時
的任何字段的值都會寫入,同時寫入的還包括它的所有超類的非靜態字段。
2、ObjectInputStream
構造函數:
- ObjectInputStream()
- ObjectInputStream(InputStream in) //創建一個ObjectInputStream,以便從指定的InputStream 中讀回對象信息。
常用方法: Object readObject() //從 ObjectInputStream中讀取一個對象。他會同時讀回對象的類;
類的簽名以及爲標記得臨時的任何字段的值都會寫入,同時寫入的還包括它的所有超類的非靜態字段。它會撤銷序列化,
以便恢復多個對象引用。
注意:1)、用writeObject和readObject方法只能讀寫“對象”,不能讀寫數字。如果想讀寫數字,
需要使用象writeInt/readInt等方法。java中,字串和數組均是對象,能使用對象流
2)、對象流中保存和恢復的任何一個類,必須是實現了Serializable(可序列化)接口的類。
3、代碼演示:
- ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("a.txt"));
- out.writerObject(new JFrame("the first Frame "));
- out.writerObject(new JLable(" Hello World !"));
- ObjectInputStream in=new ObjectInputStream(new FileInputStream("a.txt"));
- Object objects=in.readObject();//注意:在讀回對象時,必須對已保存對象的數量、
它們的順序以及它們的類型做到心中有數。對readObject()的每一次調用都會讀入類型爲Object的另一個對象。
因此需要將其造型爲恰當的類型。
4、程序演示:
- //--------------------------------* 將JFrame對象寫入a.txt,然後讀出*------------------------------
- import java.io.*;
- import javax.swing.*;
- public class H
- {
- public static void main(String args[])
- {
- try{
- ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("a.txt"));
- out.writeObject(new JFrame("the First JFrame"));
- out.close();
- ObjectInputStream in=new ObjectInputStream(new FileInputStream("a.txt"));
- JFrame f=(JFrame)in.readObject();
- System.out.println("The class is : "+f.getClass().getName());
- }catch(Exception e){}
- }
- }
小結:
二進制數據的讀寫
DataInputStream和DataOutputStream
可以完成對所有基本Java類型的讀寫;
FileInputStream和FileOutputStream
對一個磁盤文件涉及的數據流進行輸入輸出處理,是字節級的讀寫操作;
Java的流不具備預讀和緩衝功能,需要額外處理
BufferInputStram對流進行緩衝處理;
PushbackInputStream對流進行預讀處理;
寫文本輸出
二進制寫入數據:DataOutputStream;
文本格式寫入數據:PrintWriter;
讀文本輸入:
二進制讀入數據:DataInputStream;
文本格式讀入數據:BufferReader;