這一章我們討論Java程序的輸入與輸出。Java在I/O方面提供了眾多支持,使我們的工作得到大大的簡化。我們將學習利用這些支持以完成各種復雜的輸入、輸出。

7.1 理解java.io的類繼承關系

  首先,讓我們考察Java提供的常用輸出輸出流類(圖7.1)。由于類的數(shù)目較多,沒有列出1.1版本中新增的字符流類。在圖7.2中,我們把字符流類與字節(jié)流類作了對比,在該圖中可以看到字符流類的繼承關系。接口和異常類也被省略了。
                        ┌BufferedInputStream
                        ├DataInputStream
              ┌FilterInputStream┼LineNumberInputStream
              ├FileInputStream └PushbackInputStream
              ├ByteArrayInputStream
     ┌InputStream──┼PipedInputStream
     │        ├SequenceInputStream
     │        ├StringBufferInputStream
     │        └ObjectInputStream ┌BufferedOutputStream
     │         ┌FilterOutputStream┼DataOutputStream
  Object┤         ├FileOutputStream └PrintStream
     ├OutputStream──┼ByteArrayOutputStream
     ├File       ├PipedOutputStream
     ├FileDescriptor └ObjectOutputStream
     ├ObjdecStreamClass
     ├RandomAccessFile
     └StreamTokenizer
    圖7.1 java.io包中常用類層次圖(不含字符流類)
  圖7.1中包含了許多的輸入和輸出類(這還不包括我們歡天喜地上要講到的字符流輸入輸出類)。為了能正確運用它們,我們必須對它們的功能和關系有個大根式的認識。

  7.1.1 字節(jié)流與字符流

  第二章中提到了Unicode字符集和ASCII字符集。前者用16位來表示一個字符,而者用8位來表示一個字符。Unicode字符集可表示的符號顯然比ASCII字符集多得多,它可以表示世界上大多數(shù)語言的符號。
  在JDK1.0x版本中,只提供了字節(jié)流輸入輸出類。也就是說,輸入輸出的數(shù)據(jù)以字節(jié)為讀寫單位。這就給操作一些雙字節(jié)字符帶來了困難。比如漢字,用一個字節(jié)是不能表示,這就使Java程序的漢化成了問題。例如,用1.0x版的JDK開發(fā)一個文本編輯器,就可能出現(xiàn)這樣的情況:用剪貼板可以把漢字貼進文本域卻無法用鍵盤向文本域輸入漢字字符。這就是標準輸入流每次只接收了一個漢字的第一字節(jié)引起的。
  JDK1.1版對輸入輸出作了改進,為字節(jié)流輸入輸出類增加了對應的字符流輸入輸出類這樣,程序員就可以根據(jù)實際情況選用合適的類。
  字符流I/O有其顯示而易見的好處。首先它可以適用于世界上大部分語言,從而為Java程序的本地化帶來方便。其次,一次讀一個字符(16位)比讀一個字節(jié)來得快,一般情況下可以彌補將數(shù)據(jù)按當前語言標準編碼、解碼的時間開銷。
  字節(jié)流I/O類和字符流I/O類的命名有其對應關系。字節(jié)輸入流類的名字以“InputStream”結(jié)尾。而字符輸入流類的名字以“Reader” 結(jié)尾。字節(jié)輸出流類的名字后綴為“OutputStream”,而字符輸出流類的名字后綴為“Writer”。
  為了在適當?shù)臅r候能把這兩種流類聯(lián)系起來,API中設置了兩個類,充當二者的橋梁。InputStreamReader根據(jù)特定的編碼規(guī)則從字節(jié)流創(chuàng)建相應的字符流,而Output。StreamWriter則根據(jù)編碼規(guī)則從字符流讀取字符,把它們轉(zhuǎn)化為字節(jié),寫入字節(jié)流中。
  下面列出兩種流類的對應關系(圖7.2)。其中,左邊一欄是按繼承關系排列的字符流類,右邊是對應的字節(jié)流類。
  Reader            InputStream
   ├BufferedReader      BufferedInputStream
   │  └LineNumberReader  LineNumberReader
   ├CharArrayReader     ByteArrayInputStream
   ├InputStreamReader     (none)
   │  └FileReader     FileInputStream
   ├FilterReader       FilterInputStream
   │  └PushbackReader   PushbackInputStream
   ├PipedReader        PipedInputStream
   └StringReader       StringBufferInputStream

  Write             OutputStream
  ├BufferedWriter       BufferedOutputStream
  ├CharArrayWriter       ByteArrayOutputStream
  ├OutputStreamWriter     (none)
  │  └FileWriter       FileOutputStream
  ├FilterWriter         FilterOutputStream
  ├PrintWriter         PrintStream
  ├PipedWriter         PipedOutputStream
  └StringWriter         (none)
    圖7.2字符流類與字節(jié)流類的對應關系

  另外,1.1版的API中,對一些1.0x版本中已存在的類也進行了微小的修改,這主要是因為有類對字節(jié)和字符的轉(zhuǎn)換可能產(chǎn)生錯誤。如以下構造函數(shù)和方法標記為過時:
  Sting  DataInputStream.readLine()
  InputStream  Runtime.getLocalizedInputStream(InputStream)
  OutputStream Runtime.getLocalizedOutputStream(OutputStream)
         StreamTokenizer(InputStream)
         String(byte ascii[],int hibyte,int offset,int count)
         String(byte ascii[],int hibyte)
    void    String.getBytes(int srcBegin,int srcEnd,byte dst[],int dstBegin)
  另外,添加了如下構造函數(shù)和方法:
         StreamTokenizer(Reader)
  byte[]     String.getBytes()
  void     Throwable.printStackTrace(PrintWriter)
  當程序員使用舊的API編程時,可以用
  javac -deprecation(文件名)
  來進行編譯,這樣編譯器會給出較為詳細的警告信息。編程人員可根據(jù)這些信息查找新文檔,以獲知新版本中的替代方法。
  本章的例子都是依據(jù)1.1版本的API編寫的。

  7.1.2 輸入輸出類的分類

  java.io包中的類各有各的分工,粗略說來可以分為以下幾類:
  文件I/O:有三類。對字節(jié)流類來說,包括把文件作為源進行流式輸入的FileInputStream類;把文件作為目的進行流式輸出的 FileOutputStream類;若你想隨機存取文件,即在文件的任意位置讀、數(shù)據(jù),那么可以使用RandomAccessFile類。字符類則有 FileReader和FileWriter類。它們的功能對應于前兩個字節(jié)流類。
  除此之外,還有兩個類是與文件訪問有關的,確切地說其功能更近于文件管理。它們是File類,用以訪問文件或目錄;FileDescriptor則封裝了操作系統(tǒng)用以追蹤被訪問文件的信息。
  內(nèi)存緩沖區(qū)I/O:字節(jié)流類有ByteArrayInputStream類,將字節(jié)數(shù)組轉(zhuǎn)化為輸入流,是從一個字符串創(chuàng)建輸入流,與 ByteArrayInputStream異曲同工,幫也歸入此類別。相應地,字符流類有CharArrayReader, CharArrayWriter,StringReader,此外還多一個StringWriter用來寫字符串。
  余下一些類可以不同方式存取流中的數(shù)據(jù)。字節(jié)流類中,DataInputStream和DataOutputStream因其能對流中的不同類的對象分別操作而顯得與眾不同;ObjectInputStream和ObjectOutputStream能把若干完整的對象按選定的格式進行讀寫,但要求被操作對象實現(xiàn)Serializable接口;BufferedInputStream和BufferedOutputStream可以對流數(shù)據(jù)進行緩沖,實現(xiàn)類似“預輸入”、“緩輸出”的功能;LineNumberInputStream跟蹤輸入流中的行數(shù);PusthbackInputStream提供了一個“可推回”的流,從這個流中讀了數(shù)據(jù)后,還可以將它放回流中;PrintStream類提供了許多重載的方法以簡化輸出。對應的字符流類可以從 7.1.1節(jié)的對應關系中查出。
  除了上述類以外,Java還有種特殊的I/O類——管道I/O類。它們是專門為線程通訊預備的。管道提供了自動同步機制,可以防止線程通訊中的數(shù)據(jù)混亂。
  至引相信讀者已對各個I/O類的功能有所了解。這里再解釋一下過濾器I/O 推廣java.io包中有不少類是過濾器類,它們都是從FilterInputStream或FilterOutputStream之中派生而來(參見圖 7.1)。在字符流中,也有類似的類,但并不像字節(jié)流類一樣必然從某個公共的過濾器父類派生而來。
  過濾器(Filter)形成的類對象從一個流中讀入數(shù)據(jù),寫入另一個,就像一個流經(jīng)過過濾產(chǎn)生另一個流一樣。過濾器可以聯(lián)合使用,也就是說“過濾”過的流可以再經(jīng)其它過濾器“過濾”,過濾器型類的共性是:
  (1)用和種流為參數(shù)的構造,且輸入型過濾器用輸入流,輸出型過濾器用輸出流;
  (2)無明顯的源/目的限制;
  (3)流中數(shù)據(jù)的內(nèi)容“多少”并未改變,只可能性質(zhì)略有變化。
