輸出流輸入流

 

InputStream和OutputStream

  InputStream是所有表示位輸入流的類之父類,OutputStream是所有表示位輸出流的類之父類.它們都是抽象類,繼承它們的子類要重新定義其中所定義的抽象方法..

  例1可以讀取鍵盤輸入流,in對象的read()方法一次讀取一個字節的數據,讀入的數據以int 類型返回.所以在使用out對象將數據顯示出來時,就是十進制方式.

例1 StreamDemo.java

import java.io.*;

public class StreamDemo {
 public static void main(String[] args){
  try{
   System.out.print("輸入字符:");
   System.out.println("輸入字符十進制表示:" + System.in.read());
  }
  catch(IOException e){
   e.printStackTrace();
  }
 }

}

 

FileInputStream和FileOutputStream

  java.io.FileInputStream是InputStream的子類.

   當建立一個FileInputStream或FileOutputStream的實例時,必須指定文件位置及文件名稱,實例被建立時文件的流就會開啓;而不使用流時,必須關閉文件流,以釋放與流相依的系統資源,完成文件讀/寫的動作.

  FileInputStream可以使用read()方法一次讀入一個字節,並以int類型返回,或者是使用read()方法時讀入至一個byte數組,byte數組的元素有多少個,就讀入多少個字節.在將整個文件讀取完成或寫入完畢的過程中,這麼一個byte數組通常被當作緩衝區,因爲這麼一個byte數組通常扮演承載數據的中間角色.

  例2是使用FileInputStream與FileOutputStream的一個例子.程序可以複製文件,它會先從來源文件讀取數據至一個byte數組中,然後再將byte數組的數據寫入目的文件.

例2 FileStreamDemo.java

import java.io.*;

public class FileStreamDemo {
 public static void main(String[] args) {
  try {
   byte[] buffer = new byte[1024];

   // 來源文件
   FileInputStream fileInputStream = new FileInputStream(new File(
     args[0]));
   // 目的文件
   FileOutputStream fileOutputStream = new FileOutputStream(new File(
     args[1]));

   // available()可取得未讀取的數據長度
   System.out.println("複製文件:" + fileInputStream.available() + "字節");

   while (true) {
    if (fileInputStream.available() < 1024) {
     // 剩餘的數據比1024字節少
     // 一位一位讀出再寫入目的文件
     int remain = -1;
     while ((remain = fileInputStream.read()) != -1) {
      fileOutputStream.write(remain);
     }
     break;
    } else {
     // 從來源文件讀取數據至緩衝區
     fileInputStream.read(buffer);
     // 將數組數據寫入目的文件
     fileOutputStream.write(buffer);
    }
   }

   // 關閉流
   fileInputStream.close();
   fileOutputStream.close();

   System.out.println("複製完成");
  } catch (ArrayIndexOutOfBoundsException e) {
   System.out.println("using:java FileStreamDemo src des");
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

  程序中示範了兩個read()方法,一個方法可以讀入指定長度的數據至數組,另一個方法一次可以讀入一個字節.每次讀取之後,讀取的光標都會往前進,如果讀不到數據剛返回-1.使用available()方法可以獲得還有多少字節可以讀取.除了使用File來建立FileInputStream,FileOutputStream的實例之外,也可以直接使用字符串指定路徑來建立.如

  //來源文件

  FileInputStream fileInputStream = new FileInputStream(args[0]);

     //目的文件

  FileOutputStream fileOutputStream = new FileOutputStream(args[1]);

  在不使用文件流時,記得使用close()方法自行關閉流,以釋放與流相依的系統資源.

  FileOutputStream默認會以新建文件的方式來開啓流.如果指定的文件名稱已經存在,則原文件會被覆蓋;如果想以附加的模式來寫入文件,則可以在構建FileOutputStream實例時指定爲附加模式.例如:

  FileOutputStream fileOutputStream = new FileOutputStream(args[1],true);

  構建方法的第二個append參數如果設定爲true,在開啓流時如果文件不存在則會新建一個文件,如果文件存在就直接開啓流,並將寫入的數據附加至文件末端.

 

BufferedInputStream 和 BufferedOutputStream

  java.io.BufferedInputStream與java.io.BufferedOutputStream可以爲InputStream、OutputStream類的對象增加緩衝區功能,構建BufferedInputStream實例時,需要給定一個InputStream類型的實例,實現BufferedInputStream時,實際上最後是實現InputStream實例.同樣地,在構建BufferedOutputStream時,也需要給定一個OutputStream實例,實現BufferedOutputStream時,實際上最後是實現OutputStream實例.

  BufferedInputStream的數據成員buf是一個位數組,默認爲2048字節,BufferedOutputStream的數據成員buf 也是一個位數組,默認爲512字節.

  例3是對例2的改寫,不用自行設定緩衝區,比較簡單且有效率.

例3 BufferedStreamDemo.java

import java.io.*;

public class BufferedStreamDemo {
 public static void main(String[] args){
  try{
   byte[] data = new byte[1];
   
   File srcfile = new File(args[0]);
   File desfile = new File(args[1]);
   
   BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(srcfile));
   BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(desfile));
   
   System.out.println("複製文件:" + srcfile.length() + "字節");
   
   while(bufferedInputStream.read(data) != -1){
    bufferedOutputStream.write(data);
   }
   
   //將緩衝區中的數據全部寫出
   bufferedOutputStream.flush();
   
   //關閉流
   bufferedInputStream.close();
   bufferedOutputStream.close();
   
   System.out.println("複製完成");
   
  }
  catch(ArrayIndexOutOfBoundsException e){
   System.out.println("using java UseFileStream src des");
   e.printStackTrace();
  }
  catch(IOException e){
   e.printStackTrace();
  }
 }

}

