(7) - IO流 (圖)

---------------------- ASP.Net+Android+IO開發S.Net培訓、期待與您交流! ----------------------


一、IO流概述


IO流即輸入(Input)輸出(Output)流,流是種形象的說法,計算機數據(字節或字符)的轉移都是大量、持續的,像水流般。輸入指將制定位置的數據讀入內存中,輸出指將內存數據寫到指定的位置上。那麼IO流是用來處理設備間的數據傳輸的。

在java中:

(1)   對數據的操作是通過流的方式。

(2)   用於操作流的對象都在IO包中。

(3)   流按操作數據分爲兩種:字節流與字符流

(4)   流按流向分爲:輸入流和輸出流。

 

IO流操作的異常處理:

由於IO流存在各方面的不確定,需要異常處理機制的支持,在常規處理時都建議採用捕獲方式,下面IO流知識點的例子,爲了突出知識點,簡化了異常的處理,採用拋出方式,以便能使代碼清晰些,常規處理應採用下列格式:

/*
IO異常的處理方式
*/
import java.io.*;
 
class IoExceptionDemo
{
public static void main(String[] args)
{
        //在外面建立引用對象,在try中構造初始化
        //colse也要求處理IO異常,所以也的進行try、
        //即3個方法都要try處理
        //如構造時拋出異常,將中斷,但會執行finally中的
        //fw.close,因爲構造失敗fw=null,調用失敗
        //需在前進行健壯性的判斷,if(fw != null)
 
        FileWriter fw = null;
        try
        {
               fw =new FileWriter("Demo.txt");
 
               //write將內容寫入流緩存中,並未寫入文件中
               fw.write("寫入文本內容");
 
               //加flush刷新後,流中內容寫入文件中,但流並未關閉,可繼續寫
               fw.flush();
        }
        catch (IOException e)
        {
               System.out.println(e.toString());
        }
        finally
        {
               try
               {
                      //這步要有,安全判斷
                      //因fw =new FileWriter("Demo.txt");可能會失敗,比如在不存在的K盤下
                      if(fw != null)
                             fw.close();      //先刷新,後關閉流,不能再寫入數據了
               }
               catch (IOException e)
               {
                      System.out.println(e.toString());
               }
        }
        System.out.println("文件創建成功!");
}
}

二、字符流

       在進行數據操作時,操作數據以字符爲單元,該類體系爲Reader和Writer。

1、 Writer和Reader

Writer爲寫入字符流的抽象類,其子類後綴名基本是Writer。其子類必須要實現父類的抽象方法爲write(char[], int,int)、flush() 和 close()。當我們向文件write數據時,數據僅存在緩衝中,需用flush刷新後數據才從緩衝寫入文件中。Close之前會進行一次緩衝的刷新。其實jvm是在調用系統內部的讀寫方法,因爲系統會不同,jvm如都用自已的方式寫入數據是不合適的。

 

Reader爲讀入字符流的抽象類,其子類後綴名基本是Reader,必須要複寫的方法有read(char[],int, int) 和 close()。


2、 BufferedWriter和BufferedReader

爲了提高對數據的讀寫效率,IO中定義了緩衝區,可以說是一個過渡區,將陸續的數據先寫入緩衝區積攢,而後一股腦寫入目標設備。就像我們寫word,寫一段後保存,而不是寫一個字保存一下。所以不建議採用,讀一字符,寫一字符的方式,因爲這使磁頭讀寫操作頻繁,建議先讀一次性入流中,再將流數據寫入目的文件中。    最後關閉讀寫流

特點:

①緩衝區的技術原理其實就是封裝了數組。

②它的出現是爲了提高流的操作效率存在的,所以在創建時,構造函數必須傳入一個流對象。

③調用close關閉緩衝區時,就會關閉緩衝區的流對象,所以關閉了緩衝區就不需要寫流對象關閉了。

④BufferedWriter和BufferedReader的類構造採用了裝飾設計模式,對流的操作類進行了功能的加強,至於裝飾模式,將在以後介紹。

 

BufferedWriter:字符流輸出的緩衝區,提供了一個跨平臺的換行方法newLine(),解決在不同系統中換行符不統一的問題。在向目標輸出前記得先flush()刷新。

 