讀者不妨以這幾條標準去理解過濾器I/O類與其子類,并在以后的示例中加以驗證。

7.2 輸入流與輸出流

  字節(jié)輸入流InputStream與字節(jié)輸出流OUtputStream是兩個抽象類。它們?yōu)閖ava.io包中名目繁多的字節(jié)輸入和輸出流打下了基礎。由于是抽象類,它們不能被實例化(也就是說,不能得到其對象),但它們的方法可以被派生類所繼承或重寫。
  對于字符流,相應的流類是Reader和Writer。由于它們的方法與InputStream和OutputStream對應,只是把對字節(jié)的操作改為對字符的操作,這里不再重復介紹。但為了讀者能夠?qū)λ鼈兊膶P系有個基本認識,在本節(jié)末尾附上Reader類的方法列表,請讀者參照。
  InputStream的方示如下:
  ■public abstract int read() throws IOException
  ■public int read(byte b[]) throws IOException
  ■public int read(byte b[],int offset,int length) throws IOException
  功能為從輸入流中讀數(shù)據(jù)。這一方法有幾種重載形式,可以讀一個字節(jié)或一組字節(jié)。當遇到文件尾時,返回-1。最后一種形式中的offset是指把結(jié)果放在b[]中從第offset個字節(jié)開始的空間,length為長度。
  ■public int available() throws IOException
  輸入流共有多少字節(jié)可讀。注意此方法對InputStream的各派生類不一定都有效,有時會有返回零字節(jié)的錯誤結(jié)果。
  ■public void close() throws IOException
  關閉輸入流并釋放資源。
  ■public boolean markSupperted()
  返回布爾值,說明此流能否做標記。
  ■public synchronized void mark(int readlimit)
  為當前流做標記。其參數(shù)說明在標記失效前可以讀多少字節(jié),這個值通常也就設定了流的緩沖區(qū)大小。
  ■public synchronized void reset() throws IOException
  返回到上一次做標記處。
  ■public long skip (long n) throws IOEnception
從輸入流跳過幾個字節(jié)。返回值為實際跳過的字節(jié)數(shù)。
  對于“mark”我們還需解釋一下。輸入流提供“標記”這一機制,使人們可以記錄流中某些特定的位置,并能重復讀部分內(nèi)容。支持“mark”就必須要求當前流有一定大小的緩沖區(qū),存放部分數(shù)據(jù),即從標記點到當前位置的數(shù)據(jù)。當這一緩沖區(qū)裝滿溢出,我們就無法追蹤到上一個標記處的數(shù)據(jù)了,這就稱之為“標記失效”。若想用reset()返回到一個失效的標記處,將會發(fā)生輸入輸出異常(IOException)。
  OutputStream的方法如下。各方法均可能拋出輸入輸出異常(throws IOException)。
  ■public abstract void write(int b)
  ■public void write(byte b[])
  ■public void write(byte b[],int offset,int length)
  這三個重載形式都是用來向輸出流寫數(shù)據(jù)的。具體每個不甘落后 作用,讀者可根據(jù)前文read()方法對照之。
  ■public void flush()
  清除緩沖區(qū),將緩沖區(qū)內(nèi)尚未寫出的數(shù)據(jù)全部輸出。若要繼承OutputStream類,這個方法必須重寫,因為OutputStream中的方法未做任何實物性工作。
  ■public void close()
  關閉輸出流,釋放資源。
  以上提到的這些方法,在下面的章節(jié)中將有不少被運用,讀者可根據(jù)實例領會它們。
  附Reader類的方法列表。
  構造函數(shù):
  ■protected Reader() 
  ■protected Reader(object lock)
  方法:
  ■public int read() throws IOException
  ■public int read(char cbuf[]) throws IOException
  ■public abstract int read(char cbuf[],int off,int len)throws IOException
  ■public long skip(long n) throws IOException
  ■public boolean ready() throws IOException //判斷流是不可以讀
  ■public boolean mark(int readAheadLimit)throws IOException
  ■public void reset() throws IOException
  ■public abstract void close() throws IOException

