概述
java.io 包幾乎包含了所有操作輸入、輸出需要的類。所有這些流類代表了輸入源和輸出目標。java.io 包中的流支持很多種格式,比如:基本類型、對象、本地化字符集等等。一個流可以理解為一個數(shù)據(jù)的序列。輸入流表示從一個源讀取數(shù)據(jù),輸出流表示向一個目標寫數(shù)據(jù)。Java 為 I/O 提供了強大的而靈活的支持,使其更廣泛地應(yīng)用到文件傳輸和網(wǎng)絡(luò)編程中。
Java 的 I/O 大概可以分成以下幾類:
- 磁盤操作:File
- 字節(jié)操作:InputStream 和 OutputStream
- 字符操作:Reader 和 Writer
- 對象操作:Serializable
- 網(wǎng)絡(luò)操作:Socket
- 新的輸入/輸出:NIO
File
Java中IO操作有相應(yīng)步驟,以文件操作為例,主要操作流程如下:
1.使用File類打開一個文件
2.通過字節(jié)流或字符流的子類,指定輸出的位置
3.進行讀/寫操作
4.關(guān)閉輸入/輸出
那么我們先來介紹一下File類
Java文件類在java.io包中,它以抽象的方式代表文件名和目錄路徑名。該類主要用于獲取文件和目錄的屬性,文件和目錄的創(chuàng)建、查找、刪除、重命名等,但不能進行文件的讀寫操作。
File對象代表磁盤中實際存在的文件和目錄。通過以下構(gòu)造方法創(chuàng)建一個File對象。
通過給定的父抽象路徑名和子路徑名字符串創(chuàng)建一個新的File實例。
File(File parent, String child)
通過將給定路徑名字符串轉(zhuǎn)換成抽象路徑名來創(chuàng)建一個新 File 實例。
File(String pathname)
根據(jù) parent 路徑名字符串和 child 路徑名字符串創(chuàng)建一個新 File 實例。
File(String parent, String child)
通過將給定的 file: URI 轉(zhuǎn)換成一個抽象路徑名來創(chuàng)建一個新的 File 實例。
File(URI uri)
注意:
1.在各個操作系統(tǒng)中,路徑的分隔符是不一樣的,例如:Windows中使用反斜杠:"\
",Linux|Unix中使用正斜杠:"/
"。在使用反斜杠時要寫成"\\
"的形式,因為反斜杠要進行轉(zhuǎn)義。如果要讓Java保持可移植性,應(yīng)該使用File類的靜態(tài)常量File.pathSeparator。
2.構(gòu)建一個File實例并不會在機器上創(chuàng)建一個文件。不管文件是否存在,都可以創(chuàng)建任意文件名的File實例。可以調(diào)用File實例上的exists()方法來判斷這個文件是否存在。通過后續(xù)的學(xué)習我們會知道,當把一個輸出流綁定到一個不存在的File實例上時,會自動在機器上創(chuàng)建該文件,如果文件已經(jīng)存在,把輸出流綁定到該文件上則會覆蓋該文件,但這些都不是在創(chuàng)建File實例時進行的。
創(chuàng)建File對象成功后,可以使用以下列表中的方法操作文件。
下面給出一個使用File類的實例:
import java.io.File;
public class DirList {
public static void main(String args[]) {
String dirname = "/java";
File f1 = new File(dirname);
if (f1.isDirectory()) {
System.out.println( "Directory of " + dirname);
String s[] = f1.list();
for (int i=0; i < s.length; i++) {
File f = new File(dirname + "/" + s[i]);
if (f.isDirectory()) {
System.out.println(s[i] + " is a directory");
} else {
System.out.println(s[i] + " is a file");
}
}
} else {
System.out.println(dirname + " is not a directory");
}
}
}
小貼士:lastModified()方法返回的是從時間戳(1970年1月1日0時0分0秒)到當前的毫秒數(shù),返回值類型是long,可以用Date類對它進行包裝使其更易讀。
Java中的目錄
創(chuàng)建目錄:
File類中有兩個方法可以用來創(chuàng)建文件夾:
- mkdir( )方法創(chuàng)建一個文件夾,成功則返回true,失敗則返回false。失敗表明File對象指定的路徑已經(jīng)存在,或者由于整個路徑還不存在,該文件夾不能被創(chuàng)建。
- mkdirs()方法創(chuàng)建一個文件夾和它的所有父文件夾。
下面的例子創(chuàng)建 "/tmp/user/java/bin"文件夾:
import java.io.File;
public class CreateDir {
public static void main(String args[]) {
String dirname = "/tmp/user/java/bin";
File d = new File(dirname);
// 現(xiàn)在創(chuàng)建目錄
d.mkdirs();
}
}
mkdirs是遞歸創(chuàng)建文件夾,允許在創(chuàng)建某文件夾時其父文件夾不存在,從而一同創(chuàng)建;mkdir必須滿足路徑上的父文件夾全都存在
注意: Java 在 UNIX 和 Windows 自動按約定分辨文件路徑分隔符。如果你在 Windows 版本的 Java 中使用分隔符 (/) ,路徑依然能夠被正確解析。
讀取目錄:
一個目錄其實就是一個 File 對象,它包含其他文件和文件夾。
如果創(chuàng)建一個 File 對象并且它是一個目錄,那么調(diào)用 isDirectory() 方法會返回 true。
可以通過調(diào)用該對象上的 list() 方法,來提取它包含的文件和文件夾的列表。
下面展示的例子說明如何使用 list() 方法來檢查一個文件夾中包含的內(nèi)容:
import java.io.File;
public class DirList {
public static void main(String args[]) {
String dirname = "/tmp";
File f1 = new File(dirname);
if (f1.isDirectory()) {
System.out.println( "目錄 " + dirname);
String s[] = f1.list();
for (int i=0; i < s.length; i++) {
File f = new File(dirname + "/" + s[i]);
if (f.isDirectory()) {
System.out.println(s[i] + " 是一個目錄");
} else {
System.out.println(s[i] + " 是一個文件");
}
}
} else {
System.out.println(dirname + " 不是一個目錄");
}
}
}
刪除目錄或文件:
刪除文件可以使用 java.io.File.delete() 方法。
以下代碼會刪除目錄/tmp/java/,即便目錄不為空。
測試目錄結(jié)構(gòu):
/tmp/java/
|-- 1.log
|-- test
deleteFolder是一個遞歸函數(shù),類似于DFS思想
import java.io.File;
public class DeleteFileDemo {
public static void main(String args[]) {
// 這里修改為自己的測試目錄
File folder = new File("/tmp/java/");
deleteFolder(folder);
}
//刪除文件及目錄
public static void deleteFolder(File folder) {
File[] files = folder.listFiles();
if(files!=null) {
for(File f: files) {
if(f.isDirectory()) {
deleteFolder(f);
} else {
f.delete();
}
}
}
folder.delete();
}
}
RandomAccessFile
RandomAccessFile不同于File,它提供了對文件內(nèi)容的訪問,可以讀寫文件且支持隨機訪問文件的任意位置。
RandomAccessFile讀寫用到文件指針,它的初始位置為0,可以用getFilePointer()方法獲取文件指針的位置。下面是RandomAccessFile常用的方法。
public int read(int x) throws IOException 方法只讀取一個字節(jié),也就是x的低八位。
import java.io.File ;
import java.io.RandomAccessFile ;
public class RandomAccessFileDemo01{
// 所有的異常直接拋出,程序中不再進行處理
public static void main(String args[]) throws Exception{
File f = new File("d:" + File.separator + "test.txt") ; // 指定要操作的文件
RandomAccessFile rdf = null ; // 聲明RandomAccessFile類的對象
rdf = new RandomAccessFile(f,"rw") ;// 讀寫模式,如果文件不存在,會自動創(chuàng)建
String name = null ;
int age = 0 ;
name = "zhangsan" ; // 字符串長度為8
age = 30 ; // 數(shù)字的長度為4
rdf.writeBytes(name) ; // 將姓名寫入文件之中
rdf.writeInt(age) ; // 將年齡寫入文件之中
name = "lisi " ; // 字符串長度為8
age = 31 ; // 數(shù)字的長度為4
rdf.writeBytes(name) ; // 將姓名寫入文件之中
rdf.writeInt(age) ; // 將年齡寫入文件之中
name = "wangwu " ; // 字符串長度為8
age = 32 ; // 數(shù)字的長度為4
rdf.writeBytes(name) ; // 將姓名寫入文件之中
rdf.writeInt(age) ; // 將年齡寫入文件之中
rdf.close() ; // 關(guān)閉
}
};
寫完之后,開始讀取數(shù)據(jù)。寫的時候可以將一個字符串寫入,讀的時候需要一個個的以字節(jié)的形式讀取出來。
import java.io.File ;
import java.io.RandomAccessFile ;
public class RandomAccessFileDemo02{
// 所有的異常直接拋出,程序中不再進行處理
public static void main(String args[]) throws Exception{
File f = new File("d:" + File.separator + "test.txt") ; // 指定要操作的文件
RandomAccessFile rdf = null ; // 聲明RandomAccessFile類的對象
rdf = new RandomAccessFile(f,"r") ;// 以只讀的方式打開文件
String name = null ;
int age = 0 ;
byte b[] = new byte[8] ; // 開辟byte數(shù)組
// 讀取第二個人的信息,意味著要空出第一個人的信息
rdf.skipBytes(12) ; // 跳過第一個人的信息
for(int i=0;i<b.length;i++){
b[i] = rdf.readByte() ; // 讀取一個字節(jié)
}
name = new String(b) ; // 將讀取出來的byte數(shù)組變?yōu)樽址? age = rdf.readInt() ; // 讀取數(shù)字
System.out.println("第二個人的信息 --> 姓名:" + name + ";年齡:" + age) ;
// 讀取第一個人的信息
rdf.seek(0) ; // 指針回到文件的開頭
for(int i=0;i<b.length;i++){
b[i] = rdf.readByte() ; // 讀取一個字節(jié)
}
name = new String(b) ; // 將讀取出來的byte數(shù)組變?yōu)樽址? age = rdf.readInt() ; // 讀取數(shù)字
System.out.println("第一個人的信息 --> 姓名:" + name + ";年齡:" + age) ;
rdf.skipBytes(12) ; // 跳過第二個人的信息
for(int i=0;i<b.length;i++){
b[i] = rdf.readByte() ; // 讀取一個字節(jié)
}
name = new String(b) ; // 將讀取出來的byte數(shù)組變?yōu)樽址? age = rdf.readInt() ; // 讀取數(shù)字
System.out.println("第三個人的信息 --> 姓名:" + name + ";年齡:" + age) ;
rdf.close() ; // 關(guān)閉
}
};
結(jié)果如下:
流
在Java程序中所有的數(shù)據(jù)都是以流的方式進行傳輸或保存的,程序需要數(shù)據(jù)的時候要使用輸入流讀取數(shù)據(jù),而當程序需要將一些數(shù)據(jù)保存起來的時候,就要使用輸出流完成。程序中的輸入輸出都是以流的形式保存的,流中保存的實際上全都是字節(jié)文件。流涉及的領(lǐng)域很廣:標準輸入輸出,文件的操作,網(wǎng)絡(luò)上的數(shù)據(jù)流,字符串流,對象流,zip文件流等等。
流具有方向性,至于是輸入流還是輸出流則是一個相對的概念,一般以程序為參考,如果數(shù)據(jù)的流向是程序至設(shè)備,我們成為輸出流,反之我們稱為輸入流。
可以將流想象成一個“水流管道”,水流就在這管道中形成了,自然就出現(xiàn)了方向的概念。
先上一個Java IO流類層次圖,如前所述,一個流被定義為一個數(shù)據(jù)序列。輸入流用于從源讀取數(shù)據(jù),輸出流用于向目標寫數(shù)據(jù):
是不是被嚇到了?沒關(guān)系,我們將通過一個個例子來學(xué)習這些功能。
IO流分類
1.按操作數(shù)據(jù)類型分:字符流和字節(jié)流
編碼與解碼:編碼就是把字符轉(zhuǎn)換為字節(jié),而解碼是把字節(jié)重新組合成字符。
如果編碼和解碼過程使用不同的編碼方式那么就出現(xiàn)了亂碼。
- GBK 編碼中,中文字符占 2 個字節(jié),英文字符占 1 個字節(jié);
- UTF-8 編碼中,中文字符占 3 個字節(jié),英文字符占 1 個字節(jié);
- UTF-16be 編碼中,中文字符和英文字符都占 2 個字節(jié)。
UTF-16be 中的 be 指的是 Big Endian,也就是大端。相應(yīng)地也有 UTF-16le,le 指的是 Little Endian,也就是小端。
Java 使用雙字節(jié)編碼 UTF-16be,這不是指 Java 只支持這一種編碼方式,而是說 char 這種類型使用 UTF-16be 進行編碼。char 類型占 16 位,也就是兩個字節(jié),Java 使用這種雙字節(jié)編碼是為了讓一個中文或者一個英文都能使用一個 char 來存儲。
字符流:Java中的字符流處理的最基本的單元是2字節(jié)的Unicode碼元(char),它通常用來處理文本數(shù)據(jù),如字符、字符數(shù)組或字符串等。所謂Unicode碼元,也就是一個Unicode代碼單元,范圍是0x0000~0xFFFF。在以上范圍內(nèi)的每個數(shù)字都與一個字符相對應(yīng),Java中的String類型默認就把字符以Unicode規(guī)則編碼而后存儲在內(nèi)存中。然而與存儲在內(nèi)存中不同,存儲在磁盤上的數(shù)據(jù)通常有著各種各樣的編碼方式。使用不同的編碼方式,相同的字符會有不同的二進制表示。實際上字符流是這樣工作的:
- 輸出字符流:把要寫入文件的字符序列(實際上是Unicode碼元序列)轉(zhuǎn)為指定編碼方式下的字節(jié)序列,然后再寫入到文件中。
- 輸入字符流:把要讀取的字節(jié)序列按指定編碼方式解碼為相應(yīng)字符序列(實際上是Unicode碼元序列從)從而可以存在內(nèi)存中。
也就是說,所有的文件在硬盤或在傳輸時都是以字節(jié)的方式進行的,包括圖片等都是按字節(jié)的方式存儲的,而字符是只有在內(nèi)存中才會形成。
字節(jié)流:Java中的字節(jié)流處理的最基本單位為單個字節(jié)(byte),它通常用來處理二進制數(shù)據(jù),如果要得到字節(jié)對應(yīng)的字符需要強制類型轉(zhuǎn)換。
兩者比較:
1.字符流是由Java虛擬機將字節(jié)轉(zhuǎn)化為2個字節(jié)的Unicode字符為單位的字符而成的,所以它對多國語言支持性較好,如果要操作中文數(shù)據(jù)等,用字符流。
2.字符流只用來處理文本數(shù)據(jù),字節(jié)流還可以用來處理媒體數(shù)據(jù),如視頻、音頻、圖片等。
3.字符流的兩個抽象基類為Reader和Writer,字節(jié)流的兩個抽象基類為InputStream和OutputStream。它們的具體子類名以基類名為后綴進行擴展。
4.字節(jié)流在操作的時候不會用到緩沖區(qū)(內(nèi)存),是直接對文件本身操作的,而字符流在操作的時候使用緩沖區(qū)。
以向一個文件輸出"Hello world!"為例,我們分別使用字節(jié)流和字符流進行輸出,且在使用完之后都不關(guān)閉流。
使用字節(jié)流不關(guān)閉執(zhí)行:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class IOPractice {
public static void main(String[] args) throws IOException {
// 第1步:使用File類找到一個文件
File f = new File("/home/xiejunyu/"+
"桌面/text.txt");
// 第2步:通過子類實例化父類對象
OutputStream out = new FileOutputStream(f);
// 通過對象多態(tài)性進行實例化
// 第3步:進行寫操作
String str = "Hello World!";
// 準備一個字符串
byte b[] = str.getBytes();
// 字符串轉(zhuǎn)byte數(shù)組
out.write(b);
// 將內(nèi)容輸出
// 第4步:關(guān)閉輸出流
// out.close();
// 此時沒有關(guān)閉
}
}
此時沒有關(guān)閉字節(jié)流操作,但是文件中也依然存在了輸出的內(nèi)容,證明字節(jié)流是直接操作文件本身的。
使用字符流不關(guān)閉執(zhí)行:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class IOPractice {
public static void main(String[] args) throws IOException {
// 第1步:使用File類找到一個文件
File f = new File("/home/xiejunyu/桌面/test.txt");
// 第2步:通過子類實例化父類對象
Writer out = new FileWriter(f);
// 第3步:進行寫操作
String str = "Hello World!";
// 準備一個字符串
out.write(str);
// 將內(nèi)容輸出
// 第4步:關(guān)閉輸出流
// out.close();
// 此時沒有關(guān)閉
}
}
程序運行后會發(fā)現(xiàn)文件中沒有任何內(nèi)容,這是因為字符流操作時使用了緩沖區(qū),而在關(guān)閉字符流時會強制性地將緩沖區(qū)中的內(nèi)容進行輸出,但是如果程序沒有關(guān)閉字符流,緩沖區(qū)中的內(nèi)容是無法輸出的,所以得出結(jié)論:字符流使用了緩沖區(qū),而字節(jié)流沒有使用緩沖區(qū)。如果想讓緩沖區(qū)中的內(nèi)容輸出,要么關(guān)閉流強制刷新緩沖區(qū),要么調(diào)用flush方法沖刷緩沖區(qū)。可以簡單地把緩沖區(qū)理解為一段特殊的內(nèi)存。某些情況下,如果一個程序頻繁地操作一個資源(如文件或數(shù)據(jù)庫),則性能會很低,此時為了提升性能,就可以將一部分數(shù)據(jù)暫時讀入到內(nèi)存的一塊區(qū)域之中,以后直接從此區(qū)域中讀取數(shù)據(jù)即可,因為讀取內(nèi)存速度會比較快,這樣可以提升程序的性能。
在字符流的操作中,所有的字符都是在內(nèi)存中形成的,在輸出前會將所有的內(nèi)容暫時保存在內(nèi)存之中,所以使用了緩沖區(qū)暫存數(shù)據(jù)。
建議:
1.雖然不關(guān)閉字節(jié)流不影響數(shù)據(jù)的輸出,且后續(xù)JVM會自動回收這部分內(nèi)存,但還是建議在使用完任何流對象之后關(guān)閉流。
2.使用流對象都要聲明或拋出IOException
3.在創(chuàng)建一個文件時,如果目錄下有同名文件將被覆蓋
4.在寫文件時,如果文件不存在,會在創(chuàng)建輸出流對象并綁定文件時自動創(chuàng)建文件,不必使用File的exists方法提前檢測
4.在讀取文件時,必須使用File的exists方法提前檢測來保證該文件已存在,否則拋出FileNotFoundException
2.按流向分:輸入流和輸出流
輸入流:程序從輸入流讀取數(shù)據(jù)源。數(shù)據(jù)源包括外界(鍵盤、文件、網(wǎng)絡(luò)等),即是將數(shù)據(jù)源讀入到程序的通信通道。輸入流主要包括兩個抽象基類:InputStream(字節(jié)輸入流)和Reader(字符輸入流)及其擴展的具體子類。
輸出流:程序向輸出流寫入數(shù)據(jù)。將程序中的數(shù)據(jù)輸出到外界(顯示器、打印機、文件、網(wǎng)絡(luò)等)的通信通道。輸出流主要包括兩個抽象基類:OutputStream(字節(jié)輸出流)和Writer(字符輸出流)及其擴展的具體子類。
3.按功能分:節(jié)點流和處理流
按照流是否直接與特定的地方(如磁盤、內(nèi)存、設(shè)備等)相連,分為節(jié)點流和處理流兩類。
節(jié)點流:程序用于直接操作目標設(shè)備所對應(yīng)的類叫節(jié)點流。(低級流)
處理流:程序通過一個間接流類去調(diào)用節(jié)點流類,以達到更加靈活方便地讀寫各種類型的數(shù)據(jù),這個間接流類就是處理流。處理流可以看成是對已存在的流進行連接和封裝的流。(高級流)
注意:在使用到處理流對流進行連接和封裝時,讀寫完畢只需關(guān)閉處理流,不用關(guān)閉節(jié)點流。處理流關(guān)閉的時候,會調(diào)用其處理的節(jié)點流的關(guān)閉方法。如果將節(jié)點流關(guān)閉以后再關(guān)閉處理流,會拋出IO異常。
(1) 節(jié)點流
- File 文件流。對文件進行讀、寫操作:FileReader、FileWriter、FileInputStream、FileOutputStream。
- Memory 流。
向內(nèi)存數(shù)組讀寫數(shù)據(jù): CharArrayReader與 CharArrayWriter、ByteArrayInputStream與ByteArrayOutputStream。
向內(nèi)存字符串讀寫數(shù)據(jù):StringReader、StringWriter、StringBufferInputStream。 - Pipe管道流:實現(xiàn)管道的輸入和輸出(進程間通信): PipedReader與PipedWriter、PipedInputStream與PipedOutputStream。
(1) 處理流
- Buffering緩沖流:在讀入或?qū)懗鰰r,對數(shù)據(jù)進行緩存,以減少I/O的次數(shù):BufferedReader與BufferedWriter、BufferedInputStream與BufferedOutputStream。
- Filtering 濾流:在數(shù)據(jù)進行讀或?qū)憰r進行過濾:FilterReader與FilterWriter、FilterInputStream與FilterOutputStream。
- Converting between Bytes and Characters 轉(zhuǎn)換流:按照一定的編碼/解碼標準將字節(jié)流轉(zhuǎn)換為字符流,或進行反向轉(zhuǎn)換(Stream到Reader):InputStreamReader、OutputStreamWriter。
- Object Serialization 對象流 :ObjectInputStream、ObjectOutputStream。
- DataConversion數(shù)據(jù)流:按基本數(shù)據(jù)類型讀、寫(處理的數(shù)據(jù)是Java的基本類型):DataInputStream、DataOutputStream 。
- Counting計數(shù)流:在讀入數(shù)據(jù)時對行記數(shù) :LineNumberReader、LineNumberInputStream。
- Peeking Ahead預(yù)讀流: 通過緩存機制,進行預(yù)讀 :PushbackReader、PushbackInputStream。
- Printing打印流: 包含方便的打印方法 :PrintWriter、PrintStream。
讀取控制臺輸入
在Java中,從控制臺輸入有三種方法:
1.使用標準輸入流對象System.in
System.in是System中內(nèi)置的InputStream類對象,它的read方法一次只讀入一個字節(jié)數(shù)據(jù),返回0 ~ 255的一個byte值,一般用來讀取一個字符,需要強制類型轉(zhuǎn)換為char類型,而我們通常要取得一個字符串或一組數(shù)字,故這種方法不常用。下面給出這種方法的一個例子:
public class CharTest{
public static void main(String[] args) {
try{
System.out.print("Enter a Char:");
char i = (char)System.in.read();
System.out.println("Yout Enter Char is:" + i); }
catch(IOException e){
e.printStackTrace();
}
}
}
使用這種方法必須提供try-catch塊或者在main方法首部聲明IOException異常,因為System.in是一個流對象
2.使用Scanner類
Scanner類功能十分強大,可以讀入字符串、整數(shù)、浮點數(shù)、布爾類型值等等。下面是例子:
public class ScannerTest{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.println("ScannerTest, Please Enter Name:");
String name = sc.nextLine(); //讀取字符串型輸入
System.out.println("ScannerTest, Please Enter Age:");
int age = sc.nextInt(); //讀取整型輸入
System.out.println("ScannerTest, Please Enter Salary:");
float salary = sc.nextFloat(); //讀取float型輸入
System.out.println("Your Information is as below:");
System.out.println("Name:" + name +"\n" + "Age:"+age
+ "\n"+"Salary:"+salary);
}
}
注意:
1.用nextXXX()讀入XXX類型的數(shù)據(jù),XXX可以是除了char外的所有基本數(shù)據(jù)類型,還可以是BigInteger或BigDecimal,其中凡是整型類型的數(shù)據(jù)還可以指定radix(進制),可以用next()和nextLine()讀取一個字符串或一行字符
2.next()和nextLine()的區(qū)別:
next()
- 一定要讀取到有效字符后才可以結(jié)束輸入。
- 對輸入有效字符之前遇到的空白,next() 方法會自動將其去掉。
- 只有輸入有效字符后才將其后面輸入的空白作為分隔符或者結(jié)束符。
- next() 不能得到帶有空格的字符串,除非用useDelimeter方法修改分隔符。
nextLine()
- 以Enter為結(jié)束符,也就是說 nextLine()方法返回的是輸入回車之前的所有字符。
- 可以獲得空白。
3.可以用循環(huán)配合hasNextXXX方法判斷輸入是否繼續(xù)
4.Scanner類沒有直接提供讀取一個字符的方法,如果要讀取一個字符,有三種方法,一是讀入一個字符串后取字符串的第一個字符,二是使用System.in的read方法,三是使用字符流讀入
更多Scanner的用法之前已經(jīng)在Java學(xué)習總結(jié)之Java基本程序設(shè)計結(jié)構(gòu)中總結(jié)過了,不再贅述。
3.使用BufferedReader對象
可以把 System.in 包裝在一個 BufferedReader 對象中來創(chuàng)建一個字符流。
下面是創(chuàng)建 BufferedReader 的基本語法:
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
其中,System.in是一個InputStream對象(字節(jié)流),使用InputStreamReader作為橋梁,將字節(jié)流轉(zhuǎn)換為字符流,然后再使用BufferedReader進行進一步包裝。
BufferedReader 對象創(chuàng)建后,我們便可以使用 read() 方法從控制臺讀取一個字符(讀入一個用0~65535之間的整數(shù)表示的字符,需要強制類型轉(zhuǎn)換為char類型,如果已到達流末尾,則返回 -1),或者用 readLine() 方法讀取一個字符串。下面是例子:
public static void main(String[] args){
//必須要處理java.io.IOException異常
BufferedReader br = new BufferedReader(new InputStreamReader
(System.in ));
//java.io.InputStreamReader繼承了Reader類
String read = null;
System.out.print("輸入數(shù)據(jù):");
try {
read = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("輸入數(shù)據(jù):"+read);
}
下面的程序示范了用 read() 方法從控制臺不斷讀取字符直到用戶輸入 "q"。
// 使用 BufferedReader 在控制臺讀取字符
import java.io.*;
public class BRRead {
public static void main(String args[]) throws IOException
{
char c;
// 使用 System.in 創(chuàng)建 BufferedReader
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
System.out.println("輸入字符, 按下 'q' 鍵退出。");
// 讀取字符
do {
c = (char) br.read();
System.out.println(c);
} while(c != 'q');
}
}
下面的程序讀取和顯示字符行直到你輸入了單詞"end"。
// 使用 BufferedReader 在控制臺讀取字符
import java.io.*;
public class BRReadLines {
public static void main(String args[]) throws IOException
{
// 使用 System.in 創(chuàng)建 BufferedReader
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String str;
System.out.println("Enter lines of text.");
System.out.println("Enter 'end' to quit.");
do {
str = br.readLine();
System.out.println(str);
} while(!str.equals("end"));
}
}
在ACM等算法競賽中,我們常常也會使用Java,在輸入數(shù)據(jù)時有以下幾點注意:
1.hasXXX等價于C++中讀到文件末尾(EOF)
2.使用BufferedReader輸入會比Scanner輸入快十倍左右!
控制臺輸出
控制臺的輸出由 print() 和 println() 完成。這些方法都由類 PrintStream 定義,System.out 是該類的一個對象。
PrintStream 繼承了 OutputStream類,并且實現(xiàn)了方法 write()。這樣,write() 也可以用來往控制臺寫操作。
PrintStream 定義 write() 的最簡單格式如下所示:
void write(int byteval)
該方法將 byteval 的低八位字節(jié)寫到流中,即System.out的write方法一次只能寫一個字節(jié)(類比System.in的read方法一次只能讀取一個字節(jié))。
下面的例子用 write() 把字符 "A" 和緊跟著的換行符輸出到屏幕:
import java.io.*;
// 演示 System.out.write().
public class WriteDemo {
public static void main(String args[]) {
int b;
b = 'A';//向上類型轉(zhuǎn)換
System.out.write(b);
System.out.write('\n');
}
}
注意:write() 方法不經(jīng)常使用,因為 print() 和 println() 方法用起來更為方便。
字節(jié)流(OutputStream、InputStream)
字節(jié)流主要是操作byte類型的數(shù)據(jù),以byte數(shù)組為準,主要操作類是OutputStream、InputStream。
由于文件讀寫最為常見,我們先討論兩個重要的字節(jié)流 FileInputStream(文件輸入流) 和 FileOutputStream(文件輸出流),分別是抽象類InputStream和OutputStream的具體子類:
FileInputStream
該流用于從文件讀取數(shù)據(jù),它的對象可以用關(guān)鍵字 new 來創(chuàng)建。
有多種構(gòu)造方法可用來創(chuàng)建對象。
可以使用字符串類型的文件名來創(chuàng)建一個輸入流對象來讀取文件:
InputStream f = new FileInputStream("C:/java/hello");
也可以使用一個文件對象來創(chuàng)建一個輸入流對象來讀取文件。我們首先得使用 File() 方法來創(chuàng)建一個文件對象:
File f = new File("C:/java/hello");
InputStream in = new FileInputStream(f);
創(chuàng)建了InputStream對象,就可以使用下面的方法來讀取流或者進行其他的流操作。
下面是一個例子:
public static void main(String[] args) throws IOException{
InputStream f = new FileInputStream
("/home/xiejunyu/桌面/test.txt");
int c = 0;
while((c = f.read()) != -1)
//這里也可以先用available方法得到可讀的字節(jié)數(shù)
System.out.println((char)c);
}
當我們需要創(chuàng)建一個byte[]來保存讀取的字節(jié)時,如果數(shù)組太小,無法完整讀入數(shù)據(jù),如果太大又會造成內(nèi)存浪費。可以使用File類的length方法得到文件的數(shù)據(jù)字節(jié)數(shù),從而有效確定byte數(shù)組的大小。
public static void main(String[] args) {
// 創(chuàng)建一個FileInputStream對象
try {
FileInputStream fis = new FileInputStream("/home/xiejunyu/桌面/test.txt");
byte[] b=new byte[100];
fis.read(b,0,5);
/*把字節(jié)從文件讀入b數(shù)組,從b數(shù)組的0位置開始存放,
讀取5個字節(jié)*/
System.out.println(new String(b));
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
注意: 每調(diào)用一次read方法,當前讀取在文件中的位置就會向后移動一個字節(jié)或者移動byte[]的長度(read的兩個重載方法),已經(jīng)到文件末尾會返回-1,可以通過read方法返回-1判斷是否讀到文件末尾,也可以使用available方法返回下一次可以不受阻塞讀取的字節(jié)數(shù)來讀取。FileInputStream不支持mark和reset方法進行重復(fù)讀取。BufferedInputStream支持此操作。
FileOutputStream
該類用來創(chuàng)建一個文件并向文件中寫數(shù)據(jù)。
如果該流在打開文件進行輸出前,目標文件不存在,那么該流會創(chuàng)建該文件。
有兩個構(gòu)造方法可以用來創(chuàng)建 FileOutputStream 對象。
使用字符串類型的文件名來創(chuàng)建一個輸出流對象:
OutputStream f = new FileOutputStream("C:/java/hello")
也可以使用一個文件對象來創(chuàng)建一個輸出流來寫文件。我們首先得使用File()方法來創(chuàng)建一個文件對象:
File f = new File("C:/java/hello");
OutputStream f = new FileOutputStream(f);
之前的所有操作中,如果重新執(zhí)行程序,則肯定會覆蓋文件中的已有內(nèi)容,那么此時就可以通過FileOutputStream向文件中追加內(nèi)容,F(xiàn)ileOutputStream的另外一個構(gòu)造方法:
public FileOutputStream(File file,boolean append)
在構(gòu)造方法中,如果將append的值設(shè)置為true,則表示在文件的末尾追加內(nèi)容。程序代碼如下:
File f = new File("C:/java/hello");
OutputStream f = new FileOutputStream(f,true);
創(chuàng)建OutputStream 對象完成后,就可以使用下面的方法來寫入流或者進行其他的流操作。
當有一個字符串時,可以用getBytes方法轉(zhuǎn)為byte數(shù)組用于字節(jié)流的輸出。
下面是一個演示 InputStream 和 OutputStream 用法的例子:
import java.io.*;
public class FileStreamTest{
public static void main(String args[]){
try{
byte bWrite[] = "ABC".getBytes();
OutputStream os = new FileOutputStream("/home/xiejunyu/桌面/test.txt");
for(int x=0; x < bWrite.length ; x++){
os.write(bWrite[x] ); // writes the bytes
}
os.close();
InputStream is = new FileInputStream("/home/xiejunyu/桌面/test.txt");
int size = is.available();
for(int i=0; i< size; i++){
System.out.print((char)is.read() + " ");
}
is.close();
}catch(IOException e){
System.out.print("Exception");
}
}
}
上面的程序首先創(chuàng)建文件test.txt,并把給定的數(shù)字以二進制形式寫進該文件,同時輸出到控制臺上。
以上代碼由于是二進制寫入,可能存在亂碼,你可以使用以下代碼實例來解決亂碼問題:
import java.io.*;
public class fileStreamTest2{
public static void main(String[] args) throws IOException {
File f = new File("a.txt");
FileOutputStream fop = new FileOutputStream(f);
// 構(gòu)建FileOutputStream對象,文件不存在會自動新建;如果存在會覆蓋原文件
OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");
// 構(gòu)建OutputStreamWriter對象,參數(shù)可以指定編碼,默認為操作系統(tǒng)默認編碼,windows上是gbk
writer.append("中文輸入");
// 寫入到緩沖區(qū)
writer.append("\r\n");
//換行
writer.append("English");
// 刷新緩沖區(qū),寫入到文件,如果下面已經(jīng)沒有寫入的內(nèi)容了,直接close也會寫入
writer.close();
//關(guān)閉寫入流,同時會把緩沖區(qū)內(nèi)容寫入文件,所以上面的注釋掉
fop.close();
// 關(guān)閉輸出流,釋放系統(tǒng)資源
FileInputStream fip = new FileInputStream(f);
// 構(gòu)建FileInputStream對象
InputStreamReader reader = new InputStreamReader(fip, "UTF-8");
// 構(gòu)建InputStreamReader對象,編碼與寫入相同
StringBuffer sb = new StringBuffer();
while (reader.ready()) {
sb.append((char) reader.read());
// 轉(zhuǎn)成char加到StringBuffer對象中
}
System.out.println(sb.toString());
reader.close();
// 關(guān)閉讀取流
fip.close();
// 關(guān)閉輸入流,釋放系統(tǒng)資源
}
}
以上例子證明:在對多國語言的支持上,字符流表現(xiàn)更優(yōu),此時應(yīng)使用字符流而不是字節(jié)流。
還可以用InputStream和OutputStream配合進行文件的復(fù)制,即讀取原件數(shù)據(jù),寫入副本文件。
復(fù)制有兩種實現(xiàn)方式:
實現(xiàn)一:將源文件中的內(nèi)容全部讀取進來,之后一次性的寫入到目標文件
實現(xiàn)二:邊讀邊寫
在實際開發(fā)中建議使用邊讀邊寫的方式,代碼如下:
public static void main(String[] args) {
// 文件拷貝
try {
FileInputStream fis=new FileInputStream("happy.gif");
FileOutputStream fos=new FileOutputStream("happycopy.gif");
int n=0;
byte[] b=new byte[1024];
while((n=fis.read(b))!=-1){
/*循環(huán)讀取,每次1024個字節(jié),最后一次可能不滿1024。
后面的字節(jié)覆蓋前面的字節(jié),不必擔心數(shù)組溢出。*/
fos.write(b,0,n); //n是實際讀取到的字節(jié)數(shù),如果寫fos.write(b),會造成最后一次數(shù)組未滿的情況也寫1024個字節(jié),從而造成副本比原件略大
}
fis.close();
fos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
}
實際上邊讀邊寫也分為三種方式:
1.批量拷貝(循環(huán)讀取,每次讀入一個byte數(shù)組)
2.緩沖拷貝(使用緩沖流)
3.批量+緩沖拷貝(循環(huán)批量讀取到字節(jié)數(shù)組中,然后使用緩沖輸出流寫入到文件)
第三種方式是最快的。
注意:InputStream的int read()方法讀取一個字節(jié),并用這個字節(jié)填充整型的低八位并返回,OutputStream的void write(int x)寫入x的低八位,如果要寫入一個int,需要移位并寫4次。讀寫基本數(shù)據(jù)類型建議使用DataInputStream和DataOutputStream。
后續(xù)內(nèi)容見 Java學(xué)習總結(jié)之Java IO系統(tǒng)(二)