臥槽,這也太上頭了吧!
“老王,Java IO 也太上頭了吧?”新兵蛋子小二向頭頂很涼快的老王抱怨道,“你瞧,我就按照傳輸方式對(duì) IO 進(jìn)行了一個(gè)簡(jiǎn)單的分類,就能搞出來這么多的玩意!”

好久沒搞過 IO 了,老王看到這幅思維導(dǎo)圖也是吃了一驚。想想也是,他當(dāng)初學(xué)習(xí) Java IO 的時(shí)候頭也大,烏央烏央的一片,全是類,估計(jì)是所有 Java 包里面類最多的,一會(huì)是 Input 一會(huì)是 Output,一會(huì)是 Reader 一會(huì)是 Writer,真不知道 Java 的設(shè)計(jì)者是怎么想的。
看著肺都快要?dú)庹ǖ男《贤跎钌畹匚艘豢跉猓托牡貙?duì)小二說:“主要是 Java 的設(shè)計(jì)者考慮得比較多吧,所以 IO 給人一種很亂的感覺,我來給你梳理一下。”
01、傳輸方式劃分
就按照你的那副思維導(dǎo)圖來說吧。
傳輸方式有兩種,字節(jié)和字符,那首先得搞明白字節(jié)和字符有什么區(qū)別,對(duì)吧?
字節(jié)(byte)是計(jì)算機(jī)中用來表示存儲(chǔ)容量的一個(gè)計(jì)量單位,通常情況下,一個(gè)字節(jié)有 8 位(bit)。
字符(char)可以是計(jì)算機(jī)中使用的字母、數(shù)字、和符號(hào),比如說 A 1 $ 這些。
通常來說,一個(gè)字母或者一個(gè)字符占用一個(gè)字節(jié),一個(gè)漢字占用兩個(gè)字節(jié)。

具體還要看字符編碼,比如說在 UTF-8 編碼下,一個(gè)英文字母(不分大小寫)為一個(gè)字節(jié),一個(gè)中文漢字為三個(gè)字節(jié);在 Unicode 編碼中,一個(gè)英文字母為一個(gè)字節(jié),一個(gè)中文漢字為兩個(gè)字節(jié)。
PS:關(guān)于字符編碼,可以看前面的章節(jié):錕斤拷
明白了字節(jié)與字符的區(qū)別,再來看字節(jié)流和字符流就會(huì)輕松多了。
字節(jié)流用來處理二進(jìn)制文件,比如說圖片啊、MP3 啊、視頻啊。
字符流用來處理文本文件,文本文件可以看作是一種特殊的二進(jìn)制文件,只不過經(jīng)過了編碼,便于人們閱讀。
換句話說就是,字節(jié)流可以處理一切文件,而字符流只能處理文本。
雖然 IO 類很多,但核心的就是 4 個(gè)抽象類:InputStream、OutputStream、Reader、Writer。
(抽象大法真好)
雖然 IO 類的方法也很多,但核心的也就 2 個(gè):read 和 write。
InputStream 類
int read():讀取數(shù)據(jù)int read(byte b[], int off, int len):從第 off 位置開始讀,讀取 len 長(zhǎng)度的字節(jié),然后放入數(shù)組 b 中long skip(long n):跳過指定個(gè)數(shù)的字節(jié)int available():返回可讀的字節(jié)數(shù)void close():關(guān)閉流,釋放資源
OutputStream 類
void write(int b):寫入一個(gè)字節(jié),雖然參數(shù)是一個(gè) int 類型,但只有低 8 位才會(huì)寫入,高 24 位會(huì)舍棄(這塊后面再講)void write(byte b[], int off, int len):將數(shù)組 b 中的從 off 位置開始,長(zhǎng)度為 len 的字節(jié)寫入void flush():強(qiáng)制刷新,將緩沖區(qū)的數(shù)據(jù)寫入void close():關(guān)閉流
Reader 類
int read():讀取單個(gè)字符int read(char cbuf[], int off, int len):從第 off 位置開始讀,讀取 len 長(zhǎng)度的字符,然后放入數(shù)組 b 中long skip(long n):跳過指定個(gè)數(shù)的字符int ready():是否可以讀了void close():關(guān)閉流,釋放資源
Writer 類
void write(int c):寫入一個(gè)字符void write( char cbuf[], int off, int len):將數(shù)組 cbuf 中的從 off 位置開始,長(zhǎng)度為 len 的字符寫入void flush():強(qiáng)制刷新,將緩沖區(qū)的數(shù)據(jù)寫入void close():關(guān)閉流
理解了上面這些方法,基本上 IO 的靈魂也就全部掌握了。
二、操作對(duì)象劃分
小二,你細(xì)想一下,IO IO,不就是輸入輸出(Input/Output)嘛:
Input:將外部的數(shù)據(jù)讀入內(nèi)存,比如說把文件從硬盤讀取到內(nèi)存,從網(wǎng)絡(luò)讀取數(shù)據(jù)到內(nèi)存等等 Output:將內(nèi)存中的數(shù)據(jù)寫入到外部,比如說把數(shù)據(jù)從內(nèi)存寫入到文件,把數(shù)據(jù)從內(nèi)存輸出到網(wǎng)絡(luò)等等。
所有的程序,在執(zhí)行的時(shí)候,都是在內(nèi)存上進(jìn)行的,一旦關(guān)機(jī),內(nèi)存中的數(shù)據(jù)就沒了,那如果想要持久化,就需要把內(nèi)存中的數(shù)據(jù)輸出到外部,比如說文件。
文件操作算是 IO 中最典型的操作了,也是最頻繁的操作。那其實(shí)你可以換個(gè)角度來思考,比如說按照 IO 的操作對(duì)象來思考,IO 就可以分類為:文件、數(shù)組、管道、基本數(shù)據(jù)類型、緩沖、打印、對(duì)象序列化/反序列化,以及轉(zhuǎn)換等。