7.3 文件I/O

  這一節(jié)中我們將結(jié)合實例討論File,F(xiàn)ileInputStream,F(xiàn)ileOutputStream,F(xiàn)ileDescriptor和RandomAccessFile類的方法與使用。

  7.3.1 一個文件I/O實例

  讓我們用一個例子來演示對文件的輸入輸出(例7.1)。圖7.3中列出了這個例子的運行結(jié)果。
  例7.1 fileIODemo.java。
  1:import java.io.*;
  2:import java.lang.*;
  3:
  4: public class fileIODemo{
  5:  public static void main(String args[]){
  6:   try{
     //創(chuàng)建輸入輸出流
  7:   FileInputStream inStream = new FileInputStream("text.src");
  8:   FileOutputStream outStream = new FileOutputStream("text.des");
      //讀文并寫入輸出流
  9:     boolean eof = false;
  10:    while(!eof){
  11:     int c = inStream.read();
  12:     if(c==-1) eof = true;
  13:     outStream.write((char)c);
  14:    }
  15:    inStream.close();
  16:    outStream.close();
  17:   }catch(FileNotFoundException ex){
  18:    System.out.println("Error finding the files");
  19:   }catch(IOException ex){
  20:   System.out.println("IOException occured.");
  21:  }
    //獲取文件管理信息
  22:  File file = new File("text.des");
  23:  System.out.println("Parent Directory:"+file.getParent());
  24:  System.out.println("Path:"+file.getPath());
  25:  System.out.println("File Name:"+file.getName());
  26:  try{
     //創(chuàng)建RandomAccessFile對象,以便隨機讀寫。"rw"代表可讀可寫
  27:   RandomAccessFile rafile = new RandomAccessFile("text.des","rw");
     //指針置到文件頭
  28:   rafile.seek(0);
  29:   boolean eof=false;
  30:   System.out.println("The content from very head:");
     //讀文件
  31:   while(!eof){
  32:   int c = rafile.read();
  33:    if(c==-1) eof = true;
  34:    else System.out.print((char)c);
  35:   }
     //下兩行把讀指針置到第三字節(jié)
  36:   rafile.seek(0);
  37:   rafile.skipBytes(3);
  38:   System.out.println("\nThe pointer's position:"+rafile.getFilePointer());
  39:   System.out.println("The content from current position:");
  40:   eof=false;
  41:   while(!eof){
  42:    int c=rafile.read();
  43:    if(c==-1) eof=true;
  44:    else System.out.print((char)c);
  45:   }
     //強制輸出緩沖區(qū)中所有內(nèi)容
  46:   System.out.flush();
  47:   rafile.close();
  48:  }catch(IOException ex){
  49:   System.out.println("RandomAccessFile cause IOException!");
  50:  }
  51: }
  52:}
  例7.1的運行結(jié)果如下:
  (略)
  為了充分展示與文件I/O相關的類的作用,我們的例子中有一些冗余的東西。我們的這個程序位于C:\BookDemo\ch07路徑下(見例7.1行 7),此路徑又有一個子上當text,其中有文件text.src。運行此程序,將在C:\bookDemo\ch07下創(chuàng)建一個新文件 text.des,text.src的內(nèi)容被寫信此文件。下面的段對File類的演示說明了文件的部分管理信息。然后我們又使用了 RandomAccessFile,試驗了文件在指定位置的讀寫。
  第46行的Sytem.out.flush()語句不可以被省略,讀者不妨去掉它試一試。你會發(fā)現(xiàn),有一部分輸出信息不知道到哪兒去了。實際上,flush()的作用就是把緩沖區(qū)中的數(shù)據(jù)全部輸出,我們棣輸出流輸出以后,某些輸出流(有緩沖區(qū)的流)只是把數(shù)據(jù)寫進了緩沖區(qū)而已,不會馬上寫到我們要求的目的地。如果不像例子中一樣強制輸出,部分數(shù)據(jù)可以就來不及在程序結(jié)束前輸出了。
  細心的讀者或許要問:為什么第一次用ReadomAccessFile讀文件時,輸出語句后面沒有flush()呢?豈非自相矛盾嗎?原來, System.out是PrintStream類的對象(關于PrintStream后有緩沖區(qū)中的內(nèi)容清除出去。因此許多地方就不必加flush() 了。PrintStream的這個特點,在創(chuàng)建其對象時是可以去掉(disable)的。
  這個程序中用到了IOException和FileNotFoundException兩個異常。后者是從前者派生出來的,因此,如果去年程序中的所有try、catch,而在main()方法開頭加上throws IOException,哪樣可以。但這樣不好區(qū)分各種不同的異常情況,即使找不到我們需要的text.src文件,也不會有任何信息顯示。這無疑是一種不良的編程風格。因此我們提倡對各個異常分別處理,這樣對出錯情況可以很地掌握。

  7.3.2 文件輸入輸出的類庫支持

  下面我們逐一介紹例7.1中用到的各個類。
  1.File類
  File類的構造函數(shù)有三個。分別根據(jù)文件名、文件路徑與文件名、文件對象(目錄)與文件名創(chuàng)建實例。即:
  ■public File(String path)
  ■public File(String path,String name)
  ■public File(File dir,String name)
  除了例子中用到的以外,還有許多方法,下面僅列出較常用的:
  ■public boolean exists()判斷文件是否存在
  ■public boolean canRead()判斷文件是否可讀
  ■public long length()返回文件長度
  ■public boolean mkdir()創(chuàng)建目錄
  ■public boolean renameTo(File dest)文件改名
其中,后三個方法可能拋出I/O異常。
  2.FileInputStream類
  它是文件輸入流類
  構造函數(shù)有三個:
  ■public FileInputStream(String fileName) throws FileNotFoundException
  ■public FileInputStream(File file) throws FileNotFoundException
  ■public FileInputStream(int fd) throws FileNotFoundException
  三個構造函數(shù)分別根據(jù)文件名、文件對象、文件描述符創(chuàng)建一個文件輸入流。例子中用的是第一種。
  方法:
  read()、skip()、available() 、close()分別重寫了抽象類InputStream的同名方法,功能如前所述。此外還有:
  ■public final int getFD()
  返回相應的文件描述符。
  ■protedted void finalize() throws IOException
  關閉輸入流,并收集無用內(nèi)存空間。
  現(xiàn)在我們必須介紹一下文件描述符類FileDescriptor。這個類用于訪問操作系統(tǒng)維護的文件描述符(也稱句柄)。但這個類產(chǎn)不能訪問很多信息。它只提供了兩個方法,即valid(),以判斷文件描述符是否有效;sync(),用以同步系統(tǒng)緩沖區(qū)。
  3.FileOutputStream類
  文件輸出流。三個構造函數(shù),其參數(shù)、返回值及異常均與FileInputStream的相對應。write()、close()方法重寫了 OutputStream的同名方法。getFD()與finalize()功能與InputStream的類似。
  4.ReadomAccessFile類
  該類用于隨機訪問文件。
  構造函數(shù)有三種:
  ■public RandomAccessFile(String Filename,String mode) throws IOException
  ■public RandomAccessFile(int FD) throws IOException
  ■public RandomAccessFile(File file,String mode)throws IOException
  由上可見,我們可以用文件名加讀寫方式、文件描述符、File對象加讀寫方式來創(chuàng)建其對象。其中讀寫方式用“r”表示只讀,“rw”表示可讀寫,等等。用過C語言的讀者對此應當不會陌生。
  此類的成員方法很多。除了重寫InputStream的read()方法之外,還可以讀、寫一個布爾值、一個字節(jié)、一個整數(shù)......等對象。這些方法都不可重寫,并且拋出I/O異常(IOException)。訊方法名為“read”加類型名(類型名的第一字母大寫),寫方法名為“write”加類型名。如
  readInt()讀一個整型數(shù)
  writeDouble()寫一個雙精度浮點數(shù)
等。另外還有文件指針的操作,如skipBytes(int n)等。
  有了以上這些類的支持,處理文件輸入輸出和維護文件就容易多了。

7.4 內(nèi)存緩沖區(qū)

  內(nèi)存緩沖區(qū)I/O,對字節(jié)流來說指的是ByteArrayInputStream和ByteArrayOutputStream類的運用。此外, StringBufferInputStream與ByteArrayInputStream用法相似將一并介紹。對字符流不另舉例,它們使用與字節(jié)流類類似。

  7.4.1 程序示例

  同上一節(jié)一樣,我們還是先看一個例子(例7.2)
  例7.2 ByteArrayIODemo.java
  1:import java.io.*;
  2:
  3: public class ByteArrayIODemo{
  4:  public static void main(String args[]) throws IOException{
      String s ="This a test";
  5:   byte buffer[]=s.getBytes();
  6:   ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
  7:  for(int i=0;i<buffer.length;++i)
  8:    byteArrayOut.write(buffer[i]);
  //由字節(jié)數(shù)組創(chuàng)建字節(jié)輸入流
  9:   ByteArrayInputStream inStream = new
       ByteArrayInputStream(byteArrayOut.toByteArray());
  //讀寫
  10:  boolean eof=false;
  11:  while(!eof){
  12:   int c=inStream.read();
  13:   if(c==-1) eof=true;
  14:    else System.out.print((char)c);
  15:  }
  16:  System.out.println("\nThe'writeTo' method can produce same results.");
  //用ByteArrayOutputStream的writeTo()方法寫
  17:  byteArrayOut.writeTo(System.out);
  18:  byteArrayOut.close();
  //available()與reset()的使用
  19:  System.out.println("\nThe buf of inStream has the length(before
    seset):"+inStream.available());
  20:  inStream.reset();
  21:  System.out.println("\nThe buf of inStream has the length:"+inStream.available());
  22:  inStream.close();
  23: }
  24:}
  該程序的運行結(jié)果:(略)
  這個例子看來相對簡單些。我們先把字節(jié)數(shù)組的內(nèi)容寫進一個字節(jié)數(shù)組輸出流對象。然后,用一個字節(jié)數(shù)組輸入流對象讀數(shù)據(jù),再用System.out輸出。程序顯示了writeTo()方法的作用。另外,我們還在reset()前、后用了兩疚available()方法,請注意兩方法先后產(chǎn)生的不同結(jié)果。這個例子主要用來演示字節(jié)數(shù)組I/O的部分方法。

  7.4.2緩沖區(qū)I/O的類庫支持

  看過了例子,我們接下來介紹有關的類。
  1.ByteArrayInputStream類
  這個類用于從一個
  字節(jié)數(shù)組取得輸入數(shù)據(jù)。
  它有兩個構造函數(shù):
  ■public ByteArrayInputStream(byte Buf[])
  由字節(jié)數(shù)組創(chuàng)建相應的輸入流。
  ■public ByteArrayInputStream(byte buf[],int offset,int length)
  由字節(jié)數(shù)組中起點為offset長為length的一段創(chuàng)建輸入流。
  成員變量:
  protected byte buf[]數(shù)據(jù)緩沖區(qū)
  protected int pos 緩沖區(qū)中當前位置
  protected int count緩沖區(qū)中字節(jié)數(shù)目
  該類的成員方法都是同步(synchronized)的。
  ■public synchronized int read()
  讀一個字節(jié)。
  ■public synchronized int read(byte b[],int offset,intrlength)
  讀取多個字節(jié),返值一般為讀到的字節(jié)數(shù)。但讀到末尾時返回-1。
  ■public synchronized long skip(long n)
  跳過n個字節(jié)。若返回值不等于n,可能是遇到末尾。
  ■public synchronized int available()
  求得緩沖區(qū)內(nèi)字節(jié)數(shù)目。
  ■public synchronized void reset()
  該指針重新設置到輸入流的開始處。注意,這個reset()與InputStream中的功能不同。它并不作用于標記。
  2.ByteArrayOutputStream類
  這個類用于把數(shù)據(jù)寫進字節(jié)數(shù)組(緩沖區(qū))。
  構造函數(shù):
  ■public ByteArrayOutputStream()
  ■public ByteArrayOntput Stream(int size)
  其中size指定緩沖區(qū)的初始大小(它是可以動態(tài)增長的)。
  成員變量:
  protected byte buf[]緩沖區(qū)
  protected int count緩沖區(qū)大小
  方法:
  ■public synchronized void write(int b)
  寫一個字節(jié)。
  ■public synchronized void write(byte b[],int offset,int length)
  把數(shù)組b中由offset開始長為length的一部分寫入緩沖區(qū)。
  ■public synchronized void writeTo(OutputStream out)throws IOException
  把緩沖區(qū)內(nèi)容寫到另一輸出流out。
  ■public synchronized void reset()
  指針定到緩沖區(qū)開始。當然,以后再寫入也就是從緩沖區(qū)的開始位置重寫了,原有的內(nèi)容就都清掉了。
  ■public syschronized byte[] toByteArray()
  將緩沖區(qū)內(nèi)容作為一個字節(jié)數(shù)組返回。
  ■public int size()
  當前緩沖區(qū)大小。
  ■public string toString()
  ■public string toString(int hibyte)
  把緩沖區(qū)內(nèi)容轉(zhuǎn)化為字符串。其中hibyte指把字符(通常是8位的ASCII字符)轉(zhuǎn)為16位的Unicode值時,高八的值。
  3.StringBufferInputStream類
  它的構造函數(shù)以一個字符串為參數(shù),原型為:
  ■public StringBufferInputStream(String s)
  其余成員變量及方法均與ByteArrayInputStream的同名且基本功能相同,此不贅述。
  這三個類的共性是內(nèi)存中開辟了一段空間來做I/O緩沖區(qū),故稱緩沖區(qū)I/O類。

7.5 過濾器I/O

  這一節(jié)涉及的類較多,但我們可以結(jié)合幾個例子逐一介紹。
  在第一節(jié)中,我們已經(jīng)談了一些過濾器類的特性。過濾器是可以“連接”的,即一個數(shù)據(jù)流經(jīng)過過濾后,其結(jié)果可以再次過濾。我們可以使用這樣一串過濾器中的任一個方法來完成某種特殊的操作。關于這一點在第二個例子中有更明白的闡述。

  7.5.1 例1:各類數(shù)據(jù)的I/O

  第一個例子(例7.3)演示了對各類數(shù)據(jù)的輸入輸出。
  例7.3FilterIODemo1.java。
  1: import java.io.*;
  2: public class FilterIODemo1{
  3:  public static void main(String args[]) throws IOException{
      //串接過濾器
  4:   BufferedOutputStream bufOut=
  5:    new BufferedOutputStream(new FileOutputStream("text.txt"));
  6:   DataOutputStream dataOut = new DataOutputStream(bufOut);
      //用DataOutputStream類寫各種數(shù)據(jù)
  7:   dataOut.writeBoolean(true);
  8:   dataOut.writeChar('a');
  9:   dataOut.writeInt(1);
  10:  dataOut.writeDouble(3.3);
  11:  bufOut.close();
  12:  dataOut.close();
  13:  BufferedInputStream bufIn=
  14:   new BufferedInputStream(new FileInputStream("text.txt"));
  15:  DataInputStream dataIn= new DataInputStream(bufIn);
    //用DataInputStream類讀各種數(shù)據(jù)
  16:  System.out.println(dataIn.readBoolean());
  17:  System.out.println(dataIn.readChar());
  18:  System.out.println(dataIn.readInt());
  19:  System.out.println(dataIn.readDouble());
  20:  bufIn.close();
  21:  dataIn.close();
  22: }
  23:}
  例7.3的運行結(jié)果如下:(略)
  上述例子演示了DataInputStream、DataOutpurStream、BufferedInputStream和 BufferedOutputStream的使用。該程序中只有一個方法main()。
  在方法的開頭,我們實例化了BufferedOutputStream類,得到對象bufOut。注意,我們的數(shù)據(jù)輸出的最終目的地是文件 “Text.txt”。為了能夠利用BufferedOutputStream的緩輸出(把輸出內(nèi)容先存入緩沖,然后大塊輸出)功能,我們在文件輸出應對上加一個過濾器,形成:
  數(shù)據(jù)→過濾器對象bufOut→文件輸出流
這樣,我們用dataOut來寫數(shù)據(jù),就可以直接把各種類型的數(shù)據(jù)寫入文件text.txt。
  程序的后半部分幾乎是第一個程序的翻版。我們在輸入流上也加了過濾器,就可以用過濾器的方法來操作輸入流了。
  由于BufferedOutputStream和BufferedInputStream沒有提供新的方法,這個例子也許會使讀者產(chǎn)生一種錯覺,好像只有最外層(最接近“數(shù)據(jù)”)的過濾器才能操縱輸入。事實并非如此,我們將在下一個例子中說明這一點。
  我們要解釋的問題是,如果我們讀數(shù)據(jù)時,選用的讀方法與寫時不一致會怎么樣呢?讀者可以自行實驗一下。如果我們把
  dataIn.readBoolean()換作dataIn.readChar()
讀出的結(jié)果就不正確了(注意讀到的并不是字符‘t’),各種類型的數(shù)據(jù)存儲的格式是不同的。雖然我們得到了圖7.4所示的結(jié)果,但如果你用type命令看一下 text.txt,將會看到不同的輸出。因此,不要把程序的輸出和數(shù)據(jù)的內(nèi)部在存儲混為一談。當使用dataI/O時,應當對你要讀的數(shù)據(jù)的類型心中有數(shù)。DataInputStream并不能從一堆數(shù)據(jù)中析取你所需要的那個整數(shù)。

  7.5.2 過濾器類家庭

  下面我們介紹例1中出現(xiàn)的過濾器類。首先介紹一下它們的父類FilterInputStream和FilterOutputStream。
  1.FilterInputStream類
  這是一個抽象類。它是所有過濾器輸入類的父類,提供了從一個輸入流創(chuàng)建另一個輸入流的方法。
  構造函數(shù):
  ■public FilterInputStream(InputStream in)
  人一個輸入流構造過濾器輸入流。
  方法:
  重寫了InputStream的同名方法,未提供新的方法。
  2.FilterOutputStream類
  與FilterOutputStream相對應,提供從一個輸出流創(chuàng)建另一個輸出流的方法。
  構造函數(shù):
  ■public Filer OutputStream(OutputStream out)
  由輸出流創(chuàng)始創(chuàng)建一個過濾器輸出流。
  方法:
  重寫了OutputStream的同名方法。
   3.BufferedInputStream類
  從這個類開始,我們來介紹例7.3中用到的過濾器子類。BufferedInputStream類提供了一種“預輸入”功能,它把輸入數(shù)據(jù)在其緩沖區(qū)內(nèi)暫存,在適當?shù)臅r候把較大塊的數(shù)據(jù)提交出去。
  構造函數(shù):
  ■public BufferedInputStream(InputStream in)
  ■public BufferedInputStream(InputSteam in,int size)
  其中size指緩沖區(qū)大小。
  方法:
  重寫了父類的方法。其中skip()、available()、mark()、reset()均為同步(synchonized)方法。
  4. BufferedOutputStream類
  提供“緩輸出”功能,把輸出數(shù)據(jù)暫存后,在適當時候大批送出。
  構造函數(shù):
  ■public BufferedOutputStream(OutputStream out)
  ■public BufferedOutputStream(OutputStream out,int size)
  方法:
  ■public synchronized void write(int b) throws IOException
  ■public synchronized void write(byte b[],int offset,int length) throws IOException
  ■public synchronized void flush() throws IOException