  爲了確保緩衝區中的數據一定被寫出至目的地,建議最後執行flush()將緩衝區中的數據全部寫出目的流中,這個範例的執行結果與例2的執行結果是相同的.

 

DatainputStream 和 DataOutputStream 

  java.io.DataInputStream 和 java.io.DataOutputStream 可提供一些對Java基本數據類型寫入的信息,成員數據的類型假設都是Java的基本數據類型,這樣的需求不必要使用到與Object輸入、輸出的流對象,可以使用DataInputStream 、DataOutputStream來寫入或讀出數據.

  示例:先設計一個Member類.

例4 Member.java

public class Member {
 private String name;
 private int age;
 
 public Member(){
  
 }
 
 public Member(String name,int age){
  this.name = name;
  this.age = age;
 }
 
 public void setName(String name){
  this.name = name;
 }
 
 public void setAge(int age){
  this.age = age;
 }
 
 public String getname(){
  return name;
 }
 
 public int getAge(){
  return age;
 }
}

 

  下面打算將Member類實例的成員數據寫入文件中,並打算在讀入文件數據後,將這些數據還原爲Member對象.

例5 DataStreamDemo.java

import java.io.*;

public class DataStreamDemo {
 public static void main(String[] args){
  Member[] members = {new Member("A", 91),
                     new Member("B", 80),
                     new Member("C", 86)};
  
  try{
   DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(args[0]));
   
   for(Member member:members){
    //寫入UTF字符串
    dataOutputStream.writeUTF(member.getname());
    //寫入int字符串
    dataOutputStream.writeInt(member.getAge());
   }
   
   //讀出所有數據至目的地
   dataOutputStream.flush();
   //關閉流
   dataOutputStream.close();
   
   DataInputStream dataInputStream = new DataInputStream(new FileInputStream(args[0]));
   //讀出數據並還原爲對象
   for(int i = 0; i < members.length; i++){
    //讀出UTF字符串
    String name = dataInputStream.readUTF();
    //讀出int數據
    int score = dataInputStream.readInt();
    members[i] = new Member(name,score);
    
   }
   //關閉流
   dataInputStream.close();
   
   //顯示還原後的數據
   for(Member member:members){
    System.out.printf("%s/t%d%n",member.getname(),member.getAge());
   }
  }
  catch(IOException e){
   e.printStackTrace();
  }
                    
 }

}

  在從文件中讀出數據時,不用費心地自行判斷讀入字符串時或讀入int類型時何時該停止,使用對應的readUTF()或readInt()方法可以正確地讀入完整類型數據.同樣地,DataInputStream、DataOutputStream並沒有改變InputStream或OutputStream的行爲.

 

 

