系列文章目錄
? ?JavaIO流的使用和修飾器模式
文章目錄
- 系列文章目錄
- 前言
- 一、字節流:
- 1.FileInputStream(讀取文件)
- 2.FileOutputStream(寫入文件)
- 二、字符流:
- 1..基礎字符流:
- 2.處理流:
- 3.對象處理流:
- 4.轉換流:
- ?三、修飾器模式
- 總結
前言
? 前面我們講解了Java文件和IO流的基礎部分。把流簡單的分了一下類,但是我們還不知道具體是如何是使用的,下面我們將詳細的講解一下這些個流各自的職責是什么,簡言之就是各自的使用方式。然后我還想給大家強戴一下IO流當中的修飾器模式,因為這個實際上通過封裝真的太牛逼了。
? ? ? ? ? 我先給大家按字節流和字符流的分類方式來進行講述:
一、字節流:
? ? ? ?用于處理二進制數據(如圖片、視頻、任何文件)?,核心類為?InputStream
?和?OutputStream
? ? ? ? (1)FileInputStream(讀取文件)
? ? ? ?? ? ?每次調用?read()
?方法從磁盤讀取1字節,頻繁IO操作性能差。
? 適用場景:小文件讀取或需要逐字節處理的場景。
try (InputStream in = new FileInputStream("test.jpg")) {int byteData;while ((byteData = in.read()) != -1) { // 每次讀取1字節// 處理字節(例如加密、校驗)System.out.print((char)byteData + " ");}
} catch (IOException e) {e.printStackTrace();
}
? ? ? ??我們要注意,這樣單個字節讀取,如果文件當中有漢字就不行了。? ? ? ? ? ? ? ? ??
?所以進階版可以用int read(byte[] b)方法來讀取,這個方法底層是從該輸入流中讀取最多b.length字節數據到字節數組,如果讀取正常,返回實際讀取字節數, -1表示的是讀取完畢了。但記得最后還要轉換為字符串 new String(buf,0,readlen).
? ? ? ? ? ?(2) FileOutputStream(寫入文件)
? ? ? ??注意點:若文件不存在會自動創建,若存在默認覆蓋(通過構造參數可設置為追加模式)。
// 第二個參數 true 表示追加寫入
try (OutputStream out = new FileOutputStream("log.txt", true)) {String logEntry = "Error occurred at " + new Date() + "\n";out.write(logEntry.getBytes(StandardCharsets.UTF_8)); // 顯式指定編碼
} catch (IOException e) {e.printStackTrace();
}
這里還有一處細節要注意,就是這樣創建,寫入內容會覆蓋原來的內容,但如果是這樣創建的 new FileOutputStream(filepath,true),這樣再寫入內容就會追加到文件后面。
二、字符流
1.基礎字符流:
? (1)FileReader 讀文件
? 這里循環讀取使用read()是單個字符讀取,使用read(buf)返回的是實際取到的字符數.
? (2)FileWriter? ? ? 寫文件
這里面注意一定要關閉流,或者Flush才能真正的把數據寫入到文件
2.處理流:
BufferedReader
?和?BufferedWriter:
readLine()
?可逐行讀取文本。
// 讀取CSV文件并解析
try (BufferedReader br = new BufferedReader(new FileReader("data.csv"))) {String line;while ((line = br.readLine()) != null) {String[] columns = line.split(",");// 處理每一列數據}
}// 寫入帶換行的文本
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {bw.write("Line 1");bw.newLine(); // 跨平臺換行(Windows為\r\n,Linux為\n)bw.write("Line 2");
}
像BufferedReader類中,有屬性Reader,即可以封裝一個節點流 (該節點流可以是任意的,只要是Reader的子類就行,這個我們下面講修飾器模式再講)。
details:
1.BufferedReader
?和?BufferedWriter都是按照字符操作的。
2.不要去操作二進制文件(如聲音,視頻等)可能會造成文件損壞。
? ?總結:
場景 | 正確流類型 | 原因 |
---|---|---|
圖片、視頻、EXE文件 | 字節流 | 直接處理原始字節,避免編解碼干擾 |
文本文件(.txt) | 字符流 | 正確處理字符編碼(如UTF-8、GBK) |
混合數據(如PDF) | 字節流 | PDF包含文本和二進制結構,需精確控制字節 |
網絡傳輸數據 | 字節流 | 網絡協議基于字節,而非字符 |
所以說字符流是“文本專用工具”,操作二進制文件就像用剪刀擰螺絲——不僅費力,還可能搞砸!
? 3.對象處理流:
? 能夠將基本數據類型或者對象進行序列化和反序列化的操作。
? ? ? 這里我們需要注意的是如果需要讓某個對象支持序列化機制,則必須讓其類是可序列化的,而為了讓某個類是可序列化的,該類必須實現如下兩個接口之一:
? ? Serializable? 和? ?Externalizable? 我們常用的是Serializable接口,因為它不用再重寫方法了。
? ??
class User implements Serializable {private static final long serialVersionUID = 1L; // 版本號private String name;private transient String password; // transient字段不會被序列化
}// 序列化對象到文件
User user = new User("Alice", "secret");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {oos.writeObject(user);
}// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {User restoredUser = (User) ois.readObject();System.out.println(restoredUser.getName()); // 輸出 "Alice"System.out.println(restoredUser.getPassword()); // 輸出 null(transient字段)
}
? ? ?注意讀取(反序列化)的順序需要和保存數據(序列化)的順序一致,否則會出現異常。
還有最容易忽略的一點就是序列化對象時,要求里面的屬性的類型也需要實現序列化接口。
? 序列化對象時,默認將里面所有屬性都會進序列化,除了static或者transient修飾的成員。
? ? 4.轉換流:
亂碼的本質是 ?字符編碼不匹配:
- ?寫入時:文本按編碼A(如UTF-8)轉換為字節。
- ?讀取時:字節按編碼B(如GBK)解碼為字符。
- ?結果:編碼A和編碼B的映射關系不同,導致字符顯示錯誤。
轉換流的作用
類名 | 功能 | 核心價值 |
---|---|---|
InputStreamReader | 將字節流(InputStream )按指定編碼轉換為字符流 | ?解決讀取時的編碼問題 |
OutputStreamWriter | 將字符流按指定編碼轉換為字節流(OutputStream ) | ?解決寫入時的編碼問題 |
try (Reader reader = new InputStreamReader(new FileInputStream("utf8_file.txt"), StandardCharsets.UTF_8)) {// 正確讀取中文字符int data;while ((data = reader.read()) != -1) {System.out.print((char) data);}
}try (Reader reader = new InputStreamReader(new FileInputStream("utf8_file.txt"), StandardCharsets.UTF_8)) {// 正確讀取中文字符int data;while ((data = reader.read()) != -1) {System.out.print((char) data);}
}
綜上可知,學習IO流我們必須要知道什么時候使用什么流。
? ?三、修飾器模式:
? 其實我在學習的過程中也很疑惑這個修飾器模式到底有什么用,不就是像套娃一樣一層套著一層嗎,但是當我們真正理解了才發現Java設計者有多牛逼。
? ? ? ? 像以BufferedInputStream舉例:
BufferedInputStream
?的緩沖機制
- ?內部緩沖區:
BufferedInputStream
?維護一個字節數組(默認大小 8KB),用于臨時存儲從底層流讀取的數據。 - ?讀取邏輯:
- 當用戶調用?
read()
?時,BufferedInputStream
?會優先從緩沖區讀取數據。 - ?如果緩沖區為空,它會一次性從底層?
InputStream
(如?FileInputStream
)讀取一批數據(填滿緩沖區)。 - 后續的?
read()
?直接從緩沖區返回數據,直到緩沖區耗盡,再重復步驟 2。
- 當用戶調用?
? ?
- 數據來源:
BufferedInputStream
?本身不連接任何數據源(如文件、網絡等),它只是一個“功能增強包裝器”。 - ?依賴關系:緩沖流需要底層流提供原始數據,而?
FileInputStream
?是唯一能直接讀取文件的節點流。
? ?裝飾器模式(Decorator Pattern)的核心思想是 ?動態地為對象添加功能,同時保持接口的一致性。
? 舉一個咖啡加料的例子:
? ? ?假設你經營一家咖啡店,需要靈活組合咖啡和配料(如牛奶、糖),但不想為每種組合創建子類(如?MilkSugarCoffee
、SugarCoffee
?等)。裝飾器模式可以完美解決這個問題
? ? ?1.定義基礎組件:
? ? ??
// 基礎接口:咖啡
public interface Coffee {double getCost();String getDescription();
}// 具體組件:基礎咖啡
public class SimpleCoffee implements Coffee {@Overridepublic double getCost() { return 2.0; }@Overridepublic String getDescription() { return "基礎咖啡"; }
}
? ? ?2. 定義裝飾器基類:
? ?
// 裝飾器基類:實現 Coffee 接口,并持有一個 Coffee 對象
public abstract class CoffeeDecorator implements Coffee {protected Coffee decoratedCoffee;public CoffeeDecorator(Coffee coffee) {this.decoratedCoffee = coffee;}// 委托給被裝飾的 Coffee 對象@Overridepublic double getCost() { return decoratedCoffee.getCost(); }@Overridepublic String getDescription() { return decoratedCoffee.getDescription(); }
}
? ? ?3. 具體修飾器:牛奶或糖:
? ? ?
// 牛奶裝飾器
public class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee coffee) {super(coffee);}@Overridepublic double getCost() { return super.getCost() + 0.5; }@Overridepublic String getDescription() { return super.getDescription() + "+牛奶"; }
}// 糖裝飾器
public class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee coffee) {super(coffee);}@Overridepublic double getCost() { return super.getCost() + 0.2; }@Overridepublic String getDescription() { return super.getDescription() + "+糖"; }
}
? ? ?4.使用修飾器的動態組合:?
public class Main {public static void main(String[] args) {// 基礎咖啡Coffee coffee = new SimpleCoffee();System.out.println(cost: " + coffee.getCost() + ", desc: " + coffee.getDescription());// 加牛奶coffee = new MilkDecorator(coffee);System.out.println(cost: " + coffee.getCost() + ", desc: " + coffee.getDescription());// 再加糖coffee = new SugarDecorator(coffee);System.out.println(cost: " + coffee.getCost() + ", desc: " + coffee.getDescription());}
}
而在IO流中:
- 組件接口:
InputStream
(所有輸入流的基類)。 - ?具體組件:
FileInputStream
(直接操作文件的節點流)。 - ?裝飾器基類:
FilterInputStream
(實現?InputStream
,并持有?InputStream
?對象)。 - ?具體裝飾器:
BufferedInputStream
(擴展?FilterInputStream
,添加緩沖功能)。
// 節點流:直接讀取文件
InputStream fileStream = new FileInputStream("data.txt");// 裝飾器:添加緩沖功能
InputStream bufferedStream = new BufferedInputStream(fileStream);// 可以繼續裝飾:例如添加解密功能(假設有 DecryptInputStream)
InputStream decryptedStream = new DecryptInputStream(bufferedStream);
當調用?bufferedStream.read()
?時:
- ?檢查緩沖區:如果有數據,直接返回。
- ?緩沖區為空:調用底層?
fileStream.read(byte[])
?批量讀取數據到緩沖區。 - ?返回數據:從緩沖區返回一個字節。
其實吧,處理流(如?BufferedInputStream
)需要傳入?InputStream
?對象的核心目的,正是為了在自己的成員方法中調用底層流的?read
?方法,并在其基礎上添加額外功能(如緩沖、編碼轉換等)。這是裝飾器模式的精髓所在。
總結
以上就是今天要講的內容,本文僅簡單的講述了IO流分類后的使用和例子,然后講了一下修飾器模式,接下來我會一直持續更新,謝謝大家。