【Java基礎】吃透Java IO:字節流、字符流、緩衝流

原文連接:blog.csdn.net/mu\_wind/ar…?json

Java IO流

前言

有人曾問fastjson的做者(阿里技術專家高鐵):「你開發fastjson,沒獲得什麼好處,反而捱了罵背了鍋,這種事情你爲何要作呢?」設計模式

高鐵答道:「由於熱愛自己,就是獎勵啊!」數組

這個回答頓時觸動了我。想一想本身,又未嘗不是如此。寫做是個痛苦的過程,用心寫做就更加煎熬,需字字斟酌,反覆刪改纔有所成。然而,當一篇篇精良文章出本身手而呈現眼前時,那些痛苦煎熬就都那麼值得。若是這些博文能有幸得你們閱讀和承認,就更加是莫大的鼓舞了。技術人的快樂就是能夠這麼純粹和簡單。緩存

點波關注不迷路,一鍵三連好運連連!markdown

IO流是Java中的一個重要構成部分,也是咱們常常打交道的。這篇關於Java IO的博文乾貨滿滿,堪稱全網前三(請輕噴!)網絡

下面幾個問題(問題還會繼續補充),若是你能對答如流,那麼恭喜你,IO知識掌握得很好,能夠當即關閉文章。反之,你能夠在後面得文章中尋找答案。多線程

  1. Java IO流有什麼特色?
  2. Java IO流分爲幾種類型?
  3. 字節流和字符流的關係與區別?
  4. 字符流是否使用了緩衝?
  5. 緩衝流的效率必定高嗎?爲何?
  6. 緩衝流體現了Java中的哪一種設計模式思想?
  7. 爲何要實現序列化?如何實現序列化?
  8. 序列化數據後,再次修改類文件,讀取數據會出問題,如何解決呢?

1 初識Java IO

IO,即inout,也就是輸入和輸出,指應用程序和外部設備之間的數據傳遞,常見的外部設備包括文件、管道、網絡鏈接。app

Java 中是經過流處理IO 的,那麼什麼是流less

流(Stream),是一個抽象的概念,是指一連串的數據(字符或字節),是以先進先出的方式發送信息的通道。dom

當程序須要讀取數據的時候,就會開啓一個通向數據源的流,這個數據源能夠是文件,內存,或是網絡鏈接。相似的,當程序須要寫入數據的時候,就會開啓一個通向目的地的流。這時候你就能夠想象數據好像在這其中「流」動同樣。

通常來講關於流的特性有下面幾點:

  1. 先進先出:最早寫入輸出流的數據最早被輸入流讀取到。
  2. 順序存取:能夠一個接一個地往流中寫入一串字節,讀出時也將按寫入順序讀取一串字節,不能隨機訪問中間的數據。(RandomAccessFile除外)
  3. 只讀或只寫:每一個流只能是輸入流或輸出流的一種,不能同時具有兩個功能,輸入流只能進行讀操做,對輸出流只能進行寫操做。在一個數據傳輸通道中,若是既要寫入數據,又要讀取數據,則要分別提供兩個流。

1.1 IO流分類

IO流主要的分類方式有如下3種:

  1. 按數據流的方向:輸入流、輸出流
  2. 按處理數據單位:字節流、字符流
  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就推出了字符流。

字節流和字符流的其餘區別:

  1. 字節流通常用來處理圖像、視頻、音頻、PPT、Word等類型的文件。字符流通常用於處理純文本類型的文件,如TXT文件等,但不能處理圖像視頻等非文本文件。用一句話說就是:字節流能夠處理一切文件,而字符流只能處理純文本文件。
  2. 字節流自己沒有緩衝區,緩衝字節流相對於字節流,效率提高很是高。而字符流自己就帶有緩衝區,緩衝字符流相對於字符流效率提高就不是那麼大了。詳見文末效率對比。