以上方法重寫了父類的同名方法。
  在BufferedI/O類中,還有一些protect型的成員變量,是關于緩沖區(qū)和標記的,這里就不一一列出了。
  5. DataInput接口和DataOutput接口
  要介紹Data I/O類,就必須介紹Data I/O接口。
  Data I/O類的目的是從流中析取或向流中寫入指定的數(shù)據(jù)對象。一個流可以是純字符流,也可以包含許多類型的數(shù)據(jù)。DataInput接口和 DataOutput接口就提供了從流中析取和寫入數(shù)據(jù)的方法。用于讀的方法除了個別之外都是無參的,寫的方法則往往以被 寫的數(shù)據(jù)類型為參數(shù)。方法的名字也很好記,即為“read”或“write”后接類型名,如readInt(),readUnsignedByte(),writeInt(), writeUnsignedByte()等。這引起方法均可能拋出I/O異常。一般說來,讀時遇文件尾時拋出EOFException(是 IOException的子類),讀寫時發(fā)生其它錯誤拋出IOException。
除了上面所說的名字很有規(guī)律的方法外,Data I/O接口中還有幾個方法:
  ■public abstract void readFully(byte buffer[])
  讀全部數(shù)據(jù)到buffer[]數(shù)組。讀時系統(tǒng)處于阻塞狀態(tài)。
  ■public abstract void readFully(byte buffer[],int offset,int length)
  把數(shù)據(jù)讀到數(shù)組buffer[]中從Offset開始長為length的地方。
  ■public abstract int skipBytes(int n)
  跳過規(guī)定字節(jié)數(shù)。返值為實際跳過的字節(jié)數(shù)。
  ■public abstract String readLine()
  讀取一行數(shù)據(jù)。
  此外,還有我們早已熟悉的write()方法的三種重載形式。
  6. DataInputStream類
  介紹過兩個數(shù)據(jù)I/O的接口后,介紹數(shù)據(jù)I/O流類就簡單多了。DataInputStream類實現(xiàn)了DataInput接口,因面也就實現(xiàn)了這個接口的所有成員方法。此外,還有兩個read()方法:
  ■public final int read(byte b[])
  ■public final int read(byte b[],int offset,int length)
  重寫了FilterInputStream的同名方法。
  DataInputStream只有一個構造函數(shù)。像所有過濾器輸入流類一樣,這個構造函數(shù)的參數(shù)是InputStream的一個對象。
  7.DataOutputStream類
  這個類的成員方法我們都很熟悉了。除了實現(xiàn)DataOutput接口的方法之外,再就是一個flush()方法。write()與flush()重寫了FilterOutputStream類的同名方法。
  現(xiàn)在我們可以回過頭來再看一下例7.3,印證一下剛才講過的內(nèi)容。這個例子的重點之一是演示過濾器的“連接”,另一個是介紹相應的類。

  7.5.3 例2:行號與“可推回”的流

  在下面的例子(例7.4)中,我們將進一步理解過濾器的連接問題。前面例子基本上用的都是字節(jié)流類,這個例子使用字符流類。
  例7.4 FilterIODemo2.java。
  1:import java.io.*;

  3:public class FilterIODemo2{
  4: public static void main(String args[]) throws IOException{
  5:  String s="this is a multi-line string.\n It is \nused to demo filterIO.\n";
  6:  char array[]=new char[s.length()];
  7:  for(int i=0;i<s.length();++i)
  8:  array[i]=s.charAt(i);
    //創(chuàng)建字符流,串接過濾器
  9:  CharArrayReader charReader = new CharArrayReader(array);
  10:  PushbackReader pushReader = new PushbackReader(charReader);
  11:  LineNumberReader lineReader = new LineNumberReader(pushReader);
  12:  String line;
    //讀字符流,加行號輸出
  13:  while((line = lineReader.readLine())!=null){
  14:   System.out.println(lineReader.getLineNumber()+":"+line);
  15:  }
    //指針置到開頭
  16:  try{ pushReader.reset();}catch(IOException e){}
    //讀字符流,每讀到一個'\n'就把它推回
  17:  boolean eof = false;
  18:  boolean met = false;
  19:  while(!eof){
  20:   int c=pushReader.read();
  21:   if(c==-1) eof=true;
  22:   else if(((char)c=='\n')&&!met){met =true;pushReader.unread(c);}
  23:   else met =false;
  24:   if(c!=-1) System.out.print((char)c);
  25:  }
  26:  System.out.flush();
  27:  pushReader.close();
  28:  charReader.close();
  29:  lineReader.close();
  30: }
  31:}
  該程序的運行結(jié)果如下:(略)
  這個例子的功能是:給一個字符串加上行號后輸出;把每個換行符都“重復”一次,即每次換行時加一個空行。該例子使用的是字符流I/O,演示了幾個類的使用:CharArrayReader,PushbackReader,LineNumberReader。此外,我們還可以復習一下前面提到的幾個流類。
  PushbackReader,顧名思義是是可以把數(shù)據(jù)“推回”輸入流的流類。我們用它來實現(xiàn)對換行符的重復——只要讀完后把“推回去”,下次就可再讀一遍了。LineNumberReader可以追蹤輸入的行數(shù),用它來實現(xiàn)加行號輸出。
  現(xiàn)在來講解一下程序。第5行中,在main()方法的開始,定義了一個字符串s。其中,有三個換行符‘\n’。然后創(chuàng)建一個字節(jié)數(shù)組Array[],并在接下來的for循環(huán)(第7、8行)中為它賦值。以此為參數(shù)創(chuàng)建了一個內(nèi)存緩沖區(qū)輸入流的對象。這就是我們一串過濾器的源點。注意array并不是一個輸入流,相應的CaarArrayReader也不是一個過濾器。
  現(xiàn)在考慮選用過濾器。可根據(jù)我們想要的功能來選擇。既然我們要行號,那么顯然最好是一行一行讀數(shù)據(jù)。BufferedReader的readLine ()方法正是我們需要。(readLine()方法本來是DataInputStream類的方法,但在1.1版中過時了。詳細情況在第一節(jié)中已有說明。這里用DataInputStream也是可以的但編譯時會警告信息。)加行號我們可以一行一行地讀,也可以自己高于個變量來累計行數(shù)。當然也可以利用一個現(xiàn)成的類和現(xiàn)在的方法——選擇LineNumbdrReader類及其getLineNumber()方法。由于LineNumbdrReader本身是BuffredReader類的子類,可以直接用它來逐行讀數(shù)據(jù),不必再引入BufferedReader類。為了重復寫回畫換行符可選用 PushbackInputStream類和它的unread()方法。
  下面的任務是把它們串起來,如例子所示,可將它些過濾器一個“輸出”作為下一個的“輸入”。第一個while循環(huán)(第13到15行)中做的事很簡單;讀一行信息,取得其行號,然后一些輸出。
  第二個while循環(huán)(第19行到25行)的工作是重寫操作符。我們用pushReader來讀數(shù)據(jù)。布爾量eof來標識輸入是否結(jié)束,met用來標識當瓣換行符是否被推回過。當輸入沒有結(jié)束時,每讀到一個‘\n’ 時,就不會再“推回”了,保證換行符只被重復一次。
  正如前面所提到過的,一串過濾器中的任一個都可以操作數(shù)據(jù),無論該過濾器是最先的或最末的或是中間的任何一個。
  由于我們是用print()方法來輸出字符的,程序結(jié)束時可能還有一部分數(shù)據(jù)在緩沖區(qū)中,沒被寫到屏幕上。因此我們加了一個flush()方法強制顯示到屏幕上。
  將用過的流都關閉(第27到29行)是一種好的編輯風格。雖然Java的“垃圾收集”系統(tǒng)可以回收廢棄不用的資源,仍應自覺地打掃“戰(zhàn)場”,把能回收的資源主動回收。

  7.5.4 類庫支持

  下面詳細介紹一下例7.4中新出現(xiàn)的類。有一點需要解釋,就是字符流I/O類與字節(jié)流I/O類的繼承關系并不是一一對應的。比如,字節(jié)流I/O類中的 PrintStream是FilterOutputStream的子類,而對應的字符流類PrintWriter卻是Writer類的子類。因此,嚴格地說PrintWriter并非過濾器類。但是,為了能夠分六別類地研究這些類,我們不苛求這個差別,而是按照字節(jié)流I/O類的繼承關系,對應地把相應字符流I/O類也看作過濾器類。
  1.PushbackReader類
  構造函數(shù)兩個:
  ■public PushbackReader(Reader in,int size)
  創(chuàng)建緩沖區(qū)大小為size的一個PushbackReader對象。
  ■public PushbackReader(Reader in)
  創(chuàng)建緩沖區(qū)大小為一個字符的一個PushbackReader對象。
  方法:
  ■public int read()
  ■public int read(char cbuf[],int offset,int length)
  讀數(shù)據(jù)。
  ■public void unread(int ch)
  回退一個字符。當緩沖區(qū)滿或發(fā)生其它輸入輸出的異常情況時,拋出I/O異常。
  ■public int avaliable()
  返回緩沖區(qū)內(nèi)字節(jié)個數(shù)。
  ■public boolean markSupported()
  確認輸入流是否支持標記功能。
