Java開發筆記(八十六)通過緩衝區讀寫文件

来源:https://www.cnblogs.com/pinlantu/archive/2019/04/17/10726494.html
-Advertisement-
Play Games

前面介紹了利用文件寫入器和文件讀取器來讀寫文件,因為FileWriter與FileReader讀寫的數據以字元為單位,所以這種讀寫文件的方式被稱作“字元流I/O”,其中字母I代表輸入Input,字母O代表輸出Output。可是FileWriter的讀操作並不高效,緣由在於FileWriter每次調用 ...


前面介紹了利用文件寫入器和文件讀取器來讀寫文件,因為FileWriter與FileReader讀寫的數據以字元為單位,所以這種讀寫文件的方式被稱作“字元流I/O”,其中字母I代表輸入Input,字母O代表輸出Output。可是FileWriter的讀操作並不高效,緣由在於FileWriter每次調用write方法都會直接寫入文件,假如某項業務需要多次調用write方法,那麼程式就會寫入文件同樣次數。因為寫文件本質是寫磁碟,磁碟的速度遠不如記憶體,所以頻繁地寫文件必然嚴重降低程式的運行效率。為此Java又設計了緩存寫入器BufferedWriter,它的write方法並不直接寫入文件,而是先寫入一塊緩存,等到緩存寫滿了再將緩存上的數據寫入文件。由於緩存空間位於記憶體之中,寫入緩存等同訪問記憶體,這樣相當於把寫磁碟動作替換成寫記憶體動作,因此BufferedWriter的整體寫文件性能要大大優於FileWriter。除此之外,BufferedWriter還新增了下列幾個方法:
newLine:往文件末尾添加換行標記(Window系統是回車加換行)。當然實際上是先往緩存添加換行標記,並非直接往磁碟寫入換行標記。
flush:立即將緩衝區中的數據寫入磁碟。預設情況要等緩衝區滿了才會寫入磁碟,或者調用close方法關閉文件之時也會寫入磁碟,但是有時程式猴急,一定要立即寫入磁碟,此時就需調用flush方法強行寫磁碟。
使用緩存寫入器之前要先創建文件讀取器對象,並獲得父類Writer的實例,然後再據此創建緩存寫入器對象。下麵是通過緩存寫入器把多行字元串寫入文件的代碼例子:

	private static String mSrcName = "D:/test/aad.txt";
	// 使用緩存字元流寫入文件
	private static void writeBuffer() {
		String str1 = "白日依山盡,黃河入海流。";
		String str2 = "欲窮千里目,更上一層樓。";
		File file = new File(mSrcName); // 創建一個指定路徑的文件對象
		// try(...)允許在圓括弧內部擁有多個資源創建語句,語句之間以冒號分隔
		// 先創建文件寫入器,再根據文件讀取器創建緩存寫入器
		try (Writer writer = new FileWriter(file);
				BufferedWriter bwriter = new BufferedWriter(writer);) {
			// FileWriter的每次write調用都會直接寫入磁碟,不但效率低,性能也差。
			// BufferedWriter的每次write調用會先寫入緩衝區,直到緩衝區滿了才寫入磁碟,
			// 緩衝區大小預設是8K,查看源碼defaultCharBufferSize = 8192;
			// 資源釋放的close方法再把緩衝區的剩餘數據寫入磁碟,
			// 或者中途調用flush方法也可提前將緩衝區的數據寫入磁碟。
			bwriter.write(str1); // 往文件寫入字元串
			bwriter.newLine(); // 另起一行,也就是在文件末尾添加換行標記(Window系統是回車加換行)
			bwriter.write(str2);  // 往文件寫入字元串
			//bwriter.flush(); // 把緩衝區中的數據寫入磁碟
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

 

既然文件寫入器有對應的緩存寫入器,那麼文件讀取器也有對應的緩存讀取器BufferedReader。BufferedReader的實現原理與它的兄弟BufferedWriter類似,另外BufferedReader比起文件讀取器新增瞭如下方法:
readLine:從文件中讀取一行數據。
mark:在當前位置做個標記。
reset:重置文件指針,令其回到上次標記的位置。也就是回到上次mark方法標記的文件位置。
lines:讀取文件內容的所有行,返回的是Stream<String>流對象,之後便可按照流式處理來加工該字元串流。
若想使用緩存讀取器,依然要先創建文件讀取器,再根據其父類的讀取器實例創建緩存讀取器。下麵是通過緩存讀取器從文件中讀取多行字元串的代碼例子:

	// 使用緩存字元流讀取文件
	private static void readBuffer() {
		File file = new File(mSrcName); // 創建一個指定路徑的文件對象
		// try(...)允許在圓括弧內部擁有多個資源創建語句,語句之間以冒號分隔
		// 先創建文件讀取器,再根據文件讀取器創建緩存讀取器
		try (Reader reader = new FileReader(file);
				BufferedReader breader = new BufferedReader(reader);) {
			breader.mark((int) file.length()); // 做個標記
			for (int i=1; ; i++) {
				// FileReader只能一個字元一個字元地讀,或者一次性讀進字元數組。
				// BufferedReader還支持一行一行地讀。
				String line = breader.readLine(); // 從文件中讀出一行文字
				if (line == null) { // 讀到了空指針,表示已經到了文件末尾
					break;
				}
				System.out.println("第"+i+"行的文字為:"+line);
			}
			breader.reset(); // 重置文件指針,令其回到上次標記的位置
			for (int i=1; ; i++) {
				String line = breader.readLine(); // 從文件中讀出一行文字
				if (line == null) { // 讀到了空指針,表示已經到了文件末尾
					break;
				}
				System.out.println("又讀了一遍 第"+i+"行的文字為:"+line);
			}
			//breader.lines(); // 返回Stream<String>對象,之後可按照流式處理來加工該字元串流
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

 

註意到以上代碼BufferedWriter和BufferedReader的創建語句都位於try後面的圓括弧之中,這是因為Writer與Reader兩大家族統統實現了AutoCloseable介面,所以由它們繁衍而來的所有子類都具備自動釋放資源的功能。另外,try語句支持同時管理多個資源類,只要它們的對象創建語句以冒號隔開,程式在運行時即可自動回收相關的資源。
結合運用讀操作和寫操作,可以實現文件複製的功能,無非是一邊從源文件中讀出數據,另一邊緊接著往目標文件寫入數據。採用緩存讀取器和緩存寫入器逐行複製的話,具體的文件複製代碼示例如下:

	private static String mSrcName = "D:/test/aad.txt";
	private static String mDestName = "D:/test/aad_copy.txt";
	// 通過緩存字元流逐行複製文件
	private static void copyFile() {
		File src = new File(mSrcName); // 創建一個指定路徑的源文件對象
		File dest = new File(mDestName); // 創建一個指定路徑的目標文件對象
		// try(...)允許在圓括弧內部擁有多個資源創建語句,語句之間以冒號分隔
		// 分別創建源文件的緩存讀取器,以及目標文件的緩存寫入器
		try (BufferedReader breader = new BufferedReader(new FileReader(src));
				BufferedWriter bwriter = new BufferedWriter(new FileWriter(dest));) {
			for (int i=0; ; i++) {
				String line = breader.readLine(); // 從文件中讀出一行文字
				if (line == null) { // 讀到了空指針,表示已經到了文件末尾
					break;
				}
				if (i != 0) { // 第一行開頭不用換行
					bwriter.newLine(); // 另起一行,也就是在文件末尾添加換行標記
				}
				bwriter.write(line); // 往文件寫入字元串
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("文件複製完成,源文件大小="+src.length()+",新文件大小="+dest.length());
	}

 

或者也可逐個字元來複制文件,此時BufferedReader每次調用的read方法只返回整型數表示一個字元,並且BufferedWriter每次調用的write方法也只寫入該字元對應的整型數。通過依次遍歷源文件的所有字元,同時往目標文件依次寫入這些字元,從而完成逐個字元複製文件的操作流程。下麵是採取逐字元複製文件的代碼例子:

	// 通過緩存字元流逐個字元複製文件
	private static void copyFileByInt() {
		File src = new File(mSrcName); // 創建一個指定路徑的源文件對象
		File dest = new File(mDestName); // 創建一個指定路徑的目標文件對象
		// try(...)允許在圓括弧內部擁有多個資源創建語句,語句之間以冒號分隔
		// 分別創建源文件的緩存讀取器,以及目標文件的緩存寫入器
		try (BufferedReader breader = new BufferedReader(new FileReader(src));
				BufferedWriter bwriter = new BufferedWriter(new FileWriter(dest));) {
			while (true) { // 開始遍歷文件中的所有字元
				int temp = breader.read(); // 從源文件中讀出一個字元
				if (temp == -1) { // read方法返回-1表示已經讀到了文件末尾
					break;
				}
				bwriter.write(temp); // 往目標文件寫入一個字元
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("文件複製完成,源文件大小="+src.length()+",新文件大小="+dest.length());
	}

 

需要註意的是,使用字元流複製文件只有逐行複製和逐字元複製兩種方式,不可採取整個讀到字元數組再整個寫入字元數組的方式。之所以不能通過字元數組複製文件,是因為中文跟英文不一樣,一個漢字會占用多個位元組(GBK編碼的每個漢字占用兩個位元組,UTF8編碼的每個漢字占用三個位元組)。若要把文件內容讀到字元數組,勢必先得知曉該數組的長度,可是調用文件對象的length方法只能得到該文件的位元組長度,並非字元長度。譬如“白日依山盡”這個字元串在記憶體中的字元數組長度為5,寫到UTF8編碼的文件之後,文件大小是5*3=15位元組;接著想把文件內容讀到字元數組,然而15位元組的文件天曉得它有幾個字元,可能有5個UTF8編碼的中文字元,也可能有15個英文字元,也可能有5個GBK編碼的中文字元加5個英文字元共10個字元,總之你根本想不到該分配多大的字元數組。既然確定不了待讀取的字元數組長度,就無法一字不差地複製文件內容了。



更多Java技術文章參見《Java開發筆記(序)章節目錄


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、動態語⾔的定義 動態語言是在運行時確定數據類型的語言。變數使用之前不需要類型聲明,通常變數的類型是被賦值的那個值的類型。現在比較熱門的動態語言有:Python、PHP、JavaScript、Objective-C等,而 C 、 C++ 等語言則不屬於動態語言。 二、運行的過程中給對象綁定(添加) ...
  • Python3 面向對象 一丶面向對象技術簡介 類(Class): 用來描述具有相同的屬性和方法的對象的集合。它定義了該集合中每個對象所共有的屬性和方法。對象是類的實例。 方法:類中定義的函數。 類變數:類變數在整個實例化的對象中是公用的。類變數定義在類中且在函數體之外。類變數通常不作為實例變數使用 ...
  • 一、 1.final, finally, finalize 的區別? 2.int 和 Integer 有什麼區別? 3.面向對象的特征? 6.<!--[if gte mso 9]><xml> <o:OfficeDocumentSettings> <o:AllowPNG/> </o:OfficeDoc ...
  • 具體怎麼整合的網上有很多優秀的博客介紹,這裡就直接引用一篇個人覺得非常詳細的教程: https://blog.csdn.net/winter_chen001/article/details/77249029 裡面還包含了常用的額外插件,強烈推薦: PageHelper 分頁插件 mybatis ge ...
  • 今天學習了結構體這一章節,瞭解到了結構體在分配記憶體的時候採取的是對齊的方式 例如: 在這段程式中 輸出的並不是 15 //結構體集合內元素大小的簡單相加 而是 16 //此處就體現了在c語言結構體中記憶體開闢的原則 對齊原則 結構體對齊: 以最大的基本元素的大小(除數組)對齊,以本段程式為例: 1. ...
  • JANA面向對象的三大特性:封裝,繼承,多態。 今天學了繼承,繼承,通俗點說就是子類可以用父類的代碼,或重寫父類的方法、構造方法、屬性 例如我這裡要調用父類的方法: 下邊有兩個測試類,自己分別試一下,自己體驗效果。嘻嘻!!! 這是用父類new一個子類 這是直接new一個子類,這個子類的方法名如果和父 ...
  • CopyOnWriteArraySet是用Map實現的嗎? CopyOnWriteArraySet是有序的嗎? CopyOnWriteArraySet以何種方式保證元素不重覆? 如何比較兩個Set中的元素是否完全一致? ...
  • 一、各Set實現類的性能分析 HashSet和TreeSet是Set的兩個典型實現。HashSet的性能總是比TreeSet好(特別是最常用的添加、查詢元素等操作),因為TreeSet需要額外的紅黑樹演算法來維護集合元素的次序。只有當需要一個排序的Set時,才應該使用TreeSet,否則都應該使用Ha ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...