BufferedReader:字符流輸入的緩衝區,同樣提供了一個readLine方法,從文件中讀取一行數據,返回給String,當讀到文件尾時返回空,其本質還是用read一個一個字符讀取的,只不過積攢一行返回。該類有個LineNumberReader子類,比BufferedReader多出行號的追蹤功能。方法有setlineNumber();設置行號getReadLineNumber();讀出行號

 

3、 InputStreamReader和OutputStreamWriter

轉換流:由於某些需求,需要在字符流和字節流間互相轉換,java便將該操作封裝成類,以供需求,如他們的子類FileWriter和FileReader,我們直接可以用其子類進行字符的操作,而不必去考慮複雜的字節。

 

InputStreamReader爲字節流轉換成字符流,這樣便可利用字節流的BufferedReader緩衝區操作提高效率,要求在創建時向構造函數傳入InputStream的字節流對象,也可以用構造函數的Charset參數指定編碼格式。InputStreamReader構造函數中傳入System.in流參數,便可接手鍵盤錄入數據。

 

OutputStreamWriter字符流轉換成字節流,同理,只不過參數輸入改輸出,如接收System.out打印在控制檯。

 

4、 FileWriter與FileReader

他們分別屬於InputStreamReader和OutputStreamWriter的子類,專門對字符數據的文件進行操作。可接收路徑字串和File對象等。

 

FileWriter:用來寫入字符文件的便捷類,有默認的字符編碼和緩衝區。可進行對文本的寫入和文本的追加,追加文本的構造函數只要將參數2用上,初始成true即可,寫入前需刷新。創建對象時,該類會打開指定文件,若文件已打開則創建失敗。

 

FileReader:用於讀取字符文件的便捷類,同樣具有默認編碼和緩衝區。由read()讀取單個字符,返回一個整數,讀到文件尾返回-1。

 

IO流操作會拋出異常,需處理,以應對文件已打開或文件不存在等異常,異常處理稍後介紹。下面是1、2、3的一個綜合例子:將鍵盤錄入,寫入文件中。

                     

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
 
/*
 * 需求:將鍵盤錄入寫入文件中
 *
 * 說明這裏不重點體現異常處理,所以簡單的拋出了
 * 正規開發,建議用try進行捕獲
 * */
public class CopyText
{
                     //由於異常處理不是這裏要體現的,所以採取拋出異常,正常編碼建議捕捉
                     public static voidmain(String[] args) throws IOException
                     {
                            //標準的鍵盤錄入方式,將輸入轉字符交給緩衝區。
                            BufferedReader bufr= new BufferedReader(new InputStreamReader(System.in));
                           
                            //FileWriter讀取字符文件,交給緩衝,會拋出異常,簡單拋出了,平時建議捕捉
                            BufferedWriter bufw= new BufferedWriter(new FileWriter("Test.txt"));
                           
                            String strLine =null;
                            while((strLine =(bufr.readLine()))!=null)
                            {
                                   //輸入over時,結束
                                   if("over".equals(strLine))
                                          break;
                                   //寫入文本
                                   bufw.write(strLine);
                                   bufw.newLine();//換行
                                   bufw.flush();//刷新
                            }
                            bufr.close();
                            bufw.close();
                     }
}

5、 CharArrayReader和CharArrayWriter

提供在緩衝區和內存間的操作方式,用於操作字符數組,內部擁有可自動延長的內存空間,由於是操作內存,最後不需關閉。

CharArrayReader,需接收一個字符數組型的數據源,將內存的數據以數組的方式存入傳入的數組源中,無需關閉。

CharArrayWriter,同理。


6、 PrintWriter

向文本輸出流打印對象的格式化表示形式,爲其他字符流類添加了輸出功能,在構建時,將構造函數參數2置true,還可帶自動刷新功能,利用println()能實現換行,等於代替了上述代碼中的 bufw.write(strLine);bufw.newLine();bufw.flush();三句。

構造函數能接收的文件源可以是,File對象,字符串路徑,字節輸出流。

 

7、 PipedWriter和PipedReader

管道流,它具有將一個程序的輸出當做另一個程序的輸入的能力,這個涉及線程,也就是我們線程通信的另一種方式,利用管道流通信,它需要利用connect將兩者連接,即:讀.connect(寫)。

import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
 
/*
 * 字符管道流的測試*/