1)文件
文件流也就是直接操作文件的流,可以細(xì)分為字節(jié)流(FileInputStream 和 FileOuputStream)和字符流(FileReader 和 FileWriter)。
FileInputStream 的例子:
int?b;
FileInputStream?fis1?=?new?FileInputStream("fis.txt");
//?循環(huán)讀取
while?((b?=?fis1.read())!=-1)?{
????System.out.println((char)b);
}
//?關(guān)閉資源
fis1.close();
FileOutputStream 的例子:
FileOutputStream?fos?=?new?FileOutputStream("fos.txt");
fos.write("沉默王二".getBytes());
fos.close();
FileReader 的例子:
int?b?=?0;
FileReader?fileReader?=?new?FileReader("read.txt");
//?循環(huán)讀取
while?((b?=?fileReader.read())!=-1)?{
????//?自動(dòng)提升類型提升為?int?類型,所以用?char?強(qiáng)轉(zhuǎn)
????System.out.println((char)b);
}
//?關(guān)閉流
fileReader.close();
FileWriter 的例子:
FileWriter?fileWriter?=?new?FileWriter("fw.txt");
char[]?chars?=?"沉默王二".toCharArray();
fileWriter.write(chars,?0,?chars.length);
fileWriter.close();
當(dāng)掌握了文件的輸入輸出,其他的自然也就掌握了,都大差不差。
2)數(shù)組
通常來說,針對(duì)文件的讀寫操作,使用文件流配合緩沖流就夠用了,但為了提升效率,頻繁地讀寫文件并不是太好,那么就出現(xiàn)了數(shù)組流,有時(shí)候也稱為內(nèi)存流。
ByteArrayInputStream 的例子:
InputStream?is?=new?BufferedInputStream(
????????new?ByteArrayInputStream(
????????????????"沉默王二".getBytes(StandardCharsets.UTF_8)));
//操作
byte[]?flush?=new?byte[1024];
int?len?=0;
while(-1!=(len=is.read(flush))){
????System.out.println(new?String(flush,0,len));
}
//釋放資源
is.close();
ByteArrayOutputStream 的例子:
ByteArrayOutputStream?bos?=new?ByteArrayOutputStream();
byte[]?info?="沉默王二".getBytes();
bos.write(info,?0,?info.length);
//獲取數(shù)據(jù)
byte[]?dest?=bos.toByteArray();
//釋放資源
bos.close();
3)管道
Java 中的管道和 Unix/Linux 中的管道不同,在 Unix/Linux 中,不同的進(jìn)程之間可以通過管道來通信,但 Java 中,通信的雙方必須在同一個(gè)進(jìn)程中,也就是在同一個(gè) JVM 中,管道為線程之間的通信提供了通信能力。
一個(gè)線程通過 PipedOutputStream 寫入的數(shù)據(jù)可以被另外一個(gè)線程通過相關(guān)聯(lián)的 PipedInputStream 讀取出來。
final?PipedOutputStream?pipedOutputStream?=?new?PipedOutputStream();
final?PipedInputStream?pipedInputStream?=?new?PipedInputStream(pipedOutputStream);
Thread?thread1?=?new?Thread(new?Runnable()?{
????@Override
????public?void?run()?{
????????try?{
????????????pipedOutputStream.write("沉默王二".getBytes(StandardCharsets.UTF_8));
????????????pipedOutputStream.close();
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}
});
Thread?thread2?=?new?Thread(new?Runnable()?{
????@Override
????public?void?run()?{
????????try?{
????????????byte[]?flush?=new?byte[1024];
????????????int?len?=0;
????????????while(-1!=(len=pipedInputStream.read(flush))){
????????????????System.out.println(new?String(flush,0,len));
????????????}
????????????pipedInputStream.close();
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}
});
thread1.start();
thread2.start();
4)基本數(shù)據(jù)類型
基本數(shù)據(jù)類型輸入輸出流是一個(gè)字節(jié)流,該流不僅可以讀寫字節(jié)和字符,還可以讀寫基本數(shù)據(jù)類型。
DataInputStream 提供了一系列可以讀基本數(shù)據(jù)類型的方法:
DataInputStream?dis?=?new?DataInputStream(new?FileInputStream(“das.txt”))?;
byte?b?=?dis.readByte()?;
short?s?=?dis.readShort()?;
int?i?=?dis.readInt();
long?l?=?dis.readLong()?;
float?f?=?dis.readFloat()?;
double?d?=?dis.readDouble()?;
boolean?bb?=?dis.readBoolean()?;
char?ch?=?dis.readChar()?;
DataOutputStream 提供了一系列可以寫基本數(shù)據(jù)類型的方法:
DataOutputStream?das?=?new?DataOutputStream(new?FileOutputStream(“das.txt”));
das.writeByte(10);
das.writeShort(100);
das.writeInt(1000);
das.writeLong(10000L);
das.writeFloat(12.34F);
das.writeDouble(12.56);
das.writeBoolean(true);
das.writeChar('A');
5)緩沖
CPU 很快,它比內(nèi)存快 100 倍,比磁盤快百萬倍。那也就意味著,程序和內(nèi)存交互會(huì)很快,和硬盤交互相對(duì)就很慢,這樣就會(huì)導(dǎo)致性能問題。
為了減少程序和硬盤的交互,提升程序的效率,就引入了緩沖流,也就是類名前綴帶有 Buffer 的那些,比如說 BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter。

