概述
java.io 包幾乎包含了所有操作輸入富稻、輸出需要的類。所有這些流類代表了輸入源和輸出目標(biāo)猪狈。java.io 包中的流支持很多種格式,比如:基本類型辩恼、對(duì)象罪裹、本地化字符集等等。一個(gè)流可以理解為一個(gè)數(shù)據(jù)的序列运挫。輸入流表示從一個(gè)源讀取數(shù)據(jù)状共,輸出流表示向一個(gè)目標(biāo)寫數(shù)據(jù)。Java 為 I/O 提供了強(qiáng)大的而靈活的支持谁帕,使其更廣泛地應(yīng)用到文件傳輸和網(wǎng)絡(luò)編程中峡继。
Java 的 I/O 大概可以分成以下幾類:
- 磁盤操作:File
- 字節(jié)操作:InputStream 和 OutputStream
- 字符操作:Reader 和 Writer
- 對(duì)象操作:Serializable
- 網(wǎng)絡(luò)操作:Socket
- 新的輸入/輸出:NIO
File
Java中IO操作有相應(yīng)步驟,以文件操作為例匈挖,主要操作流程如下:
1.使用File類打開一個(gè)文件
2.通過字節(jié)流或字符流的子類碾牌,指定輸出的位置
3.進(jìn)行讀/寫操作
4.關(guān)閉輸入/輸出
那么我們先來介紹一下File類
Java文件類在java.io包中,它以抽象的方式代表文件名和目錄路徑名儡循。該類主要用于獲取文件和目錄的屬性舶吗,文件和目錄的創(chuàng)建、查找择膝、刪除誓琼、重命名等,但不能進(jìn)行文件的讀寫操作。
File對(duì)象代表磁盤中實(shí)際存在的文件和目錄。通過以下構(gòu)造方法創(chuàng)建一個(gè)File對(duì)象腹侣。
通過給定的父抽象路徑名和子路徑名字符串創(chuàng)建一個(gè)新的File實(shí)例叔收。
File(File parent, String child)
通過將給定路徑名字符串轉(zhuǎn)換成抽象路徑名來創(chuàng)建一個(gè)新 File 實(shí)例。
File(String pathname)
根據(jù) parent 路徑名字符串和 child 路徑名字符串創(chuàng)建一個(gè)新 File 實(shí)例傲隶。
File(String parent, String child)
通過將給定的 file: URI 轉(zhuǎn)換成一個(gè)抽象路徑名來創(chuàng)建一個(gè)新的 File 實(shí)例饺律。
File(URI uri)
注意:
1.在各個(gè)操作系統(tǒng)中,路徑的分隔符是不一樣的跺株,例如:Windows中使用反斜杠:"\
"复濒,Linux|Unix中使用正斜杠:"/
"。在使用反斜杠時(shí)要寫成"\\
"的形式乒省,因?yàn)榉葱备芤M(jìn)行轉(zhuǎn)義芝薇。如果要讓Java保持可移植性,應(yīng)該使用File類的靜態(tài)常量File.pathSeparator作儿。
2.構(gòu)建一個(gè)File實(shí)例并不會(huì)在機(jī)器上創(chuàng)建一個(gè)文件。不管文件是否存在馋劈,都可以創(chuàng)建任意文件名的File實(shí)例攻锰。可以調(diào)用File實(shí)例上的exists()方法來判斷這個(gè)文件是否存在妓雾。通過后續(xù)的學(xué)習(xí)我們會(huì)知道娶吞,當(dāng)把一個(gè)輸出流綁定到一個(gè)不存在的File實(shí)例上時(shí),會(huì)自動(dòng)在機(jī)器上創(chuàng)建該文件械姻,如果文件已經(jīng)存在妒蛇,把輸出流綁定到該文件上則會(huì)覆蓋該文件,但這些都不是在創(chuàng)建File實(shí)例時(shí)進(jìn)行的楷拳。
創(chuàng)建File對(duì)象成功后绣夺,可以使用以下列表中的方法操作文件。
下面給出一個(gè)使用File類的實(shí)例:
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()方法返回的是從時(shí)間戳(1970年1月1日0時(shí)0分0秒)到當(dāng)前的毫秒數(shù)欢揖,返回值類型是long陶耍,可以用Date類對(duì)它進(jìn)行包裝使其更易讀。
Java中的目錄
創(chuàng)建目錄:
File類中有兩個(gè)方法可以用來創(chuàng)建文件夾:
- mkdir( )方法創(chuàng)建一個(gè)文件夾她混,成功則返回true烈钞,失敗則返回false。失敗表明File對(duì)象指定的路徑已經(jīng)存在坤按,或者由于整個(gè)路徑還不存在毯欣,該文件夾不能被創(chuàng)建。
- mkdirs()方法創(chuàng)建一個(gè)文件夾和它的所有父文件夾臭脓。
下面的例子創(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)建某文件夾時(shí)其父文件夾不存在,從而一同創(chuàng)建;mkdir必須滿足路徑上的父文件夾全都存在
注意: Java 在 UNIX 和 Windows 自動(dòng)按約定分辨文件路徑分隔符。如果你在 Windows 版本的 Java 中使用分隔符 (/) ,路徑依然能夠被正確解析算吩。
讀取目錄:
一個(gè)目錄其實(shí)就是一個(gè) File 對(duì)象留凭,它包含其他文件和文件夾。
如果創(chuàng)建一個(gè) File 對(duì)象并且它是一個(gè)目錄偎巢,那么調(diào)用 isDirectory() 方法會(huì)返回 true蔼夜。
可以通過調(diào)用該對(duì)象上的 list() 方法,來提取它包含的文件和文件夾的列表压昼。
下面展示的例子說明如何使用 list() 方法來檢查一個(gè)文件夾中包含的內(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] + " 是一個(gè)目錄");
} else {
System.out.println(s[i] + " 是一個(gè)文件");
}
}
} else {
System.out.println(dirname + " 不是一個(gè)目錄");
}
}
}
刪除目錄或文件:
刪除文件可以使用 java.io.File.delete() 方法求冷。
以下代碼會(huì)刪除目錄/tmp/java/,即便目錄不為空窍霞。
測(cè)試目錄結(jié)構(gòu):
/tmp/java/
|-- 1.log
|-- test
deleteFolder是一個(gè)遞歸函數(shù)匠题,類似于DFS思想
import java.io.File;
public class DeleteFileDemo {
public static void main(String args[]) {
// 這里修改為自己的測(cè)試目錄
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,它提供了對(duì)文件內(nèi)容的訪問但金,可以讀寫文件且支持隨機(jī)訪問文件的任意位置韭山。
RandomAccessFile讀寫用到文件指針,它的初始位置為0,可以用getFilePointer()方法獲取文件指針的位置冷溃。下面是RandomAccessFile常用的方法钱磅。
public int read(int x) throws IOException 方法只讀取一個(gè)字節(jié),也就是x的低八位似枕。
import java.io.File ;
import java.io.RandomAccessFile ;
public class RandomAccessFileDemo01{
// 所有的異常直接拋出盖淡,程序中不再進(jìn)行處理
public static void main(String args[]) throws Exception{
File f = new File("d:" + File.separator + "test.txt") ; // 指定要操作的文件
RandomAccessFile rdf = null ; // 聲明RandomAccessFile類的對(duì)象
rdf = new RandomAccessFile(f,"rw") ;// 讀寫模式,如果文件不存在凿歼,會(huì)自動(dòng)創(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ù)。寫的時(shí)候可以將一個(gè)字符串寫入答憔,讀的時(shí)候需要一個(gè)個(gè)的以字節(jié)的形式讀取出來味赃。
import java.io.File ;
import java.io.RandomAccessFile ;
public class RandomAccessFileDemo02{
// 所有的異常直接拋出,程序中不再進(jìn)行處理
public static void main(String args[]) throws Exception{
File f = new File("d:" + File.separator + "test.txt") ; // 指定要操作的文件
RandomAccessFile rdf = null ; // 聲明RandomAccessFile類的對(duì)象
rdf = new RandomAccessFile(f,"r") ;// 以只讀的方式打開文件
String name = null ;
int age = 0 ;
byte b[] = new byte[8] ; // 開辟byte數(shù)組
// 讀取第二個(gè)人的信息虐拓,意味著要空出第一個(gè)人的信息
rdf.skipBytes(12) ; // 跳過第一個(gè)人的信息
for(int i=0;i<b.length;i++){
b[i] = rdf.readByte() ; // 讀取一個(gè)字節(jié)
}
name = new String(b) ; // 將讀取出來的byte數(shù)組變?yōu)樽址? age = rdf.readInt() ; // 讀取數(shù)字
System.out.println("第二個(gè)人的信息 --> 姓名:" + name + "洁桌;年齡:" + age) ;
// 讀取第一個(gè)人的信息
rdf.seek(0) ; // 指針回到文件的開頭
for(int i=0;i<b.length;i++){
b[i] = rdf.readByte() ; // 讀取一個(gè)字節(jié)
}
name = new String(b) ; // 將讀取出來的byte數(shù)組變?yōu)樽址? age = rdf.readInt() ; // 讀取數(shù)字
System.out.println("第一個(gè)人的信息 --> 姓名:" + name + ";年齡:" + age) ;
rdf.skipBytes(12) ; // 跳過第二個(gè)人的信息
for(int i=0;i<b.length;i++){
b[i] = rdf.readByte() ; // 讀取一個(gè)字節(jié)
}
name = new String(b) ; // 將讀取出來的byte數(shù)組變?yōu)樽址? age = rdf.readInt() ; // 讀取數(shù)字
System.out.println("第三個(gè)人的信息 --> 姓名:" + name + "侯嘀;年齡:" + age) ;
rdf.close() ; // 關(guān)閉
}
};
結(jié)果如下:
流
在Java程序中所有的數(shù)據(jù)都是以流的方式進(jìn)行傳輸或保存的另凌,程序需要數(shù)據(jù)的時(shí)候要使用輸入流讀取數(shù)據(jù),而當(dāng)程序需要將一些數(shù)據(jù)保存起來的時(shí)候戒幔,就要使用輸出流完成吠谢。程序中的輸入輸出都是以流的形式保存的,流中保存的實(shí)際上全都是字節(jié)文件诗茎。流涉及的領(lǐng)域很廣:標(biāo)準(zhǔn)輸入輸出工坊,文件的操作献汗,網(wǎng)絡(luò)上的數(shù)據(jù)流,字符串流王污,對(duì)象流罢吃,zip文件流等等。
流具有方向性昭齐,至于是輸入流還是輸出流則是一個(gè)相對(duì)的概念尿招,一般以程序?yàn)閰⒖迹绻麛?shù)據(jù)的流向是程序至設(shè)備阱驾,我們成為輸出流就谜,反之我們稱為輸入流。
可以將流想象成一個(gè)“水流管道”里覆,水流就在這管道中形成了丧荐,自然就出現(xiàn)了方向的概念。
先上一個(gè)Java IO流類層次圖喧枷,如前所述虹统,一個(gè)流被定義為一個(gè)數(shù)據(jù)序列。輸入流用于從源讀取數(shù)據(jù)隧甚,輸出流用于向目標(biāo)寫數(shù)據(jù):
是不是被嚇到了车荔?沒關(guān)系,我們將通過一個(gè)個(gè)例子來學(xué)習(xí)這些功能呻逆。
IO流分類
1.按操作數(shù)據(jù)類型分:字符流和字節(jié)流
編碼與解碼:編碼就是把字符轉(zhuǎn)換為字節(jié),而解碼是把字節(jié)重新組合成字符菩帝。
如果編碼和解碼過程使用不同的編碼方式那么就出現(xiàn)了亂碼咖城。
- GBK 編碼中,中文字符占 2 個(gè)字節(jié)呼奢,英文字符占 1 個(gè)字節(jié)宜雀;
- UTF-8 編碼中,中文字符占 3 個(gè)字節(jié)握础,英文字符占 1 個(gè)字節(jié)辐董;
- UTF-16be 編碼中,中文字符和英文字符都占 2 個(gè)字節(jié)禀综。
UTF-16be 中的 be 指的是 Big Endian简烘,也就是大端。相應(yīng)地也有 UTF-16le定枷,le 指的是 Little Endian孤澎,也就是小端。
Java 使用雙字節(jié)編碼 UTF-16be欠窒,這不是指 Java 只支持這一種編碼方式覆旭,而是說 char 這種類型使用 UTF-16be 進(jìn)行編碼。char 類型占 16 位,也就是兩個(gè)字節(jié)型将,Java 使用這種雙字節(jié)編碼是為了讓一個(gè)中文或者一個(gè)英文都能使用一個(gè) char 來存儲(chǔ)寂祥。
字符流:Java中的字符流處理的最基本的單元是2字節(jié)的Unicode碼元(char),它通常用來處理文本數(shù)據(jù)七兜,如字符丸凭、字符數(shù)組或字符串等。所謂Unicode碼元惊搏,也就是一個(gè)Unicode代碼單元贮乳,范圍是0x0000~0xFFFF。在以上范圍內(nèi)的每個(gè)數(shù)字都與一個(gè)字符相對(duì)應(yīng)恬惯,Java中的String類型默認(rèn)就把字符以Unicode規(guī)則編碼而后存儲(chǔ)在內(nèi)存中向拆。然而與存儲(chǔ)在內(nèi)存中不同,存儲(chǔ)在磁盤上的數(shù)據(jù)通常有著各種各樣的編碼方式酪耳。使用不同的編碼方式浓恳,相同的字符會(huì)有不同的二進(jìn)制表示。實(shí)際上字符流是這樣工作的:
- 輸出字符流:把要寫入文件的字符序列(實(shí)際上是Unicode碼元序列)轉(zhuǎn)為指定編碼方式下的字節(jié)序列碗暗,然后再寫入到文件中颈将。
- 輸入字符流:把要讀取的字節(jié)序列按指定編碼方式解碼為相應(yīng)字符序列(實(shí)際上是Unicode碼元序列從)從而可以存在內(nèi)存中。
也就是說言疗,所有的文件在硬盤或在傳輸時(shí)都是以字節(jié)的方式進(jìn)行的晴圾,包括圖片等都是按字節(jié)的方式存儲(chǔ)的,而字符是只有在內(nèi)存中才會(huì)形成噪奄。
字節(jié)流:Java中的字節(jié)流處理的最基本單位為單個(gè)字節(jié)(byte)死姚,它通常用來處理二進(jìn)制數(shù)據(jù),如果要得到字節(jié)對(duì)應(yīng)的字符需要強(qiáng)制類型轉(zhuǎn)換。
兩者比較:
1.字符流是由Java虛擬機(jī)將字節(jié)轉(zhuǎn)化為2個(gè)字節(jié)的Unicode字符為單位的字符而成的勤篮,所以它對(duì)多國語言支持性較好都毒,如果要操作中文數(shù)據(jù)等,用字符流碰缔。
2.字符流只用來處理文本數(shù)據(jù)账劲,字節(jié)流還可以用來處理媒體數(shù)據(jù),如視頻金抡、音頻瀑焦、圖片等。
3.字符流的兩個(gè)抽象基類為Reader和Writer梗肝,字節(jié)流的兩個(gè)抽象基類為InputStream和OutputStream蝠猬。它們的具體子類名以基類名為后綴進(jìn)行擴(kuò)展。
4.字節(jié)流在操作的時(shí)候不會(huì)用到緩沖區(qū)(內(nèi)存)统捶,是直接對(duì)文件本身操作的榆芦,而字符流在操作的時(shí)候使用緩沖區(qū)柄粹。
以向一個(gè)文件輸出"Hello world!"為例,我們分別使用字節(jié)流和字符流進(jìn)行輸出匆绣,且在使用完之后都不關(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類找到一個(gè)文件
File f = new File("/home/xiejunyu/"+
"桌面/text.txt");
// 第2步:通過子類實(shí)例化父類對(duì)象
OutputStream out = new FileOutputStream(f);
// 通過對(duì)象多態(tài)性進(jìn)行實(shí)例化
// 第3步:進(jìn)行寫操作
String str = "Hello World!";
// 準(zhǔn)備一個(gè)字符串
byte b[] = str.getBytes();
// 字符串轉(zhuǎn)byte數(shù)組
out.write(b);
// 將內(nèi)容輸出
// 第4步:關(guān)閉輸出流
// out.close();
// 此時(shí)沒有關(guān)閉
}
}
此時(shí)沒有關(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類找到一個(gè)文件
File f = new File("/home/xiejunyu/桌面/test.txt");
// 第2步:通過子類實(shí)例化父類對(duì)象
Writer out = new FileWriter(f);
// 第3步:進(jìn)行寫操作
String str = "Hello World!";
// 準(zhǔn)備一個(gè)字符串
out.write(str);
// 將內(nèi)容輸出
// 第4步:關(guān)閉輸出流
// out.close();
// 此時(shí)沒有關(guān)閉
}
}
程序運(yùn)行后會(huì)發(fā)現(xiàn)文件中沒有任何內(nèi)容,這是因?yàn)樽址鞑僮鲿r(shí)使用了緩沖區(qū)拣凹,而在關(guān)閉字符流時(shí)會(huì)強(qiáng)制性地將緩沖區(qū)中的內(nèi)容進(jìn)行輸出森爽,但是如果程序沒有關(guān)閉字符流,緩沖區(qū)中的內(nèi)容是無法輸出的嚣镜,所以得出結(jié)論:字符流使用了緩沖區(qū)爬迟,而字節(jié)流沒有使用緩沖區(qū)。如果想讓緩沖區(qū)中的內(nèi)容輸出菊匿,要么關(guān)閉流強(qiáng)制刷新緩沖區(qū)付呕,要么調(diào)用flush方法沖刷緩沖區(qū)〉Γ可以簡(jiǎn)單地把緩沖區(qū)理解為一段特殊的內(nèi)存徽职。某些情況下,如果一個(gè)程序頻繁地操作一個(gè)資源(如文件或數(shù)據(jù)庫)佩厚,則性能會(huì)很低姆钉,此時(shí)為了提升性能,就可以將一部分?jǐn)?shù)據(jù)暫時(shí)讀入到內(nèi)存的一塊區(qū)域之中抄瓦,以后直接從此區(qū)域中讀取數(shù)據(jù)即可潮瓶,因?yàn)樽x取內(nèi)存速度會(huì)比較快,這樣可以提升程序的性能闺鲸。
在字符流的操作中筋讨,所有的字符都是在內(nèi)存中形成的埃叭,在輸出前會(huì)將所有的內(nèi)容暫時(shí)保存在內(nèi)存之中摸恍,所以使用了緩沖區(qū)暫存數(shù)據(jù)。
建議:
1.雖然不關(guān)閉字節(jié)流不影響數(shù)據(jù)的輸出赤屋,且后續(xù)JVM會(huì)自動(dòng)回收這部分內(nèi)存立镶,但還是建議在使用完任何流對(duì)象之后關(guān)閉流。
2.使用流對(duì)象都要聲明或拋出IOException
3.在創(chuàng)建一個(gè)文件時(shí)类早,如果目錄下有同名文件將被覆蓋
4.在寫文件時(shí)媚媒,如果文件不存在,會(huì)在創(chuàng)建輸出流對(duì)象并綁定文件時(shí)自動(dòng)創(chuàng)建文件涩僻,不必使用File的exists方法提前檢測(cè)
4.在讀取文件時(shí)缭召,必須使用File的exists方法提前檢測(cè)來保證該文件已存在栈顷,否則拋出FileNotFoundException
2.按流向分:輸入流和輸出流
輸入流:程序從輸入流讀取數(shù)據(jù)源。數(shù)據(jù)源包括外界(鍵盤嵌巷、文件萄凤、網(wǎng)絡(luò)等),即是將數(shù)據(jù)源讀入到程序的通信通道搪哪。輸入流主要包括兩個(gè)抽象基類:InputStream(字節(jié)輸入流)和Reader(字符輸入流)及其擴(kuò)展的具體子類靡努。
輸出流:程序向輸出流寫入數(shù)據(jù)。將程序中的數(shù)據(jù)輸出到外界(顯示器晓折、打印機(jī)惑朦、文件、網(wǎng)絡(luò)等)的通信通道漓概。輸出流主要包括兩個(gè)抽象基類:OutputStream(字節(jié)輸出流)和Writer(字符輸出流)及其擴(kuò)展的具體子類漾月。
3.按功能分:節(jié)點(diǎn)流和處理流
按照流是否直接與特定的地方(如磁盤、內(nèi)存垛耳、設(shè)備等)相連栅屏,分為節(jié)點(diǎn)流和處理流兩類。
節(jié)點(diǎn)流:程序用于直接操作目標(biāo)設(shè)備所對(duì)應(yīng)的類叫節(jié)點(diǎn)流堂鲜。(低級(jí)流)
處理流:程序通過一個(gè)間接流類去調(diào)用節(jié)點(diǎn)流類栈雳,以達(dá)到更加靈活方便地讀寫各種類型的數(shù)據(jù),這個(gè)間接流類就是處理流缔莲。處理流可以看成是對(duì)已存在的流進(jìn)行連接和封裝的流哥纫。(高級(jí)流)
注意:在使用到處理流對(duì)流進(jìn)行連接和封裝時(shí),讀寫完畢只需關(guān)閉處理流痴奏,不用關(guān)閉節(jié)點(diǎn)流蛀骇。處理流關(guān)閉的時(shí)候,會(huì)調(diào)用其處理的節(jié)點(diǎn)流的關(guān)閉方法读拆。如果將節(jié)點(diǎn)流關(guān)閉以后再關(guān)閉處理流擅憔,會(huì)拋出IO異常。
(1) 節(jié)點(diǎn)流
- File 文件流檐晕。對(duì)文件進(jìn)行讀暑诸、寫操作:FileReader、FileWriter辟灰、FileInputStream个榕、FileOutputStream。
- Memory 流芥喇。
向內(nèi)存數(shù)組讀寫數(shù)據(jù): CharArrayReader與 CharArrayWriter西采、ByteArrayInputStream與ByteArrayOutputStream梅割。
向內(nèi)存字符串讀寫數(shù)據(jù):StringReader诺舔、StringWriter停巷、StringBufferInputStream厌杜。 - Pipe管道流:實(shí)現(xiàn)管道的輸入和輸出(進(jìn)程間通信): PipedReader與PipedWriter、PipedInputStream與PipedOutputStream霹崎。
(1) 處理流
- Buffering緩沖流:在讀入或?qū)懗鰰r(shí)瘦材,對(duì)數(shù)據(jù)進(jìn)行緩存,以減少I/O的次數(shù):BufferedReader與BufferedWriter仿畸、BufferedInputStream與BufferedOutputStream食棕。
- Filtering 濾流:在數(shù)據(jù)進(jìn)行讀或?qū)憰r(shí)進(jìn)行過濾:FilterReader與FilterWriter、FilterInputStream與FilterOutputStream错沽。
- Converting between Bytes and Characters 轉(zhuǎn)換流:按照一定的編碼/解碼標(biāo)準(zhǔn)將字節(jié)流轉(zhuǎn)換為字符流簿晓,或進(jìn)行反向轉(zhuǎn)換(Stream到Reader):InputStreamReader、OutputStreamWriter千埃。
- Object Serialization 對(duì)象流 :ObjectInputStream憔儿、ObjectOutputStream。
- DataConversion數(shù)據(jù)流:按基本數(shù)據(jù)類型讀放可、寫(處理的數(shù)據(jù)是Java的基本類型):DataInputStream谒臼、DataOutputStream 。
- Counting計(jì)數(shù)流:在讀入數(shù)據(jù)時(shí)對(duì)行記數(shù) :LineNumberReader耀里、LineNumberInputStream蜈缤。
- Peeking Ahead預(yù)讀流: 通過緩存機(jī)制,進(jìn)行預(yù)讀 :PushbackReader冯挎、PushbackInputStream底哥。
- Printing打印流: 包含方便的打印方法 :PrintWriter、PrintStream房官。
讀取控制臺(tái)輸入
在Java中趾徽,從控制臺(tái)輸入有三種方法:
1.使用標(biāo)準(zhǔn)輸入流對(duì)象System.in
System.in是System中內(nèi)置的InputStream類對(duì)象,它的read方法一次只讀入一個(gè)字節(jié)數(shù)據(jù)翰守,返回0 ~ 255的一個(gè)byte值,一般用來讀取一個(gè)字符孵奶,需要強(qiáng)制類型轉(zhuǎn)換為char類型,而我們通常要取得一個(gè)字符串或一組數(shù)字蜡峰,故這種方法不常用了袁。下面給出這種方法的一個(gè)例子:
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異常,因?yàn)镾ystem.in是一個(gè)流對(duì)象
2.使用Scanner類
Scanner類功能十分強(qiáng)大事示,可以讀入字符串早像、整數(shù)僻肖、浮點(diǎn)數(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(進(jìn)制),可以用next()和nextLine()讀取一個(gè)字符串或一行字符
2.next()和nextLine()的區(qū)別:
next()
- 一定要讀取到有效字符后才可以結(jié)束輸入秒啦。
- 對(duì)輸入有效字符之前遇到的空白熬粗,next() 方法會(huì)自動(dòng)將其去掉。
- 只有輸入有效字符后才將其后面輸入的空白作為分隔符或者結(jié)束符余境。
- next() 不能得到帶有空格的字符串驻呐,除非用useDelimeter方法修改分隔符。
nextLine()
- 以Enter為結(jié)束符,也就是說 nextLine()方法返回的是輸入回車之前的所有字符芳来。
- 可以獲得空白含末。
3.可以用循環(huán)配合hasNextXXX方法判斷輸入是否繼續(xù)
4.Scanner類沒有直接提供讀取一個(gè)字符的方法,如果要讀取一個(gè)字符即舌,有三種方法佣盒,一是讀入一個(gè)字符串后取字符串的第一個(gè)字符,二是使用System.in的read方法顽聂,三是使用字符流讀入
更多Scanner的用法之前已經(jīng)在Java學(xué)習(xí)總結(jié)之Java基本程序設(shè)計(jì)結(jié)構(gòu)中總結(jié)過了肥惭,不再贅述。
3.使用BufferedReader對(duì)象
可以把 System.in 包裝在一個(gè) BufferedReader 對(duì)象中來創(chuàng)建一個(gè)字符流紊搪。
下面是創(chuàng)建 BufferedReader 的基本語法:
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
其中蜜葱,System.in是一個(gè)InputStream對(duì)象(字節(jié)流),使用InputStreamReader作為橋梁耀石,將字節(jié)流轉(zhuǎn)換為字符流笼沥,然后再使用BufferedReader進(jìn)行進(jìn)一步包裝。
BufferedReader 對(duì)象創(chuàng)建后娶牌,我們便可以使用 read() 方法從控制臺(tái)讀取一個(gè)字符(讀入一個(gè)用0~65535之間的整數(shù)表示的字符奔浅,需要強(qiáng)制類型轉(zhuǎn)換為char類型,如果已到達(dá)流末尾诗良,則返回 -1)汹桦,或者用 readLine() 方法讀取一個(gè)字符串。下面是例子:
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() 方法從控制臺(tái)不斷讀取字符直到用戶輸入 "q"鉴裹。
// 使用 BufferedReader 在控制臺(tái)讀取字符
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 在控制臺(tái)讀取字符
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等算法競(jìng)賽中径荔,我們常常也會(huì)使用Java督禽,在輸入數(shù)據(jù)時(shí)有以下幾點(diǎn)注意:
1.hasXXX等價(jià)于C++中讀到文件末尾(EOF)
2.使用BufferedReader輸入會(huì)比Scanner輸入快十倍左右!
控制臺(tái)輸出
控制臺(tái)的輸出由 print() 和 println() 完成。這些方法都由類 PrintStream 定義总处,System.out 是該類的一個(gè)對(duì)象狈惫。
PrintStream 繼承了 OutputStream類,并且實(shí)現(xiàn)了方法 write()鹦马。這樣胧谈,write() 也可以用來往控制臺(tái)寫操作忆肾。
PrintStream 定義 write() 的最簡(jiǎn)單格式如下所示:
void write(int byteval)
該方法將 byteval 的低八位字節(jié)寫到流中,即System.out的write方法一次只能寫一個(gè)字節(jié)(類比System.in的read方法一次只能讀取一個(gè)字節(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)常使用菱肖,因?yàn)?print() 和 println() 方法用起來更為方便客冈。
字節(jié)流(OutputStream、InputStream)
字節(jié)流主要是操作byte類型的數(shù)據(jù)稳强,以byte數(shù)組為準(zhǔn)场仲,主要操作類是OutputStream、InputStream退疫。
由于文件讀寫最為常見燎窘,我們先討論兩個(gè)重要的字節(jié)流 FileInputStream(文件輸入流) 和 FileOutputStream(文件輸出流),分別是抽象類InputStream和OutputStream的具體子類:
FileInputStream
該流用于從文件讀取數(shù)據(jù)蹄咖,它的對(duì)象可以用關(guān)鍵字 new 來創(chuàng)建褐健。
有多種構(gòu)造方法可用來創(chuàng)建對(duì)象。
可以使用字符串類型的文件名來創(chuàng)建一個(gè)輸入流對(duì)象來讀取文件:
InputStream f = new FileInputStream("C:/java/hello");
也可以使用一個(gè)文件對(duì)象來創(chuàng)建一個(gè)輸入流對(duì)象來讀取文件澜汤。我們首先得使用 File() 方法來創(chuàng)建一個(gè)文件對(duì)象:
File f = new File("C:/java/hello");
InputStream in = new FileInputStream(f);
創(chuàng)建了InputStream對(duì)象蚜迅,就可以使用下面的方法來讀取流或者進(jìn)行其他的流操作。
下面是一個(gè)例子:
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);
}
當(dāng)我們需要?jiǎng)?chuàng)建一個(gè)byte[]來保存讀取的字節(jié)時(shí)俊抵,如果數(shù)組太小谁不,無法完整讀入數(shù)據(jù),如果太大又會(huì)造成內(nèi)存浪費(fèi)徽诲。可以使用File類的length方法得到文件的數(shù)據(jù)字節(jié)數(shù)刹帕,從而有效確定byte數(shù)組的大小。
public static void main(String[] args) {
// 創(chuàng)建一個(gè)FileInputStream對(duì)象
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個(gè)字節(jié)*/
System.out.println(new String(b));
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
注意: 每調(diào)用一次read方法,當(dāng)前讀取在文件中的位置就會(huì)向后移動(dòng)一個(gè)字節(jié)或者移動(dòng)byte[]的長度(read的兩個(gè)重載方法),已經(jīng)到文件末尾會(huì)返回-1钱贯,可以通過read方法返回-1判斷是否讀到文件末尾挫掏,也可以使用available方法返回下一次可以不受阻塞讀取的字節(jié)數(shù)來讀取。FileInputStream不支持mark和reset方法進(jìn)行重復(fù)讀取秩命。BufferedInputStream支持此操作尉共。
FileOutputStream
該類用來創(chuàng)建一個(gè)文件并向文件中寫數(shù)據(jù)。
如果該流在打開文件進(jìn)行輸出前弃锐,目標(biāo)文件不存在袄友,那么該流會(huì)創(chuàng)建該文件。
有兩個(gè)構(gòu)造方法可以用來創(chuàng)建 FileOutputStream 對(duì)象霹菊。
使用字符串類型的文件名來創(chuàng)建一個(gè)輸出流對(duì)象:
OutputStream f = new FileOutputStream("C:/java/hello")
也可以使用一個(gè)文件對(duì)象來創(chuàng)建一個(gè)輸出流來寫文件剧蚣。我們首先得使用File()方法來創(chuàng)建一個(gè)文件對(duì)象:
File f = new File("C:/java/hello");
OutputStream f = new FileOutputStream(f);
之前的所有操作中,如果重新執(zhí)行程序,則肯定會(huì)覆蓋文件中的已有內(nèi)容券敌,那么此時(shí)就可以通過FileOutputStream向文件中追加內(nèi)容,F(xiàn)ileOutputStream的另外一個(gè)構(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 對(duì)象完成后熊镣,就可以使用下面的方法來寫入流或者進(jìn)行其他的流操作卑雁。
當(dāng)有一個(gè)字符串時(shí),可以用getBytes方法轉(zhuǎn)為byte數(shù)組用于字節(jié)流的輸出绪囱。
下面是一個(gè)演示 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ù)字以二進(jìn)制形式寫進(jìn)該文件,同時(shí)輸出到控制臺(tái)上鬼吵。
以上代碼由于是二進(jìn)制寫入扣甲,可能存在亂碼,你可以使用以下代碼實(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對(duì)象,文件不存在會(huì)自動(dòng)新建;如果存在會(huì)覆蓋原文件
OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");
// 構(gòu)建OutputStreamWriter對(duì)象,參數(shù)可以指定編碼,默認(rèn)為操作系統(tǒng)默認(rèn)編碼,windows上是gbk
writer.append("中文輸入");
// 寫入到緩沖區(qū)
writer.append("\r\n");
//換行
writer.append("English");
// 刷新緩沖區(qū),寫入到文件,如果下面已經(jīng)沒有寫入的內(nèi)容了,直接close也會(huì)寫入
writer.close();
//關(guān)閉寫入流,同時(shí)會(huì)把緩沖區(qū)內(nèi)容寫入文件,所以上面的注釋掉
fop.close();
// 關(guān)閉輸出流,釋放系統(tǒng)資源
FileInputStream fip = new FileInputStream(f);
// 構(gòu)建FileInputStream對(duì)象
InputStreamReader reader = new InputStreamReader(fip, "UTF-8");
// 構(gòu)建InputStreamReader對(duì)象,編碼與寫入相同
StringBuffer sb = new StringBuffer();
while (reader.ready()) {
sb.append((char) reader.read());
// 轉(zhuǎn)成char加到StringBuffer對(duì)象中
}
System.out.println(sb.toString());
reader.close();
// 關(guān)閉讀取流
fip.close();
// 關(guān)閉輸入流,釋放系統(tǒng)資源
}
}
以上例子證明:在對(duì)多國語言的支持上齿椅,字符流表現(xiàn)更優(yōu)琉挖,此時(shí)應(yīng)使用字符流而不是字節(jié)流。
還可以用InputStream和OutputStream配合進(jìn)行文件的復(fù)制涣脚,即讀取原件數(shù)據(jù)示辈,寫入副本文件。
復(fù)制有兩種實(shí)現(xiàn)方式:
實(shí)現(xiàn)一:將源文件中的內(nèi)容全部讀取進(jìn)來遣蚀,之后一次性的寫入到目標(biāo)文件
實(shí)現(xiàn)二:邊讀邊寫
在實(shí)際開發(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個(gè)字節(jié)芭梯,最后一次可能不滿1024险耀。
后面的字節(jié)覆蓋前面的字節(jié),不必?fù)?dān)心數(shù)組溢出玖喘。*/
fos.write(b,0,n); //n是實(shí)際讀取到的字節(jié)數(shù)胰耗,如果寫fos.write(b),會(huì)造成最后一次數(shù)組未滿的情況也寫1024個(gè)字節(jié)芒涡,從而造成副本比原件略大
}
fis.close();
fos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
}
實(shí)際上邊讀邊寫也分為三種方式:
1.批量拷貝(循環(huán)讀取柴灯,每次讀入一個(gè)byte數(shù)組)
2.緩沖拷貝(使用緩沖流)
3.批量+緩沖拷貝(循環(huán)批量讀取到字節(jié)數(shù)組中,然后使用緩沖輸出流寫入到文件)
第三種方式是最快的费尽。
注意:InputStream的int read()方法讀取一個(gè)字節(jié)赠群,并用這個(gè)字節(jié)填充整型的低八位并返回,OutputStream的void write(int x)寫入x的低八位旱幼,如果要寫入一個(gè)int查描,需要移位并寫4次。讀寫基本數(shù)據(jù)類型建議使用DataInputStream和DataOutputStream。
后續(xù)內(nèi)容見 Java學(xué)習(xí)總結(jié)之Java IO系統(tǒng)(二)