以寫文件爲例,咱們查看字符流的源碼,發現確實有利用到緩衝區:
在這裏插入圖片描述
在這裏插入圖片描述

三、節點流和處理流

節點流:直接操做數據讀寫的流類,好比FileInputStream

處理流:對一個已存在的流的連接和封裝,經過對數據進行處理爲程序提供功能強大、靈活的讀寫功能,例如BufferedInputStream(緩衝字節流)

處理流和節點流應用了Java的裝飾者設計模式。

下圖就很形象地描繪了節點流和處理流,處理流是對節點流的封裝,最終的數據處理仍是由節點流完成的。
在這裏插入圖片描述
在諸多處理流中,有一個很是重要,那就是緩衝流

咱們知道,程序與磁盤的交互相對於內存運算是很慢的,容易成爲程序的性能瓶頸。減小程序與磁盤的交互,是提高程序效率一種有效手段。緩衝流,就應用這種思路:普通流每次讀寫一個字節,而緩衝流在內存中設置一個緩存區,緩衝區先存儲足夠的待操做數據後,再與內存或磁盤進行交互。這樣,在總數據量不變的狀況下,經過提升每次交互的數據量,減小了交互次數。
在這裏插入圖片描述

聯想一下生活中的例子,咱們搬磚的時候,一塊一塊地往車上裝確定是很低效的。咱們可使用一個小推車,先把磚裝到小推車上,再把這小推車推到車前,把磚裝到車上。這個例子中,小推車能夠視爲緩衝區,小推車的存在,減小了咱們裝車次數,從而提升了效率。
在這裏插入圖片描述

須要注意的是,緩衝流效率必定高嗎?不必定,某些情形下,緩衝流效率反而更低,具體請見IO流效率對比。

完整的IO分類圖以下:
在這裏插入圖片描述

1.2 案例實操

接下來,咱們看看如何使用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(緩衝字節流)

緩衝字節流是爲高效率而設計的,真正的讀寫操做仍是靠FileOutputStreamFileInputStream,因此其構造方法入參是這兩個類的對象也就不奇怪了。

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提供了FileWriterFileReader簡化字符流的讀寫,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
複製代碼

2 IO流對象

第一節中,咱們大體瞭解了IO,並完成了幾個案例,但對IO還缺少更詳細的認知,那麼接下來咱們就對Java IO細細分解,梳理出完整的知識體系來。

Java種提供了40多個類,咱們只須要詳細瞭解一下其中比較重要的就能夠知足平常應用了。

2.1 File類

File類是用來操做文件的類,但它不能操做文件中的數據。

public class File extends Object implements Serializable, Comparable<File>
1
複製代碼

File類實現了SerializableComparable<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
複製代碼

2.2 字節流

InputStreamOutputStream是兩個抽象類,是字節流的基類,全部具體的字節流實現類都是分別繼承了這兩個類。

InputStream爲例,它繼承了Object,實現了Closeable

public abstract class InputStream
extends Object
implements Closeable
123
複製代碼

InputStream類有不少的實現子類,下面列舉了一些比較經常使用的:
在這裏插入圖片描述
詳細說明一下上圖中的類:

  1. InputStreamInputStream是全部字節輸入流的抽象基類,前面說過抽象類不能被實例化,其實是做爲模板而存在的,爲全部實現類定義了處理輸入流的方法。
  2. FileInputSream:文件輸入流,一個很是重要的字節輸入流,用於對文件進行讀取操做。
  3. PipedInputStream:管道字節輸入流,能實現多線程間的管道通訊。
  4. ByteArrayInputStream:字節數組輸入流,從字節數組(byte[])中進行以字節爲單位的讀取,也就是將資源文件都以字節的形式存入到該類中的字節數組中去。
  5. FilterInputStream:裝飾者類,具體的裝飾者繼承該類,這些類都是處理類,做用是對節點類進行封裝,實現一些特殊功能。
  6. DataInputStream:數據輸入流,它是用來裝飾其它輸入流,做用是「容許應用程序以與機器無關方式從底層輸入流中讀取基本 Java 數據類型」。
  7. BufferedInputStream:緩衝流,對節點流進行裝飾,內部會有一個緩存區,用來存放字節,每次都是將緩存區存滿而後發送,而不是一個字節或兩個字節這樣發送,效率更高。
  8. ObjectInputStream:對象輸入流,用來提供對基本數據或對象的持久存儲。通俗點說,也就是能直接傳輸對象,一般應用在反序列化中。它也是一種處理流,構造器的入參是一個InputStream的實例對象。