read()、unread()、available()均可能拋出IOException。
  2.LineNumberReader類
  構造函數(shù)兩個,與PushbackReader類似。
  下面列出方法的原型,其中我們已經(jīng)熟悉的,在此就不給出解釋了。
  ■public int read() throws IOException
  ■public int read(char cbuf[],int offset,int length) throws IOException
  ■public void setLineNumber(int lineNumber)
  設置行號。
  ■public int getLineNumber()
  讀行號。
  ■public long skip(long n) throws IOException
  ■public int available()throws IOException
  ■public void mark(int readAheadLimit)throws IOException
  在當前位置作標記。從此讀取readAheadLimit個字符后標記變?yōu)闊o效。
  ■public void reset()throws IOException
  返回到上一標記。
  3.PrintStream類和PrintWriter類
  PrintStream類是過濾器類中一個不可忽視的成員,最基本的標準輸出就要借助于它——我們常用的System.out變量就是 PrintStream實例。與之對應的字符流類是PrintWriter類。
  PrintStream有兩個構造函數(shù)(在新版API中已標記為過時):
  ■public PrintStream(OutputStream out)
  ■public PrintStream(OutputStream out,boolean autoFlush)
其中,autoFlush置為true時,每當輸出遇到換行符,緩沖區(qū)的內(nèi)容就被強制全部輸出,如同調(diào)用了一次flush()。但要注意,如果沒遇到換行符,還是會有數(shù)據(jù)“憋”在緩沖區(qū)里。
  方法(已熟悉的就不解釋):
  ■public void write(int b)
  ■public void write(byte b,int offset,int length)
  ■public void flush()
  ■public void close()
  ■public void print(Object obj)
  這個方法功能是非常強大的,它可以輸出任何對象,而不必另加說明。此外print()方法有許多重載形式,即有多種參數(shù)。它們是字符串 (String)、字符數(shù)組(char[])、字符(char)、整數(shù)(int)、長整數(shù)(long)、浮點數(shù)(float)、雙精度浮點數(shù) (double)、布爾值(boolean)。其中,輸出多個數(shù)單位的print()方法(也就是指參數(shù)為String和char[]的)是同步 (synchronized)方法。
  ■public void println()輸出一個換行符。
  ■public synchronized void println(Object obj)
  println()方法有9個重載形式,幾乎就是print()方法的翻版。唯一的區(qū)別在于println()方法都是同步的。
  ■public boolean checkError()
  檢查輸出過程中有什么錯誤,如有,返回true值。只要輸出流中出現(xiàn)一次錯誤,則出錯后的任意對checkError()的調(diào)用均會返回真值。
  下面介紹PrintWriter類。
  如同第二節(jié)中所說,PrintWriter是JDK1.1版增加了與字節(jié)流I/O相對應的字符流I/O。但是,為了保持兼容性,原先的類幾乎沒有改動。再加之調(diào)試的需要,PrintStream類被保留,并且System類中的成員變量out、err仍作為它的對象。然而,PrintWriter用于大多數(shù)輸出比PrintStream更為合適。因此1.1版的API中建議新開發(fā)的代碼使用PrintWriter類,并將 PrintStream類的兩個構造函數(shù)標記為過時。這樣,雖然使用System.out輸出不會產(chǎn)生問題,在程序中創(chuàng)建新的PrintStream對象時卻會產(chǎn)生編譯時的警告。
  PrintWriter類與PrintStream類的方法是對應的。有一個不同之外需提請讀者注意,就是當前者的自動清空緩沖區(qū)的功能被使能時(構造函數(shù)中autoFlush置為true),僅當println()方法被調(diào)用時才自動清緩沖區(qū),而不是像PrintStream一樣遇到一個換行符就清緩沖。
  到此為止,我們已介紹了各種類型的過濾器I/O類。適用于字節(jié)流和字符的各種對應過濾器類,其方法也是對應的。因此,對沒有介紹的類讀者可以從其對應類推理其功能。

