原文連接:blog.csdn.net/mu\_wind/ar…?json
有人曾問fastjson的做者(阿里技術專家高鐵):「你開發fastjson,沒獲得什麼好處,反而捱了罵背了鍋,這種事情你爲何要作呢?」設計模式
高鐵答道:「由於熱愛自己,就是獎勵啊!」數組
這個回答頓時觸動了我。想一想本身,又未嘗不是如此。寫做是個痛苦的過程,用心寫做就更加煎熬,需字字斟酌,反覆刪改纔有所成。然而,當一篇篇精良文章出本身手而呈現眼前時,那些痛苦煎熬就都那麼值得。若是這些博文能有幸得你們閱讀和承認,就更加是莫大的鼓舞了。技術人的快樂就是能夠這麼純粹和簡單。緩存
點波關注不迷路,一鍵三連好運連連!markdown
IO流是Java中的一個重要構成部分,也是咱們常常打交道的。這篇關於Java IO的博文乾貨滿滿,堪稱全網前三(請輕噴!)網絡
下面幾個問題(問題還會繼續補充),若是你能對答如流,那麼恭喜你,IO知識掌握得很好,能夠當即關閉文章。反之,你能夠在後面得文章中尋找答案。多線程
IO,即in
和out
,也就是輸入和輸出,指應用程序和外部設備之間的數據傳遞,常見的外部設備包括文件、管道、網絡鏈接。app
Java 中是經過流處理IO 的,那麼什麼是流?less
流(Stream
),是一個抽象的概念,是指一連串的數據(字符或字節),是以先進先出的方式發送信息的通道。dom
當程序須要讀取數據的時候,就會開啓一個通向數據源的流,這個數據源能夠是文件,內存,或是網絡鏈接。相似的,當程序須要寫入數據的時候,就會開啓一個通向目的地的流。這時候你就能夠想象數據好像在這其中「流」動同樣。
通常來講關於流的特性有下面幾點:
RandomAccessFile
除外)IO流主要的分類方式有如下3種:
一、輸入流與輸出流
輸入與輸出是相對於應用程序而言的,好比文件讀寫,讀取文件是輸入流,寫文件是輸出流,這點很容易搞反。
二、字節流與字符流
字節流和字符流的用法幾乎完成全同樣,區別在於字節流和字符流所操做的數據單元不一樣,字節流操做的單元是數據單元是8位的字節,字符流操做的是數據單元爲16位的字符。
爲何要有字符流?
Java中字符是採用Unicode標準,Unicode 編碼中,一個英文爲一個字節,一箇中文爲兩個字節。
而在UTF-8編碼中,一箇中文字符是3個字節。例以下面圖中,「雲深不知處」5箇中文對應的是15個字節:-28-70-111-26-73-79-28-72-115-25-97-91-27-92-124
那麼問題來了,若是使用字節流處理中文,若是一次讀寫一個字符對應的字節數就不會有問題,一旦將一個字符對應的字節分裂開來,就會出現亂碼了。爲了更方便地處理中文這些字符,Java就推出了字符流。
字節流和字符流的其餘區別:
以寫文件爲例,咱們查看字符流的源碼,發現確實有利用到緩衝區:
三、節點流和處理流
節點流:直接操做數據讀寫的流類,好比FileInputStream
處理流:對一個已存在的流的連接和封裝,經過對數據進行處理爲程序提供功能強大、靈活的讀寫功能,例如BufferedInputStream
(緩衝字節流)
處理流和節點流應用了Java的裝飾者設計模式。
下圖就很形象地描繪了節點流和處理流,處理流是對節點流的封裝,最終的數據處理仍是由節點流完成的。
在諸多處理流中,有一個很是重要,那就是緩衝流。
咱們知道,程序與磁盤的交互相對於內存運算是很慢的,容易成爲程序的性能瓶頸。減小程序與磁盤的交互,是提高程序效率一種有效手段。緩衝流,就應用這種思路:普通流每次讀寫一個字節,而緩衝流在內存中設置一個緩存區,緩衝區先存儲足夠的待操做數據後,再與內存或磁盤進行交互。這樣,在總數據量不變的狀況下,經過提升每次交互的數據量,減小了交互次數。
聯想一下生活中的例子,咱們搬磚的時候,一塊一塊地往車上裝確定是很低效的。咱們可使用一個小推車,先把磚裝到小推車上,再把這小推車推到車前,把磚裝到車上。這個例子中,小推車能夠視爲緩衝區,小推車的存在,減小了咱們裝車次數,從而提升了效率。
須要注意的是,緩衝流效率必定高嗎?不必定,某些情形下,緩衝流效率反而更低,具體請見IO流效率對比。
完整的IO分類圖以下:
接下來,咱們看看如何使用Java IO。
文本讀寫的例子,也就是文章開頭所說的,將「松下問童子,言師採藥去。只在此山中,雲深不知處。」寫入本地文本,而後再從文件讀取內容並輸出到控制檯。
一、FileInputStream、FileOutputStream(字節流)
字節流的方式效率較低,不建議使用
public class IOTest {
public static void main(String[] args) throws IOException {
File file = new File("D:/test.txt");
write(file);
System.out.println(read(file));
}
public static void write(File file) throws IOException {
OutputStream os = new FileOutputStream(file, true);
// 要寫入的字符串
String string = "松下問童子,言師採藥去。只在此山中,雲深不知處。";
// 寫入文件
os.write(string.getBytes());
// 關閉流
os.close();
}
public static String read(File file) throws IOException {
InputStream in = new FileInputStream(file);
// 一次性取多少個字節
byte[] bytes = new byte[1024];
// 用來接收讀取的字節數組
StringBuilder sb = new StringBuilder();
// 讀取到的字節數組長度,爲-1時表示沒有數據
int length = 0;
// 循環取數據
while ((length = in.read(bytes)) != -1) {
// 將讀取的內容轉換成字符串
sb.append(new String(bytes, 0, length));
}
// 關閉流
in.close();
return sb.toString();
}
}
123456789101112131415161718192021222324252627282930313233343536373839
複製代碼
二、BufferedInputStream、BufferedOutputStream(緩衝字節流)
緩衝字節流是爲高效率而設計的,真正的讀寫操做仍是靠
FileOutputStream
和FileInputStream
,因此其構造方法入參是這兩個類的對象也就不奇怪了。
public class IOTest {
public static void write(File file) throws IOException {
// 緩衝字節流,提升了效率
BufferedOutputStream bis = new BufferedOutputStream(new FileOutputStream(file, true));
// 要寫入的字符串
String string = "松下問童子,言師採藥去。只在此山中,雲深不知處。";
// 寫入文件
bis.write(string.getBytes());
// 關閉流
bis.close();
}
public static String read(File file) throws IOException {
BufferedInputStream fis = new BufferedInputStream(new FileInputStream(file));
// 一次性取多少個字節
byte[] bytes = new byte[1024];
// 用來接收讀取的字節數組
StringBuilder sb = new StringBuilder();
// 讀取到的字節數組長度,爲-1時表示沒有數據
int length = 0;
// 循環取數據
while ((length = fis.read(bytes)) != -1) {
// 將讀取的內容轉換成字符串
sb.append(new String(bytes, 0, length));
}
// 關閉流
fis.close();
return sb.toString();
}
}
12345678910111213141516171819202122232425262728293031323334
複製代碼
三、InputStreamReader、OutputStreamWriter(字符流)
字符流適用於文本文件的讀寫,
OutputStreamWriter
類其實也是藉助FileOutputStream
類實現的,故其構造方法是FileOutputStream
的對象
public class IOTest {
public static void write(File file) throws IOException {
// OutputStreamWriter能夠顯示指定字符集,不然使用默認字符集
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(file, true), "UTF-8");
// 要寫入的字符串
String string = "松下問童子,言師採藥去。只在此山中,雲深不知處。";
osw.write(string);
osw.close();
}
public static String read(File file) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "UTF-8");
// 字符數組:一次讀取多少個字符
char[] chars = new char[1024];
// 每次讀取的字符數組先append到StringBuilder中
StringBuilder sb = new StringBuilder();
// 讀取到的字符數組長度,爲-1時表示沒有數據
int length;
// 循環取數據
while ((length = isr.read(chars)) != -1) {
// 將讀取的內容轉換成字符串
sb.append(chars, 0, length);
}
// 關閉流
isr.close();
return sb.toString()
}
}
12345678910111213141516171819202122232425262728293031
複製代碼
四、字符流便捷類
Java提供了
FileWriter
和FileReader
簡化字符流的讀寫,new FileWriter
等同於new OutputStreamWriter(new FileOutputStream(file, true))
public class IOTest {
public static void write(File file) throws IOException {
FileWriter fw = new FileWriter(file, true);
// 要寫入的字符串
String string = "松下問童子,言師採藥去。只在此山中,雲深不知處。";
fw.write(string);
fw.close();
}
public static String read(File file) throws IOException {
FileReader fr = new FileReader(file);
// 一次性取多少個字節
char[] chars = new char[1024];
// 用來接收讀取的字節數組
StringBuilder sb = new StringBuilder();
// 讀取到的字節數組長度,爲-1時表示沒有數據
int length;
// 循環取數據
while ((length = fr.read(chars)) != -1) {
// 將讀取的內容轉換成字符串
sb.append(chars, 0, length);
}
// 關閉流
fr.close();
return sb.toString();
}
}
123456789101112131415161718192021222324252627282930
複製代碼
五、BufferedReader、BufferedWriter(字符緩衝流)
public class IOTest {
public static void write(File file) throws IOException {
// BufferedWriter fw = new BufferedWriter(new OutputStreamWriter(new
// FileOutputStream(file, true), "UTF-8"));
// FileWriter能夠大幅度簡化代碼
BufferedWriter bw = new BufferedWriter(new FileWriter(file, true));
// 要寫入的字符串
String string = "松下問童子,言師採藥去。只在此山中,雲深不知處。";
bw.write(string);
bw.close();
}
public static String read(File file) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(file));
// 用來接收讀取的字節數組
StringBuilder sb = new StringBuilder();
// 按行讀數據
String line;
// 循環取數據
while ((line = br.readLine()) != null) {
// 將讀取的內容轉換成字符串
sb.append(line);
}
// 關閉流
br.close();
return sb.toString();
}
}
1234567891011121314151617181920212223242526272829303132
複製代碼
第一節中,咱們大體瞭解了IO,並完成了幾個案例,但對IO還缺少更詳細的認知,那麼接下來咱們就對Java IO細細分解,梳理出完整的知識體系來。
Java種提供了40多個類,咱們只須要詳細瞭解一下其中比較重要的就能夠知足平常應用了。
File
類是用來操做文件的類,但它不能操做文件中的數據。
public class File extends Object implements Serializable, Comparable<File>
1
複製代碼
File
類實現了Serializable
、 Comparable<File>
,說明它是支持序列化和排序的。
File類的構造方法
方法名
說明
File(File parent, String child)
根據 parent 抽象路徑名和 child 路徑名字符串建立一個新 File 實例。
File(String pathname)
經過將給定路徑名字符串轉換爲抽象路徑名來建立一個新 File 實例。
File(String parent, String child)
根據 parent 路徑名字符串和 child 路徑名字符串建立一個新 File 實例。
File(URI uri)
經過將給定的 file: URI 轉換爲一個抽象路徑名來建立一個新的 File 實例。
File類的經常使用方法
方法
說明
createNewFile()
當且僅當不存在具備此抽象路徑名指定名稱的文件時,不可分地建立一個新的空文件。
delete()
刪除此抽象路徑名錶示的文件或目錄。
exists()
測試此抽象路徑名錶示的文件或目錄是否存在。
getAbsoluteFile()
返回此抽象路徑名的絕對路徑名形式。
getAbsolutePath()
返回此抽象路徑名的絕對路徑名字符串。
length()
返回由此抽象路徑名錶示的文件的長度。
mkdir()
建立此抽象路徑名指定的目錄。
File類使用實例
public class FileTest {
public static void main(String[] args) throws IOException {
File file = new File("C:/Mu/fileTest.txt");
// 判斷文件是否存在
if (!file.exists()) {
// 不存在則建立
file.createNewFile();
}
System.out.println("文件的絕對路徑:" + file.getAbsolutePath());
System.out.println("文件的大小:" + file.length());
// 刪除文件
file.delete();
}
}
12345678910111213141516
複製代碼
InputStream
與OutputStream
是兩個抽象類,是字節流的基類,全部具體的字節流實現類都是分別繼承了這兩個類。
以InputStream
爲例,它繼承了Object
,實現了Closeable
public abstract class InputStream
extends Object
implements Closeable
123
複製代碼
InputStream
類有不少的實現子類,下面列舉了一些比較經常使用的:
詳細說明一下上圖中的類:
InputStream
:InputStream
是全部字節輸入流的抽象基類,前面說過抽象類不能被實例化,其實是做爲模板而存在的,爲全部實現類定義了處理輸入流的方法。FileInputSream
:文件輸入流,一個很是重要的字節輸入流,用於對文件進行讀取操做。PipedInputStream
:管道字節輸入流,能實現多線程間的管道通訊。ByteArrayInputStream
:字節數組輸入流,從字節數組(byte[])中進行以字節爲單位的讀取,也就是將資源文件都以字節的形式存入到該類中的字節數組中去。FilterInputStream
:裝飾者類,具體的裝飾者繼承該類,這些類都是處理類,做用是對節點類進行封裝,實現一些特殊功能。DataInputStream
:數據輸入流,它是用來裝飾其它輸入流,做用是「容許應用程序以與機器無關方式從底層輸入流中讀取基本 Java 數據類型」。BufferedInputStream
:緩衝流,對節點流進行裝飾,內部會有一個緩存區,用來存放字節,每次都是將緩存區存滿而後發送,而不是一個字節或兩個字節這樣發送,效率更高。ObjectInputStream
:對象輸入流,用來提供對基本數據或對象的持久存儲。通俗點說,也就是能直接傳輸對象,一般應用在反序列化中。它也是一種處理流,構造器的入參是一個InputStream
的實例對象。OutputStream
類繼承關係圖:
OutputStream
類繼承關係與InputStream
相似,須要注意的是PrintStream
.
與字節流相似,字符流也有兩個抽象基類,分別是Reader
和Writer
。其餘的字符流實現類都是繼承了這兩個類。
以Reader
爲例,它的主要實現子類以下圖:
各個類的詳細說明:
InputStreamReader
:從字節流到字符流的橋樑(InputStreamReader
構造器入參是FileInputStream
的實例對象),它讀取字節並使用指定的字符集將其解碼爲字符。它使用的字符集能夠經過名稱指定,也能夠顯式給定,或者能夠接受平臺的默認字符集。BufferedReader
:從字符輸入流中讀取文本,設置一個緩衝區來提升效率。BufferedReader
是對InputStreamReader
的封裝,前者構造器的入參就是後者的一個實例對象。FileReader
:用於讀取字符文件的便利類,new FileReader(File file)
等同於new InputStreamReader(new FileInputStream(file, true),"UTF-8")
,但FileReader
不能指定字符編碼和默認字節緩衝區大小。PipedReader
:管道字符輸入流。實現多線程間的管道通訊。CharArrayReader
:從Char
數組中讀取數據的介質流。StringReader
:從String
中讀取數據的介質流。Writer
與Reader
結構相似,方向相反,再也不贅述。惟一有區別的是,Writer
的子類PrintWriter
。
待續…
字節輸入流InputStream
主要方法:
read()
:今後輸入流中讀取一個數據字節。read(byte[] b)
:今後輸入流中將最多 b.length 個字節的數據讀入一個 byte 數組中。read(byte[] b, int off, int len)
:今後輸入流中將最多 len 個字節的數據讀入一個 byte 數組中。close()
:關閉此輸入流並釋放與該流關聯的全部系統資源。字節輸出流OutputStream
主要方法:
write(byte[] b)
:將 b.length 個字節從指定 byte 數組寫入此文件輸出流中。write(byte[] b, int off, int len)
:將指定 byte 數組中從偏移量 off 開始的 len 個字節寫入此文件輸出流。write(int b)
:將指定字節寫入此文件輸出流。close()
:關閉此輸入流並釋放與該流關聯的全部系統資源。字符輸入流Reader
主要方法:
read()
:讀取單個字符。read(char[] cbuf)
:將字符讀入數組。read(char[] cbuf, int off, int len)
: 將字符讀入數組的某一部分。read(CharBuffer target)
:試圖將字符讀入指定的字符緩衝區。flush()
:刷新該流的緩衝。close()
:關閉此流,但要先刷新它。字符輸出流Writer
主要方法:
write(char[] cbuf)
:寫入字符數組。write(char[] cbuf, int off, int len)
:寫入字符數組的某一部分。write(int c)
:寫入單個字符。write(String str)
:寫入字符串。write(String str, int off, int len)
:寫入字符串的某一部分。flush()
:刷新該流的緩衝。close()
:關閉此流,但要先刷新它。另外,字符緩衝流還有兩個獨特的方法:
BufferedWriter
類newLine()
:寫入一個行分隔符。這個方法會自動適配所在系統的行分隔符。BufferedReader
類readLine()
:讀取一個文本行。字節(Byte)是計量單位,表示數據量多少,是計算機信息技術用於計量存儲容量的一種計量單位,一般狀況下一字節等於八位。
字符(Character)計算機中使用的字母、數字、字和符號,好比’A’、‘B’、’$’、’&'等。
通常在英文狀態下一個字母或字符佔用一個字節,一個漢字用兩個字節表示。
字節與字符:
首先,對比下普通字節流和緩衝字節流的效率:
public class MyTest {
public static void main(String[] args) throws IOException {
File file = new File("C:/Mu/test.txt");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 3000000; i++) {
sb.append("abcdefghigklmnopqrstuvwsyz");
}
byte[] bytes = sb.toString().getBytes();
long start = System.currentTimeMillis();
write(file, bytes);
long end = System.currentTimeMillis();
long start2 = System.currentTimeMillis();
bufferedWrite(file, bytes);
long end2 = System.currentTimeMillis();
System.out.println("普通字節流耗時:" + (end - start) + " ms");
System.out.println("緩衝字節流耗時:" + (end2 - start2) + " ms");
}
// 普通字節流
public static void write(File file, byte[] bytes) throws IOException {
OutputStream os = new FileOutputStream(file);
os.write(bytes);
os.close();
}
// 緩衝字節流
public static void bufferedWrite(File file, byte[] bytes) throws IOException {
BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(file));
bo.write(bytes);
bo.close();
}
}
12345678910111213141516171819202122232425262728293031323334353637
複製代碼
運行結果:
普通字節流耗時:250 ms
緩衝字節流耗時:268 ms
12
複製代碼
這個結果讓我大跌眼鏡,不是說好緩衝流效率很高麼?要知道爲何,只能去源碼裏找答案了。翻看字節緩衝流的write
方法:
public synchronized void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer();
out.write(b, off, len);
return;
}
if (len > buf.length - count) {
flushBuffer();
}
System.arraycopy(b, off, buf, count, len);
count += len;
}
123456789101112131415
複製代碼
註釋裏說得很明白:若是請求長度超過輸出緩衝區的大小,刷新輸出緩衝區,而後直接寫入數據。這樣,緩衝流將無害地級聯。
可是,至於爲何這麼設計,我沒有想明白,有哪位明白的大佬能夠留言指點一下。
基於上面的情形,要想對比普通字節流和緩衝字節流的效率差距,就要避免直接讀寫較長的字符串,因而,設計了下面這個對比案例:用字節流和緩衝字節流分別複製文件。
public class MyTest {
public static void main(String[] args) throws IOException {
File data = new File("C:/Mu/data.zip");
File a = new File("C:/Mu/a.zip");
File b = new File("C:/Mu/b.zip");
StringBuilder sb = new StringBuilder();
long start = System.currentTimeMillis();
copy(data, a);
long end = System.currentTimeMillis();
long start2 = System.currentTimeMillis();
bufferedCopy(data, b);
long end2 = System.currentTimeMillis();
System.out.println("普通字節流耗時:" + (end - start) + " ms");
System.out.println("緩衝字節流耗時:" + (end2 - start2) + " ms");
}
// 普通字節流
public static void copy(File in, File out) throws IOException {
// 封裝數據源
InputStream is = new FileInputStream(in);
// 封裝目的地
OutputStream os = new FileOutputStream(out);
int by = 0;
while ((by = is.read()) != -1) {
os.write(by);
}
is.close();
os.close();
}
// 緩衝字節流
public static void bufferedCopy(File in, File out) throws IOException {
// 封裝數據源
BufferedInputStream bi = new BufferedInputStream(new FileInputStream(in));
// 封裝目的地
BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(out));
int by = 0;
while ((by = bi.read()) != -1) {
bo.write(by);
}
bo.close();
bi.close();
}
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
複製代碼
運行結果:
普通字節流耗時:184867 ms
緩衝字節流耗時:752 ms
12
複製代碼
此次,普通字節流和緩衝字節流的效率差別就很明顯了,達到了245倍。
再看看字符流和緩衝字符流的效率對比:
public class IOTest {
public static void main(String[] args) throws IOException {
// 數據準備
dataReady();
File data = new File("C:/Mu/data.txt");
File a = new File("C:/Mu/a.txt");
File b = new File("C:/Mu/b.txt");
File c = new File("C:/Mu/c.txt");
long start = System.currentTimeMillis();
copy(data, a);
long end = System.currentTimeMillis();
long start2 = System.currentTimeMillis();
copyChars(data, b);
long end2 = System.currentTimeMillis();
long start3 = System.currentTimeMillis();
bufferedCopy(data, c);
long end3 = System.currentTimeMillis();
System.out.println("普通字節流1耗時:" + (end - start) + " ms,文件大小:" + a.length() / 1024 + " kb");
System.out.println("普通字節流2耗時:" + (end2 - start2) + " ms,文件大小:" + b.length() / 1024 + " kb");
System.out.println("緩衝字節流耗時:" + (end3 - start3) + " ms,文件大小:" + c.length() / 1024 + " kb");
}
// 普通字符流不使用數組
public static void copy(File in, File out) throws IOException {
Reader reader = new FileReader(in);
Writer writer = new FileWriter(out);
int ch = 0;
while ((ch = reader.read()) != -1) {
writer.write((char) ch);
}
reader.close();
writer.close();
}
// 普通字符流使用字符流
public static void copyChars(File in, File out) throws IOException {
Reader reader = new FileReader(in);
Writer writer = new FileWriter(out);
char[] chs = new char[1024];
while ((reader.read(chs)) != -1) {
writer.write(chs);
}
reader.close();
writer.close();
}
// 緩衝字符流
public static void bufferedCopy(File in, File out) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(in));
BufferedWriter bw = new BufferedWriter(new FileWriter(out));
String line = null;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
// 釋放資源
bw.close();
br.close();
}
// 數據準備
public static void dataReady() throws IOException {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 600000; i++) {
sb.append("abcdefghijklmnopqrstuvwxyz");
}
OutputStream os = new FileOutputStream(new File("C:/Mu/data.txt"));
os.write(sb.toString().getBytes());
os.close();
System.out.println("完畢");
}
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
複製代碼
運行結果:
普通字符流1耗時:1337 ms,文件大小:15234 kb
普通字符流2耗時:82 ms,文件大小:15235 kb
緩衝字符流耗時:205 ms,文件大小:15234 kb
123
複製代碼
測試屢次,結果差很少,可見字符緩衝流效率上並無明顯提升,咱們更多的是要使用它的readLine()
和newLine()
方法。