io包支持Java的基本I/O(輸入/輸出)系統,包括文件的輸入/輸出。對輸入/輸出的支持是來(lái)源于Java的內核API庫,而不是語(yǔ)言關(guān)鍵字。
一、輸入/輸出基礎
很多實(shí)際的Java應用程序不是基于文本的控制臺程序。盡管基于文本的程序作為教學(xué)實(shí)例是很出色的,它們無(wú)法勝任JAVA在實(shí)際中的重要應用。Java對外設輸入/輸出的支持也是有限的,并且用起來(lái)有些笨拙——甚至是在簡(jiǎn)單的例子程序中?;谖谋镜目刂婆_輸入/輸出對于Java程序并不是十分重要。
Java 提供了與文件和網(wǎng)絡(luò )相關(guān)的強大的和靈活的輸入/輸出支持,Java的輸入/輸出系統是緊密相連并且是具有一致性的。
1.1 流的概念
Java程序通過(guò)流來(lái)完成輸入/輸出。流是生產(chǎn)或消費信息的抽象。流通過(guò)Java的輸入/輸出系統與物理設備鏈接。盡管與它們鏈接的物理設備不盡相同,所有流的行為具有同樣的方式。這樣,相同的輸入/輸出類(lèi)和方法適用于所有類(lèi)型的外部設備。這意味著(zhù)一個(gè)輸入流能夠抽象多種不同類(lèi)型的輸入:從磁盤(pán)文件,從鍵盤(pán)或從網(wǎng)絡(luò )套接字。同樣,一個(gè)輸出流可以輸出到控制臺,磁盤(pán)文件或相連的網(wǎng)絡(luò )。流是處理輸入/輸出的一個(gè)潔凈的方法,例如它不需要代碼理解鍵盤(pán)和網(wǎng)絡(luò )的不同。Java中流的實(shí)現是在java.io包定義的類(lèi)層次結構內部的。
1.2 字節流和字符流
要使用流類(lèi),必須導入Java.io包。Java 2 定義了兩種類(lèi)型的流:字節類(lèi)和字符類(lèi)。字節流(byte stream)為處理字節的輸入和輸出提供了方便的方法。例如使用字節流讀取或書(shū)寫(xiě)二進(jìn)制數據。字符流(character stream)為字符的輸入和輸出處理提供了方便。它們采用了統一的編碼標準,因而可以國際化。在某些場(chǎng)合,字符流比字節流更有效。在最底層,所有的輸入/輸出都是字節形式的?;谧址牧髦粸樘幚碜址峁┓奖阌行У姆椒?。下面是對字節流和字符流的概述。
1.2.1 字節流類(lèi)
字節流由兩個(gè)類(lèi)層次結構定義。在頂層有兩個(gè)抽象類(lèi):InputStream 和 OutputStream。每個(gè)抽象類(lèi)都有多個(gè)具體的子類(lèi),這些子類(lèi)對不同的外設進(jìn)行處理,例如磁盤(pán)文件,網(wǎng)絡(luò )連接,甚至是內存緩沖區。字節流類(lèi)顯示于表1-1中。
表1-1 字節流類(lèi)
流類(lèi) 含義
BufferedInputStream緩沖輸入流
BufferedOutputStream緩沖輸出流
ByteArrayInputStream從字節數組讀取的輸入流
ByteArrayOutputStream向字節數組寫(xiě)入的輸出流
DataInputStream包含讀取Java標準數據類(lèi)型方法的輸入流
DataOutputStream包含編寫(xiě)Java標準數據類(lèi)型方法的輸出流
FileInputStream讀取文件的輸入流
FileOutputStream寫(xiě)文件的輸出流
FilterInputStream實(shí)現InputStream
FilterOutputStream實(shí)現OutputStream
InputStream描述流輸入的抽象類(lèi)
OutputStream描述流輸出的抽象類(lèi)
PipedInputStream輸入管道
PipedOutputStream輸出管道
PrintStream包含print()和println()的輸出流
PushbackInputStream 支持向輸入流返回一個(gè)字節的單字節的“unget”的輸入流
RandomAccessFile支持隨機文件輸入/輸出
SequenceInputStream兩個(gè)或兩個(gè)以上順序讀取的輸入流組成的輸入流
抽象類(lèi)InputStream 和 OutputStream定義了實(shí)現其他流類(lèi)的關(guān)鍵方法。最重要的兩種方法是read()和write(),它們分別對數據的字節進(jìn)行讀寫(xiě)。兩種方法都在InputStream 和OutputStream中被定義為抽象方法。它們被派生的流類(lèi)重載。
1.2.2 字符流類(lèi)
字符流類(lèi)由兩個(gè)類(lèi)層次結構定義。頂層有兩個(gè)抽象類(lèi):Reader和Writer。這些抽象類(lèi)處理統一編碼的字符流。Java中這些類(lèi)含有多個(gè)具體的子類(lèi)。字符流類(lèi)如表1-2所示。
表1-2 字符流的輸入/輸出類(lèi)
抽象類(lèi)Reader和Writer定義了幾個(gè)實(shí)現其他流類(lèi)的關(guān)鍵方法。其中兩個(gè)最重要的是read()和write(),它們分別進(jìn)行字符數據的讀和寫(xiě)。這些方法被派生流類(lèi)重載。
1.3 預定義流
所有的Java程序自動(dòng)導入java.lang包。該包定義了一個(gè)名為System的類(lèi),該類(lèi)封裝了運行時(shí)環(huán)境的多個(gè)方面。System 同時(shí)包含三個(gè)預定義的流變量,in,out和err。這些成員在System中是被定義成public 和static型的,這意味著(zhù)它們可以不引用特定的System對象而被用于程序的其他部分。
System.out是標準的輸出流。默認情況下,它是一個(gè)控制臺。System.in是標準輸入,默認情況下,它指的是鍵盤(pán)。System.err指的是標準錯誤流,它默認是控制臺。然而,這些流可以重定向到任何兼容的輸入/輸出設備。System.in 是inputStream的對象;System.out和System.err是PrintStream的對象。它們都是字節流,盡管它們用來(lái)讀寫(xiě)外設的字符。但可以用基于字符的流來(lái)包裝它們。
二、讀取控制臺輸入
在Java 1.0中,完成控制臺輸入的惟一途徑是字節流,使用該方法的老代碼依然存在。今天,運用字節流讀取控制臺輸入在技術(shù)上仍是可行的,但這樣做需要用到不被贊成的方法,這種做法不值得推薦。Java 2中讀取控制臺輸入的首選方法是字符流,它使程序容易符合國際標準并且易于維護。
Java沒(méi)有像標準C的函數scanf()或C++輸入操作符那樣的統一的控制臺輸入方法。Java中,控制臺輸入由從System.in讀取數據來(lái)完成。為獲得屬于控制臺的字符流,在BufferedReader對象中包裝System.in。BufferedReader 支持緩沖輸入流。它最常見(jiàn)的構造函數如下:
BufferedReader(Reader inputReader)
這里,inputReader是鏈接被創(chuàng )建的BufferedReader實(shí)例的流。Reader是一個(gè)抽象類(lèi)。它的一個(gè)具體的子類(lèi)是InputStreamReader,該子類(lèi)把字節轉換成字符。為獲得鏈接System.in的一個(gè)InputStreamReader的對象,用下面的構造函數:
InputStreamReader(InputStream inputStream)
因為System .in引用了InputStream 類(lèi)型的對象,它可以用于inputStream。綜上所述,下面的一行代碼創(chuàng )建了與鍵盤(pán)相連的BufferedReader對象。
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
當該語(yǔ)句執行后,br是通過(guò)System.in生成的鏈接控制臺的字符流。
2.1 讀取字符
從BufferedReader讀取字符,用read()。這里所用的read()版本如下:
int read( ) throws IOException
該方法每次執行都從輸入流讀取一個(gè)字符然后以整型返回。當遇到流的末尾時(shí)它返回-1??梢钥吹?,它要引發(fā)一個(gè)IOException異常。下面的例程演示了read()方法,從控制臺讀取字符直到用戶(hù)鍵入“q”:
// Use a BufferedReader to read characters from the console.
import java.io.*;
class BRRead {
public static void main(String args[])
throws IOException
{
char c;
BufferedReader br = new
BufferedReader(new InputStreamReader(System.in));
System.out.println("Enter characters, 'q' to quit.");
// read characters
do {
c = (char) br.read();
System.out.println(c);
} while(c != 'q');
}
}
下面是程序運行:
Enter characters, 'q' to quit.
123abcq
1
2
3
a
b
c
q
2.2 讀取字符串
從鍵盤(pán)讀取字符串,使用readLine()。它是BufferedReader 類(lèi)的成員。它的通常形式如下:
String readLine( ) throws IOException
它返回一個(gè)String對象。下面的例子闡述了BufferedReader類(lèi)和readLine()方法;程序讀取和顯示文本的行直到鍵入“stop”:
// Read a string from console using a BufferedReader.
import java.io.*;
class BRReadLines {
public static void main(String args[])
throws IOException
{
// create a BufferedReader using System.in
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String str;
System.out.println("Enter lines of text.");
System.out.println("Enter 'stop' to quit.");
do {
str = br.readLine();
System.out.println(str);
} while(!str.equals("stop"));
}
}
下面的例程生成了一個(gè)小文本編輯器。它創(chuàng )建了一個(gè)String對象的數組,然后依行讀取文本,把文本每一行存入數組。它將讀取到100行或直到按“stop”才停止。該例運用一個(gè)BufferedReader類(lèi)來(lái)從控制臺讀取數據。
// A tiny editor.
import java.io.*;
class TinyEdit {
public static void main(String args[])
throws IOException
{
// create a BufferedReader using System.in
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String str[] = new String[100];
System.out.println("Enter lines of text.");
System.out.println("Enter 'stop' to quit.");
for(int i=0; i<100; i++) {
str[i] = br.readLine();
if(str[i].equals("stop")) break;
}
System.out.println("\nHere is your file:");
// display the lines
for(int i=0; i<100; i++) {
if(str[i].equals("stop")) break;
System.out.println(str[i]);
}
}
}
下面是輸出部分:
Enter lines of text.
Enter ‘stop’ to quit.
This is line one.
This is line two.
Java makes working with strings easy.
Just create String objects.
stop
Here is your file:
This is line one.
This is line two.
Java makes working with strings easy.
Just create String objects.
三、向控制臺寫(xiě)輸出
控制臺輸出由print( ) 和 println( )來(lái)完成最為簡(jiǎn)單。這兩種方法由PrintStream(System.out引用的對象類(lèi)型)定義。盡管System.out是一個(gè)字節流,用它作為簡(jiǎn)單程序的輸出是可行的。因為PrintStream是從OutputStream派生的輸出流,它同樣實(shí)現低級方法write(),write()可用來(lái)向控制臺寫(xiě)數據。PrintStream 定義的write( )的最簡(jiǎn)單的形式如下:
void write(int byteval)
該方法按照byteval指定的數目向文件寫(xiě)字節。盡管byteval 定義成整數,但只有低位的8個(gè)字節被寫(xiě)入。下面的短例用 write()向屏幕輸出字符“A”,然后是新的行。
// Demonstrate System.out.write().
class WriteDemo {
public static void main(String args[]) {
int b;
b = 'A';
System.out.write(b);
System.out.write('\n');
}
}
一般不常用write()來(lái)完成向控制臺的輸出(盡管這樣做在某些場(chǎng)合非常有用),因為print()和println() 更容易用。
四、PrintWriter類(lèi)
盡管Java允許用System.out向控制臺寫(xiě)數據,但建議僅用在調試程序時(shí)或在例程中。對于實(shí)際的程序,Java推薦的向控制臺寫(xiě)數據的方法是用PrintWriter流。PrintWriter是基于字符的類(lèi)。用基于字符類(lèi)向控制臺寫(xiě)數據使程序更為國際化。PrintWriter定義了多個(gè)構造函數,這里所用到的一個(gè)如下:
PrintWriter(OutputStream outputStream, boolean flushOnNewline)
outputStream是OutputStream類(lèi)的對象,flushOnNewline控制Java是否在println()方法被調用時(shí)刷新輸出流。如果flushOnNewline為true,刷新自動(dòng)發(fā)生,若為false,則不發(fā)生。
PrintWriter支持所有類(lèi)型(包括Object)的print( )和println( )方法,這樣,就可以像用System.out那樣用這些方法。如果遇到不同類(lèi)型的情況,PrintWriter方法調用對象的toString()方法并打印結果。用PrintWriter向外設寫(xiě)數據,指定輸出流為System.out并在每一新行后刷新流。例如這行代碼創(chuàng )建了與控制臺輸出相連的PrintWriter類(lèi)。
PrintWriter pw = new PrintWriter(System.out, true);
下面的應用程序說(shuō)明了用PrintWriter處理控制臺輸出的方法:
// Demonstrate PrintWriter
import java.io.*;
public class PrintWriterDemo {
public static void main(String args[]) {
PrintWriter pw = new PrintWriter(System.out, true);
pw.println("This is a string");
int i = -7;
pw.println(i);
double d = 4.5e-7;
pw.println(d);
}
}
該程序的輸出如下:
This is a string
-7
4.5E-7
五、文件的讀寫(xiě)
Java提供了一系列的讀寫(xiě)文件的類(lèi)和方法。在Java中,所有的文件都是字節形式的。Java提供從文件讀寫(xiě)字節的方法。而且,Java允許在字符形式的對象中使用字節文件流。
兩個(gè)最常用的流類(lèi)是FileInputStream和FileOutputStream,它們生成與文件鏈接的字節流。為打開(kāi)文件,只需創(chuàng )建這些類(lèi)中某一個(gè)類(lèi)的一個(gè)對象,在構造函數中以參數形式指定文件的名稱(chēng)。這兩個(gè)類(lèi)都支持其他形式的重載構造函數。下面是這里將要用到的形式:
FileInputStream(String fileName) throws FileNotFoundException
FileOutputStream(String fileName) throws FileNotFoundException
fileName指定需要打開(kāi)的文件名。當創(chuàng )建了一個(gè)輸入流而文件不存在時(shí),引發(fā)FileNotFoundException異常。對于輸出流,如果文件不能生成,則引發(fā)FileNotFoundException異常。如果一個(gè)輸出文件被打開(kāi),所有原先存在的同名的文件被破壞。注意:在早期的Java版本中,當輸出文件不能創(chuàng )建時(shí)FileOutputStream()引發(fā)一個(gè)IOException異常。這在Java 2中有所修改。當對文件的操作結束后,需要調用close( )來(lái)關(guān)閉文件。該方法在FileInputStream和FileOutputStream中都有定義。如下:
void close( ) throws IOException
為讀文件,可以使用在FileInputStream中定義的read( )方法。這里用到的如下:
int read( ) throws IOException
該方法每次被調用,它僅從文件中讀取一個(gè)字節并將該字節以整數形式返回。當讀到文件尾時(shí),read( )返回-1。該方法可以引發(fā)IOException異常。下面的程序用read()來(lái)輸入和顯示文本文件的內容,該文件名以命令行形式指定。注意try/catch塊處理程序運行時(shí)可能發(fā)生的兩個(gè)錯誤——未找到指定的文件或用戶(hù)忘記包括文件名了。
import java.io.*;
class ShowFile {
public static void main(String args[])
throws IOException
{
int i;
FileInputStream fin;
try {
fin = new FileInputStream(args[0]);
} catch(FileNotFoundException e) {
System.out.println("File Not Found");
return;
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("Usage: ShowFile File");
return;
}
// read characters until EOF is encountered
do {
i = fin.read();
if(i != -1) System.out.print((char) i);
} while(i != -1);
fin.close();
}
}
向文件中寫(xiě)數據,需用FileOutputStream定義的write()方法。它的最簡(jiǎn)單形式如下:
void write(int byteval) throws IOException
該方法按照byteval指定的數目向文件寫(xiě)入字節。盡管byteval作為整數聲明,但僅低8位字節可以寫(xiě)入文件。如果在寫(xiě)的過(guò)程中出現問(wèn)題,一個(gè)IOException被引發(fā)。下面的例子用write()拷貝一個(gè)文本文件:
import java.io.*;
class CopyFile {
public static void main(String args[])
throws IOException
{
int i;
FileInputStream fin;
FileOutputStream fout;
try {
// open input file
try {
fin = new FileInputStream(args[0]);
} catch(FileNotFoundException e) {
System.out.println("Input File Not Found");
return;
}
// open output file
try {
fout = new FileOutputStream(args[1]);
} catch(FileNotFoundException e) {
System.out.println("Error Opening Output File");
return;
}
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("Usage: CopyFile From To");
return;
}
// Copy File
try {
do {
i = fin.read();
if(i != -1) fout.write(i);
} while(i != -1);
} catch(IOException e) {
System.out.println("File Error");
}
fin.close();
fout.close();
}
}
注意本程序中和前面ShowFile程序中處理潛在輸入/輸出錯誤的方法。不像其他的計算機語(yǔ)言,包括C和C++,這些語(yǔ)言用錯誤代碼報告文件錯誤,而Java用異常處理機制。這樣不僅是文件處理更為簡(jiǎn)潔,而且使Java正在執行輸入時(shí)容易區分是文件出錯還是EOF條件問(wèn)題。在C/C++中,很多輸入函數在出錯時(shí)和到達文件結尾時(shí)返回相同的值(也就是說(shuō),在C/C++中,EOF情況與輸入錯誤情況映射相同)。這通常意味著(zhù)程序員必須還要編寫(xiě)特殊程序語(yǔ)句來(lái)判定究竟是哪種事件發(fā)生。Java中,錯誤通過(guò)異常引發(fā),而不是通過(guò)read()的返回值。這樣,當read( )返回-1時(shí),它僅表示一點(diǎn):遇到了文件的結尾。