7.6 管道I/O

  管道I/O是專門用于線程通信的。對于字節(jié)流Java提供了兩個類,PipedInputStream類被線程用來寫字節(jié)數(shù)據(jù)。兩個管道I/O流對象可以連接起來,這樣一個線程寫的數(shù)據(jù)就可以被另一個線程來讀。對于字符流也有兩個類,分別叫做PipedReader和PipedWriter。我們只詳細介紹字節(jié)流的管道I/O類。

  7.6.1 PipedInputStream類

  這個類有兩個構造函數(shù)。一個無參,用它建立起輸入流后,需將它與一個管道輸出流相連接。另一個以管道輸出流(PipedOutputStream)對象為參數(shù),創(chuàng)建一個與該輸出流對象相連接的輸入流。
  PipedInputStream類的所有方法均可能拋出IOException。
  ■public void connect (PipedOutputStream src)
  將輸入流連接到某管道輸出流。
  ■public synchronized int read()
  ■public synchronized int read(byte b[],int offset,int length)
  讀數(shù)據(jù)。
  ■public void close()
  關閉流。

  7.6.2 PipedOutputStream類

  與PipedInputStream類完全對應,它有兩個構造函數(shù),其中一個以PipedInputStream對象為參數(shù),另一個無參。成員方法也是包括connect(),close(),另外還有兩種形式的write()方法,這里就不細述了。

  7.6.3 程序示例

  下面用一個示例(例7.5)具體演示管道I/O的使用。
  例7.5 PipeIODemo.java
  1: import java.lang.*;
  2: import java.io.PipedInputStream;
  3: import java.io.PipedOutputStream;
  4: import java.io.IOException;
  5:

  6: public class PipeIODemo{
  7:  public static void main(String args[]){
     //這里的Reader和Writer不是字符流輸入輸出的基本類,而是下文自定義的
  8:   Reader thread1=new Reader("1");
  9:   Writer thread2=new Writer("2");
    //聯(lián)接管道
  10:  try{
  11:   thread2.pipeOut.connect(thread1.pipeIn);
  12:  }catch(IOException ex){
  13:   System.out.println("IOException occured when connecting two stream");
  14:  }
    //啟動線程
  15:  thread1.start();
  16:  thread2.start();
    //循環(huán),等線程均結(jié)束后程序中止
  17:  do{
  18:  }while(thread1.isAlive()||thread2.isAlive());
  19:  System.out.println("All over!");
  20: }
  21:}
  //自定義讀者類
  22:class Reader extends Thread{
  23: public PipedInputStream pipeIn;
  24: String threadName;
  25: public Reader(String name){
  26:  super();
  27:  threadName = name;
  28:  pipeIn = new PipedInputStream();
  29: }
  30: public void run(){
  31:  try{
  32:   boolean over = false;
  33:   while(!over){
  34:    int ch=pipeIn.read();
  35:    try{
  36:     Thread.sleep(200);
  37:    }catch(InterruptedException ex){
  38:     System.out.println("Sleep is interrupted!");
  39:   }
  40:   if(ch=='.') over = true;
  41:    else System.out.println("Thread "+threadName+" read "+(char)ch);
  42:   }
  43:
  44:  }catch(IOException ex){
  45:   System.out.println("IOException occured when try to read data");
  46:  }
  47: }
  48:}
  //自定義寫者類
  49:class Writer extends Thread{
  50: public PipedOutputStream pipeOut;
  51: String threadName;
   //待寫內(nèi)容
  52: String content = "orange apple";
  53: public Writer(String name){
  54:  super();
  55:  threadName=name;
  56:  pipeOut = new PipedOutputStream();
  57: }
  58: public void run(){
  59:  try{
     //將字符串內(nèi)容逐字輸出
  60:   for(int i=0;i<content.length();++i){
  61:    pipeOut.write(content.charAt(i));
  62:    try{
  63:     Thread.sleep(200);
  64:    }catch(InterruptedException ex){
  65:     System.out.println("Sleep is interrupted!");
  66:    }
  67:    System.out.println("Thread "+threadName+" wrote "+content.charAt(i));
  68:   }
  69:    pipeOut.write('.');
  70:   }catch(IOException ex){
  71:   System.out.println("IOException occured when try to write data");
  72:  }
  73: }
  74:}
  該程序的運行結(jié)果如下:(略)
  這個例子功能很簡單。兩個線程,一個是讀者,一個是寫者,讀者取寫者所寫的內(nèi)容。雙方約定以‘.’為結(jié)束符。
  這個例子演示了管道I/O一般過程,首先是創(chuàng)建管理I/O流類對象。這個工作是在Reader和Writer類的構造函數(shù)中做的(第28、56行)。因此當我們創(chuàng)建了thread1和thread2兩個線程時,pipeIn和pipeOut就被創(chuàng)建了。然后我們把它們連接起來,再啟動兩個線程工作,最后打印“All Over!” 表示運行結(jié)束。
  可以看出,讀線程與寫線程實際上是不必關心對方的情況的。它們的工作就是讀或?qū)懀刻幚硪粋€字符輸出一條信息表明自己做過的工作。我們在pipeIn 的輸出信息中加了一大段空格,這樣的目的是使兩個線程的輸出能容易分辨。另外,讓兩個線程處理一個字符就睡眠(sleep) 一會兒并不是必須的,這樣只是為了增加線程交替執(zhí)行的機會。如果去年這一段,可能執(zhí)行數(shù)次者不出現(xiàn)thread1、thread2交替輸出信息的現(xiàn)象,容易被誤解為兩個線程必須一個死亡才執(zhí)行另一個。另外,作為結(jié)束符的“.” 并沒有顯示出來。
  這個例子實現(xiàn)的是單向通信。實際上,為每個線程都分別創(chuàng)建輸入流對象和輸出流對象,再分別連接起來,就可以實現(xiàn)雙向通信。讀者有興趣不妨一試。