class Read implements Runnable
{
private PipedReader pr;
Read(PipedReader pr)
{
        this.pr = pr;
}
public void run()
{
        try
        {
               //構建接收數組
               char[] chs = new char[1024];
               //讀取連接的寫入管道流,多句用循環
               int len = pr.read(chs);
               System.out.println(newString(chs,0,len));
               pr.close();
        }catch(Exception e)
        {
               throw new RuntimeException("讀取失敗!");
        }
}
}
class Write implements Runnable
{
private PipedWriter pw;
Write(PipedWriter pw)
{
        this.pw = pw;
}
public void run()
{
        try
        {
               //直接寫出
               pw.write("這是管道流數據!");
              //多句要pw.flush()刷新,這裏再close前自動刷新了                    
              pw.close();
        }catch(Exception e)
        {
               throw new RuntimeException("寫入失敗!");
        }
}
}
public class PipedDemo
{
public static void main(String[] args) throws IOException
{
      PipedWriter pw = new PipedWriter();
      PipedReader pr = new PipedReader();
     //需連接,讀的連接寫的
     pr.connect(pw);
     new Thread(new Read(pr)).start();
     new Thread(new Write(pw)).start();
}
}


8、RandomAccessFile

該類不算是IO體系中子類,而是直接繼承自Object。隨機訪問文件,自身具備讀寫方法。通過skipBytes(int x),seek(int x)來達到隨機訪問,即含有文件指針,可跳躍。

但是它是java.IO包中成員,因爲它具備讀和寫功能,

它內部封裝了一個數組,而且通過指針對數組的元素進行操作。可以通過getFilePointer獲取指針位置。同時可以通過seek改變指針的位置。

其實完成讀寫的原理就是內部封裝了字節輸入流和輸出流。

其構造函數只能接收文件對象,而且可以指定模式,如’r’,只讀。

 

侷限性:通過構造函數可已看出,該類只能操作文件。而且操作文件要用模式。

      

三、字節流

在進行數據操作時,操作數據以字節爲單元,該類體系爲InputStream和OutputStream,字節流的輸出是直接從內存寫入,不需要轉字符的過程,因此沒有字符流刷新的過程。


1、InputStream和OutputStream

InputStream爲寫出字節流的抽象類,其子類後綴名基本是InputStream。提供read()方法。

OutputStream爲讀入字節流的抽象類,其子類後綴名基本爲OutputStream。提供write()方法。


2、FileOutputStream和FileInputStream

分別爲InputStream和OutputStream的子類,複寫了read()方法,主要用於讀取諸如圖像數據之類的原始字節流。沒有緩衝區,自己構建byte類型的數組。

FileOutputStream:以字節流的形式輸出到文件中,複寫了write方法,一次寫入一個字節,若要已向文件結尾處追加字節,在創建時的構造函數中初始第二個參數爲true。

 

FileInputStream:將字節流讀入到內存中,複寫了read方法,每次只能讀一個字節,返回一個int型數據,當讀到文件尾時返回-1。他有個特有的方法available(),返回文件剩餘的字節數。如果文件較小,可以先讀出大小,聲明足夠的byte數組,不用循環,但是文件不能過大,虛擬機不可能開闢很大的內存空間,比如視頻文件,new一個數組不會成功,所以應視情況使用available()。

             

其格式類似與FileWriter與FileReader,在這裏不寫例子了,稍作改動即可,將字符讀入改爲字節流的byte,不用刷新等。


3、ByteArrayOutputStream與ByteArrayInputStream

       與字符流的CharArrayReader和CharArrayWriter同理,只不過數據源爲byte數組。


4、PipedOutputStream與PipedInputStream

與字符流的PipedReader和PipedWriter同理,只不過一切按字節流的操作方式。


5、ObjectInputStream與ObjectOutputStream

對象序列化流,用於對象的序列化(持久化),即能將對象存入硬盤中,保存對象和對象數據。由ObjectOutputStream將java對象的基本數據類型和圖形寫入OutputStream(流)中,使用ObjectOutputStream可讀取重構該對象。被操作的對象所屬類必須實現Serializable接口,該接口沒有要複寫的方法,我們叫這種類型的接口爲標記接口。實現該接口,其實就是給每給要保存的類一個ID號,簡稱UID,若要保存對象的類不實現該接口使用對象序列化流,將會報NotserializableException異常。

默認對象序列化後,不能修改其類,因爲序列化文件中記錄的UID變化了,使用時或報錯,爲防止修改類時,保存文件變化UID而導致不可用,我們採用自定義UID,

如public static final long srrialVersionUID = 42L;