ObjectInputStream和ObjectOutputStream

  在Java程序執行的過程中,很多數據都是以對象的方式存在於內存中,有時會希望直接將內存中的整個對象存儲至文件,而不是隻存儲對象中的某些基本類型成員信息,而在下一次程序運行時,希望可以從文件中讀出數據並還原爲對象.這時可以使用java.io.ObjectInputStream和java.io.ObjectOutputStream來進行這項工作.

  如果要直接存儲對象,定義該對象的類必須實現java.io.Serializable接口.不過Serializable接口中並沒有規範任何必須實現的方法,所以這裏所謂實現的意義,其實像是對對象貼上一個標誌,代表該對象是可序列化的(Serializable).

  爲了說明如何直接存儲對象,先實現一個User類.

例6 User.java

import java.io.Serializable;

public class User implements Serializable {
 private static final long serialVersionUID = 1L;
 
 private String name;
 private int number;
 
 public User(){
  
 }
 
 public User(String name,int number){
  this.name = name;
  this.number = number;
 }
 public void setName(String name){
  this.name = name;
 }
 
 public void setNumber(int number){
  this.number = number;
 }
 
 public String getName(){
  return name;
 }
 
 public int getNumber(){
  return number;
 }
}

  注意到serialVersionUID,它代表了可序列化對象的版本.如果沒有提供這個版本信息,則實現Serializable接口的類會自動依類名稱、實現的接口、成員等來產生.如果是自動產生的,則下次更改User類,自動產生的serialVersionUID也會跟着變更,從文件讀回對象時若兩個對象的serialVersionUID不相同,就會丟出java.io.InvalidClassException.如果想要維持版本信息的一致,則要明確聲明serialVersionUID.

  ObjectInputStream 和 ObjectOutputStream 爲 InputStream、OutputStream的實例加上了可以讓使用者寫入對象與讀出對象的功能.在寫入對象時,要使用 writeObject()方法,讀出對象時則使用readObject()方法,被讀出的對象都是以Object類型返回.

例7 ObjectStreamDemo.java

import java.io.*;
import java.util.*;

public class ObjectStreamDemo {
 public static void main(String[] args){
  User[] users = {new User("A",101),new User("B",102),new User("C",103)};
  
  try{
   //寫入新文件
   writeObjectsToFile(users,args[0]);
   //讀取文件數據
   users = readObjectsFromFile(args[0]);
   //顯示讀回的對象
   for(User user:users){
    System.out.printf("%s/t%d/n",user.getName(),user.getNumber());
   }
   System.out.println();
   
   users = new User[2];
   users[0] = new User("D", 104);
   users[1] = new User("E", 105);
   
   //附加新對象至文件
   appendObjectsToFile(users,args[0]);
   
   //讀取文件數據
   users = readObjectsFromFile(args[0]);
   
   //顯示讀回的對象
   for(User user:users){
    System.out.printf("%s/t%d/n", user.getName(),user.getNumber());
   }
  }
  catch(ArrayIndexOutOfBoundsException e){
   System.out.println("沒有指定文件名");
   //e.printStackTrace();
  }
  catch(FileNotFoundException e){
   e.printStackTrace();
  }
  catch(Exception e){
   e.printStackTrace();
   System.out.println("沒有指定文件名");
   
  }
 }
 
 //將指定的對象寫入至指定的文件
 public static void writeObjectsToFile(Object[] objs,String filename){
  File file = new File(filename);
  
  try{
   ObjectOutputStream objOoutputStream = new ObjectOutputStream(new FileOutputStream(file));
   for(Object obj:objs){
    //將對象寫入文件
    objOoutputStream.writeObject(obj);
   }
   //關閉流
   objOoutputStream.close();
  }
  catch(IOException e){
   e.printStackTrace();
  }
  
 }
 
 //將指定文件中的對象數據讀回
 public static User[] readObjectsFromFile(String filename)
                       throws FileNotFoundException{
  File file = new File(filename);
  
  //如果文件不存在就丟出異常
  if (!file.exists())
   throw new FileNotFoundException();
  
  //使用List先存儲讀回的對象
  List<User> list = new ArrayList<User>();
  
  try{
   FileInputStream fileInputStream = new FileInputStream(file);
   ObjectInputStream objInputStream = new ObjectInputStream(fileInputStream);
   
   while (fileInputStream.available() > 0){
    list.add((User) objInputStream.readObject());
   }
   objInputStream.close();
  }
  catch(ClassNotFoundException e){
   e.printStackTrace();
  }
  catch(IOException e){
   e.printStackTrace();
  }
  
  User[] users = new User[list.size()];
  return list.toArray(users);
 }
 
 public static void appendObjectsToFile(Object[] objs, String filename)
                    throws FileNotFoundException{
  File file = new File(filename);
  
  //如果文件不存在則丟出異常
  if (!file.exists())
   throw new FileNotFoundException();
  
  try{
   //附加模式
   ObjectOutputStream objOutputStream = new ObjectOutputStream(
                                   new FileOutputStream(file,true)){
    //如果要附加對象至文件後
    //必須重新定義這個方法
    protected void writeStreamHeader() throws IOException{}
   };
   
   for (Object obj : objs){
    //將對象寫入文件
    objOutputStream.writeObject(obj);
   }
   objOutputStream.close();
  }
  catch (IOException e){
   e.printStackTrace();
  }
 }
}

  注意,在試圖將對象附加至一個先前已寫入對象的文件時,由於ObjectOutputStream在寫入數據時,還會加上一個特別的流頭(Stream Header),所以在讀取文件時會檢查這個流頭,如果一個文件中被多次附加對象,那麼該文件中會有多個流頭,這樣讀取檢查時就會發現不一致,這會丟出java.io.StreamCorrupedException.爲了解決這個問題,可以重新定義ObjectOutputStream的writeStreamHeader()方法.如果是以附加的方式來寫入對象,就不寫入流頭:

  ObjectOutputStream objOutputStream = new ObjectOutputStream(

                                                                                     new FileOutputStream(file,true)) {

                                                                                       protected void writeStreamHeader()

                                                                                             throws IOException{}

};

  序列化對象的功能並不只應用於文件的讀取或寫入,也可以應用於其他領域.例如將對象直接通過網絡進行傳送或是傳送影像的位數組數據等.

 

SequenceInputStream

   若要將一個文件侵害爲數個文件,再將之組合還原爲一個文件,最基本的作法是使用數個FileInputStream來打開分割後的文件,然後一個一個文件的讀取,並使用同一個FileOutputStream實例寫到同一個文件中,必須要自行判斷每一個分割文件的讀取是否完畢,如果完畢就讀取下一個文件.

  如果使用java.io.SequenceInputStream就不用這麼麻煩,SequenceInputStream可以看作是數個InputStream對象的組合.當一個InputStream對象的內容讀取完畢後,它就會取出下一個InputStream對象,直到所有的InputStream對象都讀取完畢爲止.

 

PrintStream

ByteArrayInputStream 和 ByteArrayOutputSTream

PushbackInputStream

 

File類

 一個File的實例被建立時,它就不能再被改變內容.File實例除了用作一個文件或目錄的抽象表示之外,它還提供了不少相關操作方法:可以用它來對文件系統作一些查詢與設定的動作.要注意的是,不管是文件還是目錄,在Java中都是以File的實例來表示..

  以下是一個設定與操作File實例的簡單示例,可以指定查詢某個目錄下的所有文件與目錄名稱.

FileDemo.java

import java.io.*;
import java.util.*;

public class FileDemo {
 public static void main(String[] args){
  try{
   File file = new File(args[0]);
   if (file.isFile()){//是否爲文件
    System.out.println(args[0] + "文件");
    System.out.print(file.canRead()? "可讀" :"不可讀");
    System.out.print(file.canWrite() ? "可寫": "不可寫");
    System.out.println(file.length() + "字節");
   }
   else{
    //列出所有的文件及目錄
    File[] files = file.listFiles();
    ArrayList<File> fileList = new ArrayList<File>();
    
    for (int i = 0; i < files.length; i++){
     //先列出目錄
     if (files[i].isDirectory()) {
      //取得路徑名
      System.out.println("[" + files[i].getPath() + "]");
     }
     else{
      //文件先存入fileList,待會再列出
      fileList.add(files[i]);
     }
    }
    //列出文件
    for(File f: fileList){
     System.out.println(f.toString());
    }
    System.out.println();
   }
  }
  catch(ArrayIndexOutOfBoundsException e){
   System.out.println("using: java FileDemo pathname");
   
  }
 }

}

  File類主要是文件的抽象代表,若要作文件輸出/輸入,必須配合其他相關類來使用.

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