7.7 java.io包中的其它類

  7.7.1 SequenceInputStream類

  這個類的功能是合并多個輸入流。其構造函數(shù)有兩個,一個以枚舉(Enumeration)對象為參數(shù),一個以兩個InputStream對象為參數(shù)。方法則有兩個read()方法,分別讀一個字符、讀數(shù)據(jù)入字節(jié)數(shù)組中的一段。再就是一個close()方法。例7.6利用它來實現(xiàn)了兩上文件的并接。其中還使用了ByteArrayOutputStream,用意是將兩個文件并接的結(jié)果先在內(nèi)存緩沖區(qū)中暫存一下。這個例子允許目的的文件是兩個源文件之一。
  例7.6 FileCat.java
  import java.lang.System;
  import java.io.*;

  public class FileCat{
   public static void main(String args[]){
    SequenceInputStream seqIn;
    if(args.length!=3){System.out.println("Usage:java FileCat filesrc filesrc filedst");}
    else{
     try{
      FileInputStream f1=new FileInputStream(args[0]);
      FileInputStream f2=new FileInputStream(args[1]);
      seqIn=new SequenceInputStream(f1,f2);
      ByteArrayOutputStream byteArrayOut=new ByteArrayOutputStream();
      boolean eof=false;
      int byteCount=0;
      while(!eof){
       int c=seqIn.read();
       if(c==-1)eof=true;
       else{
        //將讀到的數(shù)據(jù)寫入字節(jié)數(shù)組輸出流
        byteArrayOut.write((char)c);
        ++byteCount;
       }
      }
      FileOutputStream outStream=new FileOutputStream(args[2]);
      //將數(shù)據(jù)寫入文件
      byteArrayOut.writeTo(outStream);
      System.out.println(byteCount+" bytes were read.");
      seqIn.close();
      outStream.close();
      byteArrayOut.close();
      f1.close();
      f2.close();
     }catch(FileNotFoundException ex){
      System.out.println("Cannot open source files.Please check if they"+
        "exists and allows freading.");
     }catch(IOException ex){
      System.out.println("IOexception occured!");
     }
    }
   }
  }

  7.7.2 Streamtokenizer類

  這個類是用來構造詞法分析器的。缺省情況下,它可以識別數(shù)值、字母以及字符串。它的構造函數(shù)只有一個,以輸入流(inputStream)對象為參數(shù)。本節(jié)我們給出一個例子(例7.7),并介紹例子中出現(xiàn)的該類的部分方法。
  例7.7 TokenIODemo.java。
  1:import java.io.IOException ;
  2:import java.lang.System;
  3:import java.io.InputStreamReader ;
  4:import java.io.StreamTokenizer;
  5:import java.io.FileInputStream ;
  6:
  7:public class TokenIODemo{
  8: public static void main(String args[]) throws IOException{
    //從文件創(chuàng)建輸入流
  9:  FileInputStream fileIn = new FileInputStream ("hello.c");
    //從字節(jié)流創(chuàng)建字符流
  10:  InputStreamReader inReader = new InputStreamReader (fileIn);
  11:  StreamTokenizer tokenStream = new StreamTokenizer (inReader);
    //設置注釋風格
  12:  tokenStream.slashStarComments(true);
  13:  tokenStream.slashSlashComments (true);
    //識別行結(jié)束符;如果參數(shù)為假,將行結(jié)束符視作空白符
  14:  tokenStream.eolIsSignificant (true);
    //設置引號的符號表示
  15:  tokenStream.quoteChar ('"');
    //將ASCII碼為0-32的字符設為空白符
  16:  tokenStream.whitespaceChars (0,32);
  17:  boolean eof = false;
  18:  do{
  19:   int token = tokenStream.nextToken ();
  20:   switch(token){
      //文件結(jié)束符
  21:   case tokenStream.TT_EOF :
  22:    System.out.print(" EOF ");
  23:    eof=true;
  24:    break;
      //行結(jié)束符
  25:   case tokenStream.TT_EOL :
  26:    System.out.print (" EOL ");
  27:    break;
      //單詞
  28:   case tokenStream.TT_WORD :
  29:    System.out.print (" Word "+tokenStream.sval );
  30:    break;
      //數(shù)字
  31:   case tokenStream.TT_NUMBER :
  32:    System.out.print(" Number "+tokenStream.nval );
  33:    break;
  34:   default:
  35:    System.out.print(" "+(char)token);
  36:   }
  37:  }while(!eof);
  38:  System.out.flush();
  39: }
  40:}
  下面是該例的運行結(jié)果:
  E:\>java TokenIODemo
  # Word include < Word stdio.h > EOL EOL Word main ( ) { EOL Word print ( " ,
Number 1234.0 ) ; EOL EOL } EOL EOF
  E:\>
  其中,hello.c程序的源代碼如下:
  #include <stdio.h>
  //To say "hello world"
  main(){
   print("hello world %d\n",1234);
   /* It is a test for TokenIODemo*/
  }
  例子中我們用到了這樣一些方法:
  ■public void whitespaceChars(int low,int hi)
  把給定范圍的字符設為空格(不可見)字符。類似的方法還有wordChars()(設為單詞字符),ordinaryChars()(設置為除了單詞字符、數(shù)據(jù)字符等有實際含義字符之外的其它字符)。
  ■public void slachStarComments(boolean flag)
  ■public void slachSlashComments(boolean flag)
  flag為真,則可訓別相應風格的注釋。前者(slashStar)指C風格的注釋(/*...*/)。后者指C++風格的注釋“//”。
  ■public int nextToken()
  從輸入流取得下一個詞法分析單位。
  ■public void eolIsSingnificant(boolean flag)
  如果參數(shù)為真,識別行結(jié)束符;否則,將行結(jié)束符視作空白符。
  例子中還用到了一些常量和變量。TT_EOF、TT_EOL、TT_NUMBER、TT_WORD分別表示文件結(jié)束符、行結(jié)束符、數(shù)值和單詞。public String sval是指字符串值;public double nval指雙精度值。這些常量、變量的使用在例子中已有明確的演示,這里就不多說了。

  7.7.3 FilenameFilter接口

  這個接口不太常用,只提供了一個方法:
  ■public abstract boolean accept(File dir,String fileName)
  功能是確定某一文件列表是否包含了指定的文件。

  7.7.4 Serializable接口

  實現(xiàn)這一接口的類可以被“串行化”,即它們的對象可以被轉(zhuǎn)化為某種形式,該形式可以被輸入輸出,而保存對象的結(jié)構。也就是說,只有實現(xiàn)了這一接口,類的對象才能被完整地輸入輸出和存儲。
  該接口不含任何方法和變量,它只充當一個標記。編程時只要在類定義時中上:
  ... implements Serializable
即可使該類的對象具有“串行性” 。

本章小結(jié)

  在這一章中,我們比較全面地介紹了java.io包中的類和接口,并給出了示例。讀者通過這一章學習,應掌握java的輸入輸出類,并將種I/O手段靈活運用于自編的程序之中。