靜態成員不會被序列化,他再方法區,而對象在堆中。若不想某成員序列化,加tansient修飾該成員。

這兩個類必須成對使用,用ObjectOutputStream寫,就用ObjectInputStream。

如下例:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ObjectStreamDemo
{
  public static void main(String[] args) throws Exception
  {
         writeObject();
         readObject();
  }
 
  public static void writeObject() throwsException
  {
         //這裏爲簡化代碼,異常省略捕捉處理
         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
         //寫入一個Person對象
         oos.writeObject(new Person("張三"));
         oos.close();
  }
  public static void readObject() throws Exception
  {
         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
         //從文件中讀取序列化的對象
         Person p = (Person)ois.readObject();
         System.out.println(p);
         ois.close();
  }
}
//要序列化的對象類,必須向實現Serializable
classPerson  implements Serializable
{
  //自定義一個標記號,防止修改二影響序列化的對象不可用。
  public static final long serialVersionUID =42L;
  private String name=null;
  Person(String name)
  {
         this.name = name;
  }
  public String toString()
  {
         return "姓名:" + name;
  }
}

6、SequenceInputStream

     SequenceInputStream對多個流進行合併。從第一個流,讀完,再接着讀取第二流,依次下去。可以將多個流的操作數據寫入到一個目標中。比如將三個文件的數據寫入到一個文件中。構造函數中傳入Enumeration對象,由Enumeration保存多個流對象。Enumeration時舊版本中Vector的類型。

     先準備三個文件1.txt,2.txt,3.txt,將內容合併到4.txt中如下例子:

    

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;
 
public class SequenceInputStreamDemo
{
     //這裏爲代碼簡練省略捕捉處理異常
     public static void main(String[] args)throws IOException
     {
       //由於SequenceInputStream的構造函數要傳Enumeration類型,這裏用Vector
       Vector<FileInputStream> vct = new Vector<FileInputStream>();
      
       //準備合併的三個文件
       vct.add(new FileInputStream("1.txt"));
       vct.add(new FileInputStream("2.txt"));
       vct.add(new FileInputStream("3.txt"));
      
       //獲取三個文件的流對象
       Enumeration<FileInputStream> en =vct.elements();
       //合併
       SequenceInputStream sis = new SequenceInputStream(en);
      
       //合併後寫入另一個文件
       FileOutputStream fos = new FileOutputStream("4.txt");
      
       byte[] buf = new byte[1024];
      
       int len = 0;
       while((len = sis.read(buf))!=-1)
       {
              fos.write(buf,0,len);
       }
       fos.close();
       sis.close();
     }
}

 

切割文件:

利用自定義byte[]數組限制讀入大小,將文件分批存入多個新建的文件中。

 但是在切割較大的視頻的文件時,就不能new過大的byte數組了,

應定義1024*1024(1M)大小的數組向一文件存入數據,再採用一計數器記錄裝入了多少數據,達到要求時,再新建一文件,繼續裝入下一個。無太多技術要求,純算法,這裏不再舉例。

 

8、 FilterInputStream與FilterInputStream的子類

(1)   BufferedInputStream與BufferedInputStream

功能與字符流的BufferedReader與BufferedWriter緩衝區類似,早構造時可以指定緩衝大小

下面寫一個拷貝圖片的例子:

import java.io.*;
 
public class CopyPicDemo
{
//簡化代碼,沒做異常的捕捉
public static void main(String[] args) throws IOException
{
        //字節流緩衝區
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("2.jpg"));
       
        int by = 0;;
        while((by = bis.read())!=-1)
        {
               bos.write(by);
        }
        bis.close();
        bos.close();
}
}


(2)   DataOutputStream與DataIntputStream

可以對基本數據類型輸入輸出,構造時需傳入流對象,DataOutputStream使用writeInt(int)、writeBoolen(bool)等方法向文本寫入基本數據類型,用DataIntputStream的readInt()、readBoolean()等方法讀出,有嚴格的字節順序,讀錯順序後面的數據將不能正常讀出。

(3)   PrintStream

類似於字符流的PrintWriter類。它爲其他輸出流添加了功能,使它們能夠方便地打印各種數據值表示形式。與其他輸出流不同,PrintStream 永遠不會拋出IOException;而是,異常情況僅設置可通過checkError 方法測試的內部標誌,通過checkError()方法獲得。


---------------------- ASP.Net+Android+IO開發S.Net培訓、期待與您交流! ----------------------

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章