OutputStream類繼承關係圖:
在這裏插入圖片描述

OutputStream類繼承關係與InputStream相似,須要注意的是PrintStream.

2.3 字符流

與字節流相似,字符流也有兩個抽象基類,分別是ReaderWriter。其餘的字符流實現類都是繼承了這兩個類。

Reader爲例,它的主要實現子類以下圖:
在這裏插入圖片描述
各個類的詳細說明:

  1. InputStreamReader:從字節流到字符流的橋樑(InputStreamReader構造器入參是FileInputStream的實例對象),它讀取字節並使用指定的字符集將其解碼爲字符。它使用的字符集能夠經過名稱指定,也能夠顯式給定,或者能夠接受平臺的默認字符集。
  2. BufferedReader:從字符輸入流中讀取文本,設置一個緩衝區來提升效率。BufferedReader是對InputStreamReader的封裝,前者構造器的入參就是後者的一個實例對象。
  3. FileReader:用於讀取字符文件的便利類,new FileReader(File file)等同於new InputStreamReader(new FileInputStream(file, true),"UTF-8"),但FileReader不能指定字符編碼和默認字節緩衝區大小。
  4. PipedReader :管道字符輸入流。實現多線程間的管道通訊。
  5. CharArrayReader:從Char數組中讀取數據的介質流。
  6. StringReader :從String中讀取數據的介質流。

WriterReader結構相似,方向相反,再也不贅述。惟一有區別的是,Writer的子類PrintWriter

2.4 序列化

待續…

3 IO流方法

3.1 字節流方法

字節輸入流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() :關閉此輸入流並釋放與該流關聯的全部系統資源。

3.2 字符流方法

字符輸入流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() :關閉此流,但要先刷新它。

另外,字符緩衝流還有兩個獨特的方法:

  • BufferedWriternewLine()寫入一個行分隔符。這個方法會自動適配所在系統的行分隔符。
  • BufferedReaderreadLine() :讀取一個文本行。

4 附加內容

4.1 位、字節、字符

字節(Byte)是計量單位,表示數據量多少,是計算機信息技術用於計量存儲容量的一種計量單位,一般狀況下一字節等於八位。

字符(Character)計算機中使用的字母、數字、字和符號,好比’A’、‘B’、’$’、’&'等。

通常在英文狀態下一個字母或字符佔用一個字節,一個漢字用兩個字節表示。

字節與字符:

  • ASCII 碼中,一個英文字母(不分大小寫)爲一個字節,一箇中文漢字爲兩個字節。
  • UTF-8 編碼中,一個英文字爲一個字節,一箇中文爲三個字節。
  • Unicode 編碼中,一個英文爲一個字節,一箇中文爲兩個字節。
  • 符號:英文標點爲一個字節,中文標點爲兩個字節。例如:英文句號 . 佔1個字節的大小,中文句號 。佔2個字節的大小。
  • UTF-16 編碼中,一個英文字母字符或一個漢字字符存儲都須要 2 個字節(Unicode 擴展區的一些漢字存儲須要 4 個字節)。
  • UTF-32 編碼中,世界上任何字符的存儲都須要 4 個字節。

4.2 IO流效率對比

首先,對比下普通字節流和緩衝字節流的效率:

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()方法。

相關文章
相關標籤/搜索