緩沖流在內(nèi)存中設(shè)置了一個(gè)緩沖區(qū),只有緩沖區(qū)存儲(chǔ)了足夠多的帶操作的數(shù)據(jù)后,才會(huì)和內(nèi)存或者硬盤進(jìn)行交互。簡(jiǎn)單來說,就是一次多讀/寫點(diǎn),少讀/寫幾次,這樣程序的性能就會(huì)提高。
6)打印
恐怕 Java 程序員一生當(dāng)中最常用的就是打印流了:System.out 其實(shí)返回的就是一個(gè) PrintStream 對(duì)象,可以用來打印各式各樣的對(duì)象。
System.out.println("沉默王二是真的二!");
PrintStream 最終輸出的是字節(jié)數(shù)據(jù),而 PrintWriter 則是擴(kuò)展了 Writer 接口,所以它的 print()/println() 方法最終輸出的是字符數(shù)據(jù)。使用上幾乎和 PrintStream 一模一樣。
StringWriter?buffer?=?new?StringWriter();
try?(PrintWriter?pw?=?new?PrintWriter(buffer))?{
????pw.println("沉默王二");
}
System.out.println(buffer.toString());
7)對(duì)象序列化/反序列化
序列化本質(zhì)上是將一個(gè) Java 對(duì)象轉(zhuǎn)成字節(jié)數(shù)組,然后可以將其保存到文件中,或者通過網(wǎng)絡(luò)傳輸?shù)竭h(yuǎn)程。
ByteArrayOutputStream?buffer?=?new?ByteArrayOutputStream();
try?(ObjectOutputStream?output?=?new?ObjectOutputStream(buffer))?{
????output.writeUTF("沉默王二");
}
System.out.println(Arrays.toString(buffer.toByteArray()));
與其對(duì)應(yīng)的,有序列化,就有反序列化,也就是再將字節(jié)數(shù)組轉(zhuǎn)成 Java 對(duì)象的過程。
try?(ObjectInputStream?input?=?new?ObjectInputStream(new?FileInputStream(
????????new?File("Person.txt"))))?{
????String?s?=?input.readUTF();
}
8)轉(zhuǎn)換
InputStreamReader 是從字節(jié)流到字符流的橋連接,它使用指定的字符集讀取字節(jié)并將它們解碼為字符。
InputStreamReader?isr?=?new?InputStreamReader(
????????new?FileInputStream("demo.txt"));
char?[]cha?=?new?char[1024];
int?len?=?isr.read(cha);
System.out.println(new?String(cha,0,len));
isr.close();
OutputStreamWriter 將一個(gè)字符流的輸出對(duì)象變?yōu)樽止?jié)流的輸出對(duì)象,是字符流通向字節(jié)流的橋梁。
File?f?=?new?File("test.txt")?;
Writer?out?=?new?OutputStreamWriter(new?FileOutputStream(f))?;?//?字節(jié)流變?yōu)樽址??
out.write("hello?world!!")?;????//?使用字符流輸出??
out.close()?;
“小二啊,你看,經(jīng)過我的梳理,是不是感覺 IO 也沒多少東西!針對(duì)不同的場(chǎng)景、不同的業(yè)務(wù),選擇對(duì)應(yīng)的 IO 流就可以了,用法上就是讀和寫。”老王一口氣講完這些,長(zhǎng)長(zhǎng)的舒了一口氣。
此時(shí)此刻的小二,還沉浸在老王的滔滔不絕中。不僅感覺老王的肺活量是真的大,還感慨老王不愧是工作了十多年的“老油條”,一下子就把自己感覺頭大的 IO 給梳理得很清晰了。
《Java 程序員進(jìn)階之路》專欄,風(fēng)趣幽默、通俗易懂,對(duì) Java 初學(xué)者極度友好和舒適??,內(nèi)容包括但不限于 Java 語法、Java 集合框架、Java IO、Java 并發(fā)編程、Java 虛擬機(jī)等核心知識(shí)點(diǎn)。目前已更新到第 68 篇。
點(diǎn)擊上方名片,發(fā)送關(guān)鍵字「03」 就可以獲取離線版 PDF 了,15 萬+字,妥妥的良心。亮白與暗黑兩個(gè)版本都有喲,讓我們一起成為更好的 Java 工程師吧。

以后不要再問二哥哪本書適合 Java 新手了,問就是二哥的《Java 程序員進(jìn)階之路》啊~
鐵子們,《Java 程序員進(jìn)階之路》在 GitHub 上已經(jīng)收獲了 571 枚星標(biāo),小伙伴們趕緊去點(diǎn)點(diǎn)了,沖 600 star!
https://github.com/itwanger/toBeBetterJavaer

沒有什么使我停留——除了目的,縱然岸旁有玫瑰、有綠蔭、有寧靜的港灣,我是不系之舟。
推薦閱讀:
