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類主要是文件的抽象代表,若要作文件輸出/輸入,必須配合其他相關類來使用.