?
🌟?你好,我是 勵志成為糕手 !
🌌?在代碼的宇宙中,我是那個追逐優雅與性能的星際旅人。??每一行代碼都是我種下的星光,在邏輯的土壤里生長成璀璨的銀河;
🛠??每一個算法都是我繪制的星圖,指引著數據流動的最短路徑;
🔍?每一次調試都是星際對話,用耐心和智慧解開宇宙的謎題。🚀 準備好開始我們的星際編碼之旅了嗎?
續前篇:編程語言Java——核心技術篇(四)集合類詳解-CSDN博客
目錄
5. IO流
5.1 IO流基本概述
5.1.1 IO流概述
5.1.2 IO流體系
5.2 常用I/O流詳解
5.2.1 基礎文件流
5.2.2 緩沖流
5.2.3 字符流
5.2.4 高級功能流
5.3 常用I/O流示例
5.3.1 文本文件讀取示例
5.3.2?緩沖字符流完整示例(文本處理)
5.3.3?對象序列化與反序列化
5.4 常用I/O流對比
5.4.1 文件讀寫流對比
5.4.1.1 FileInputStream vs FileReader
5.4.1.2?FileOutputStream vs FileWriter
5.4.2?緩沖流對比
5.4.2.1?BufferedInputStream vs BufferedReader
5.4.2.2?緩沖流性能測試
5.4.3 高級功能流對比
5.4.3.1?DataInputStream vs ObjectInputStream
5.4.3.2?PrintStream vs PrintWriter
5.5 流的選擇決策樹
5. IO流
5.1 IO流基本概述
5.1.1 IO流概述
I/O流(Input/Output Stream)是Java中用于處理輸入輸出的抽象概念,它代表數據的流動:
-
流的方向:
-
輸入流(InputStream/Reader):從數據源讀取數據到程序
-
輸出流(OutputStream/Writer):從程序寫出數據到目的地
-
-
數據類型:
-
字節流(Byte Stream):以字節(8bit)為單位,處理二進制數據
-
InputStream/OutputStream及其子類
-
-
字符流(Character Stream):以字符(16bit)為單位,處理文本數據
-
Reader/Writer及其子類
-
-
5.1.2 IO流體系
這里直接上圖吧,這個圖里面也畫得很清楚了。我們可以看到Java在設計IO流的時候主要考慮了兩個維度,一個是流的方向,而另一個就是流的屬性了。如果從流的方向來考慮就是輸入流和輸出流;從流的屬性上考慮就是字節流和字符流了。
另外,在每一個不同的流的實現類中從取名上來講也很好分辨——我們可以把字節流想象成一個類似輸出的框架負責輸入和輸出,in和out分別代表不同方向;字節流就看成字符串,只不過可是多了一層方向的屬性,剛好輸入就是reader(讀),輸出就是writer(寫)。至于再往下細分可能不同的小類側重點又有不同,適合不同的數據類型,這個需要在后續寫代碼的過程中不斷積累。
1. 字節流體系
(1)?輸入流(InputStream)
java.io.InputStream
├── FileInputStream????????? // 文件輸入流
├── ByteArrayInputStream???? // 字節數組輸入流
├── PipedInputStream???????? // 管道輸入流
├── FilterInputStream??????? // 過濾流(裝飾器基類)
│?? ├── BufferedInputStream? // 緩沖輸入流
│?? ├── DataInputStream????? // 數據輸入流(讀取基本數據類型)
│?? └── PushbackInputStream? // 回退輸入流
├── ObjectInputStream??????? // 對象反序列化流
└── SequenceInputStream????? // 序列輸入流(合并多個流)
(2)?輸出流(OutputStream)
java.io.OutputStream
├── FileOutputStream???????? // 文件輸出流
├── ByteArrayOutputStream??? // 字節數組輸出流
├── PipedOutputStream??????? // 管道輸出流
├── FilterOutputStream?????? // 過濾流(裝飾器基類)
│?? ├── BufferedOutputStream // 緩沖輸出流
│?? ├── DataOutputStream???? // 數據輸出流(寫入基本數據類型)
│?? └── PrintStream????????? // 打印流
└── ObjectOutputStream?????? // 對象序列化流
2. 字符流體系
(1)?輸入流(Reader)
java.io.Reader
├── InputStreamReader??????? // 字節到字符的橋接流
│?? └── FileReader?????????? // 文件字符輸入流(常用)
├── CharArrayReader????????? // 字符數組輸入流
├── PipedReader????????????? // 管道字符輸入流
├── FilterReader???????????? // 過濾字符流
│?? └── PushbackReader?????? // 回退字符流
├── BufferedReader?????????? // 緩沖字符輸入流(常用)
└── StringReader???????????? // 字符串輸入流
(2)?輸出流(Writer)
java.io.Writer
├── OutputStreamWriter?????? // 字符到字節的橋接流
│?? └── FileWriter?????????? // 文件字符輸出流(常用)
├── CharArrayWriter????????? // 字符數組輸出流
├── PipedWriter????????????? // 管道字符輸出流
├── FilterWriter???????????? // 過濾字符流
├── BufferedWriter?????????? // 緩沖字符輸出流(常用)
├── PrintWriter????????????? // 打印字符流(常用)
└── StringWriter???????????? // 字符串輸出流
?這里只是做一個簡單的解釋,如果想看詳細的解析可以直接嘗試看看源碼。
5.2 常用I/O流詳解
需要注意的是,因為流的運行需要涉及到輸入和輸出,所以一般需要用try-catch的形式來捕獲異常,或者在寫代碼前就將異常拋出,不然代碼會報錯。
5.2.1 基礎文件流
1. FileInputStream(文件字節輸入流)
特點:
-
用于讀取二進制文件
-
每次讀取一個字節(8位)
-
適合處理圖片、音頻、視頻等非文本文件
構造方法:
FileInputStream(String name)
FileInputStream(File file)
常用方法:
int read()? // 讀取單個字節
int read(byte[] b)? // 讀取到字節數組
int read(byte[] b, int off, int len)? // 讀取到數組指定位置
示例代碼:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;public class FileInputStreamExample {public static void main(String[] args) {FileInputStream fis = null;try {File file = new File("example.txt");fis = new FileInputStream(file);System.out.println("文件大小: " + file.length() + " bytes");int content;while ((content = fis.read()) != -1) {System.out.print((char) content);}} catch (IOException e) {System.err.println("讀取文件時發生錯誤: " + e.getMessage());e.printStackTrace();} finally {if (fis != null) {try {fis.close();} catch (IOException e) {System.err.println("關閉文件流時發生錯誤: " + e.getMessage());}}}}
}
2. FileOutputStream(文件字節輸出流)
特點:
-
用于寫入二進制文件
-
支持追加模式
-
必須手動調用flush()或close()確保數據寫入
構造方法:
FileOutputStream(String name)
FileOutputStream(String name, boolean append)? // append為true表示追加
FileOutputStream(File file)
常用方法:
void write(int b)? // 寫入單個字節
void write(byte[] b)
void write(byte[] b, int off, int len)
示例代碼:
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;public class FileOutputStreamExample {public static void main(String[] args) {// 使用try-with-resources自動關閉資源try (FileOutputStream fos = new FileOutputStream("output.log", true)) { // true表示追加模式String logEntry = "新的日志條目 - " + System.currentTimeMillis() + "\n";// 轉換為字節數組寫入byte[] bytes = logEntry.getBytes(StandardCharsets.UTF_8);fos.write(bytes);System.out.println("日志寫入成功");} catch (IOException e) {System.err.println("寫入文件時發生錯誤: " + e.getMessage());e.printStackTrace();}}
}
5.2.2 緩沖流
1. BufferedInputStream(緩沖字節輸入流)
優勢:
-
默認8KB緩沖區(可配置)
-
減少實際磁盤I/O次數
-
特別適合大文件讀取
典型用法:緩沖流文件復制
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class BufferedFileCopy {public static void main(String[] args) {if (args.length < 2) {System.out.println("用法: java BufferedFileCopy <源文件> <目標文件>");return;}String sourceFile = args[0];String destFile = args[1];try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile))) {byte[] buffer = new byte[8192]; // 8KB緩沖區int bytesRead;while ((bytesRead = bis.read(buffer)) != -1) {bos.write(buffer, 0, bytesRead);}System.out.println("文件復制完成");} catch (IOException e) {System.err.println("文件復制過程中發生錯誤: " + e.getMessage());e.printStackTrace();// 刪除可能不完整的輸出文件try {new java.io.File(destFile).delete();} catch (SecurityException se) {System.err.println("無法刪除不完整的輸出文件: " + se.getMessage());}}}
}
2. BufferedOutputStream(緩沖字節輸出流)
注意事項:
-
必須調用flush()或close()確保數據寫出
-
緩沖區滿時自動flush
-
適合頻繁的小數據寫入
性能對比:
// 無緩沖寫入1萬次(耗時約1200ms)
try (FileOutputStream fos = new FileOutputStream("no_buffer.txt")) {for (int i = 0; i < 10_000; i++) {fos.write("line\n".getBytes());}
}// 有緩沖寫入1萬次(耗時約50ms)
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("with_buffer.txt"))) {for (int i = 0; i < 10_000; i++) {bos.write("line\n".getBytes());}bos.flush(); // 確保所有數據寫出
}
5.2.3 字符流
1. InputStreamReader(字節到字符的橋梁)
核心作用:
-
將字節流轉換為字符流
-
可指定字符編碼(避免亂碼)
編碼處理示例:
// 讀取GBK編碼的文本文件
try (InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk_file.txt"), "GBK")) {char[] buffer = new char[1024];int charsRead;while ((charsRead = isr.read(buffer)) != -1) {String text = new String(buffer, 0, charsRead);System.out.print(text);}
}catch{...
}
2. FileReader(文件字符輸入流)
本質:
-
InputStreamReader的子類
-
使用系統默認編碼(容易導致跨平臺問題)
最佳實踐:
// 不推薦(使用默認編碼)
FileReader reader = new FileReader("file.txt");// 推薦做法(明確指定編碼)
Reader reader = new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8);
3. BufferedReader(帶緩沖的字符流)
特有方法:
String readLine()? // 讀取整行(去掉換行符)
經典用法:
// 統計文本文件行數
try (BufferedReader br = new BufferedReader(new FileReader("big_text.txt"))) {int lineCount = 0;while (br.readLine() != null) {lineCount++;}System.out.println("總行數: " + lineCount);
}catch{...
}
5.2.4 高級功能流
1. DataInputStream/DataOutputStream(基本數據類型處理)
支持的數據類型:
-
所有Java基本類型(int, double, boolean等)
-
String(UTF格式)
二進制文件讀寫示例:
// 寫入結構化數據
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("settings.dat"))) {dos.writeUTF("系統配置");? // 字符串dos.writeInt(1024);????? // 整數dos.writeDouble(3.1415); // 浮點數dos.writeBoolean(true);? // 布爾值
}// 讀取結構化數據
try (DataInputStream dis = new DataInputStream(new FileInputStream("settings.dat"))) {String title = dis.readUTF();int size = dis.readInt();double value = dis.readDouble();boolean enabled = dis.readBoolean();System.out.printf("%s: size=%d, value=%.2f, enabled=%b",title, size, value, enabled);
}
2. ObjectInputStream/ObjectOutputStream(對象序列化)
序列化要求:
-
實現Serializable接口
-
建議添加serialVersionUID
-
transient修飾不序列化的字段
完整示例:
class User implements Serializable {private static final long serialVersionUID = 1L;private String username;private transient String password;? // 不被序列化// 構造方法、getter/setter省略
}// 序列化對象
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {User user = new User("admin", "123456");oos.writeObject(user);
}// 反序列化對象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {User user = (User) ois.readObject();System.out.println(user.getUsername());? // 輸出: adminSystem.out.println(user.getPassword());? // 輸出: null(transient字段)
}
5.3 常用I/O流示例
5.3.1 文本文件讀取示例
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;public class TextFileReader {public static void main(String[] args) {Path filePath = Paths.get("poem.txt");// 方法1:傳統方式(JDK1.7之前)System.out.println("=== 傳統讀取方式 ===");BufferedReader br = null;try {br = new BufferedReader(new FileReader(filePath.toFile()));String line;while ((line = br.readLine()) != null) {System.out.println(line);}} catch (IOException e) {System.err.println("讀取文件時發生錯誤: " + e.getMessage());} finally {if (br != null) {try {br.close();} catch (IOException e) {System.err.println("關閉流時發生錯誤: " + e.getMessage());}}}// 方法2:try-with-resources方式(推薦)System.out.println("\n=== 現代讀取方式 ===");try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {reader.lines().forEach(System.out::println);} catch (IOException e) {System.err.println("讀取文件時發生錯誤: " + e.getMessage());e.printStackTrace();}}
}
5.3.2?緩沖字符流完整示例(文本處理)
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;public class BufferedTextProcessor {public static void main(String[] args) {Path inputFile = Paths.get("input.txt");Path outputFile = Paths.get("output_uppercase.txt");// 處理文本文件(轉換為大寫)processTextFile(inputFile, outputFile);// 讀取文件到ListList<String> lines = readLinesFromFile(inputFile);System.out.println("讀取到 " + lines.size() + " 行文本");}/*** 使用緩沖字符流處理文本文件*/private static void processTextFile(Path input, Path output) {try (BufferedReader reader = Files.newBufferedReader(input, StandardCharsets.UTF_8);BufferedWriter writer = Files.newBufferedWriter(output, StandardCharsets.UTF_8)) {String line;int lineCount = 0;while ((line = reader.readLine()) != null) {// 處理每一行(這里轉換為大寫)String processedLine = line.toUpperCase();// 寫入處理后的行writer.write(processedLine);writer.newLine(); // 寫入系統相關的換行符lineCount++;// 每處理100行輸出進度if (lineCount % 100 == 0) {System.out.println("已處理 " + lineCount + " 行");}}// 確保所有數據寫出writer.flush();System.out.println("文件處理完成,共處理 " + lineCount + " 行");} catch (IOException e) {System.err.println("文本處理失敗: " + e.getMessage());e.printStackTrace();}}/*** 使用緩沖流讀取文件到List*/private static List<String> readLinesFromFile(Path file) {List<String> lines = new ArrayList<>();try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {String line;while ((line = reader.readLine()) != null) {lines.add(line);}} catch (IOException e) {System.err.println("讀取文件失敗: " + e.getMessage());e.printStackTrace();}return lines;}
}
5.3.3?對象序列化與反序列化
import java.io.*;
import java.util.ArrayList;
import java.util.List;class Student implements Serializable {private static final long serialVersionUID = 1L;private String name;private transient String password; // 不被序列化private int age;public Student(String name, String password, int age) {this.name = name;this.password = password;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", password='" + password + '\'' +", age=" + age +'}';}
}public class ObjectSerializationDemo {public static void main(String[] args) {List<Student> students = new ArrayList<>();students.add(new Student("張三", "zhang123", 20));students.add(new Student("李四", "li456", 22));// 序列化String filename = "students.dat";try (ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(filename)))) {oos.writeObject(students);System.out.println("對象序列化完成");} catch (IOException e) {System.err.println("序列化過程中發生錯誤: " + e.getMessage());e.printStackTrace();}// 反序列化try (ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(filename)))) {@SuppressWarnings("unchecked")List<Student> deserialized = (List<Student>) ois.readObject();System.out.println("\n反序列化結果:");deserialized.forEach(System.out::println);} catch (IOException | ClassNotFoundException e) {System.err.println("反序列化過程中發生錯誤: " + e.getMessage());e.printStackTrace();}}
}
ps:這里做一個補充,可能很多人會不明白序列化和反序列化是什么意思。
?序列化(Serialization)?:
將對象的狀態信息轉換為可存儲(如文件、數據庫)或可傳輸(如網絡通信)的標準化格式,常見形式包括二進制字節流、JSON、XML等。其本質是解決對象在跨平臺、跨語言或持久化場景下的數據交換問題。??
?反序列化(Deserialization)?:
將序列化后的數據重新解析并恢復為內存中的對象,確保程序能繼續操作原始數據結構。例如從網絡接收的JSON數據還原為程序中的類實例。??
5.4 常用I/O流對比
5.4.1 文件讀寫流對比
5.4.1.1 FileInputStream vs FileReader
相同點:
-
都用于文件讀取
-
都是低級流(直接連接數據源)
不同點:
特性 | FileInputStream | FileReader |
---|---|---|
數據類型 | 字節(8bit) | 字符(16bit) |
處理內容 | 二進制文件(圖片、視頻等) | 文本文件 |
編碼處理 | 無編碼轉換 | 自動按系統默認編碼/指定編碼轉換 |
讀取方法 | read()返回0-255的int | read()返回0-65535的int(Unicode) |
5.4.1.2?FileOutputStream vs FileWriter
相同點:
-
都用于文件寫入
-
都是低級流
不同點:
特性 | FileOutputStream | FileWriter |
---|---|---|
數據類型 | 字節 | 字符 |
寫入內容 | 二進制數據 | 文本數據 |
編碼處理 | 直接寫入字節 | 字符→字節轉換(可指定編碼) |
構造參數 | 可追加寫入(append=true) | 可追加寫入(append=true) |
5.4.2?緩沖流對比
5.4.2.1?BufferedInputStream vs BufferedReader
性能對比:
-
緩沖流比非緩沖流快8-10倍(減少實際I/O操作次數)
-
BufferedReader特有readLine()方法
示例對比:
// 使用BufferedInputStream讀取
try (InputStream is = new BufferedInputStream(new FileInputStream("largefile.bin"), 8192)) { // 8KB緩沖區byte[] buffer = new byte[1024];int bytesRead;while ((bytesRead = is.read(buffer)) != -1) {// 處理數據}
}// 使用BufferedReader讀取文本
try (BufferedReader br = new BufferedReader(new FileReader("text.txt"), 16384)) { // 16KB緩沖區String line;while ((line = br.readLine()) != null) { // 按行讀取System.out.println(line);}
}
5.4.2.2?緩沖流性能測試
// 測試無緩沖 vs 有緩沖的復制速度
long start = System.nanoTime();
try (InputStream is = new FileInputStream("1GBfile.zip");OutputStream os = new FileOutputStream("copy.zip")) {byte[] buffer = new byte[8192];int bytesRead;while ((bytesRead = is.read(buffer)) != -1) {os.write(buffer, 0, bytesRead);}
}
long end = System.nanoTime();
System.out.println("無緩沖耗時: " + (end-start)/1_000_000 + "ms");start = System.nanoTime();
try (InputStream is = new BufferedInputStream(new FileInputStream("1GBfile.zip"));OutputStream os = new BufferedOutputStream(new FileOutputStream("copy.zip"))) {byte[] buffer = new byte[8192];int bytesRead;while ((bytesRead = is.read(buffer)) != -1) {os.write(buffer, 0, bytesRead);}
}
end = System.nanoTime();
System.out.println("緩沖流耗時: " + (end-start)/1_000_000 + "ms");
典型結果:
無緩沖耗時: 4523ms
緩沖流耗時: 587ms
5.4.3 高級功能流對比
5.4.3.1?DataInputStream vs ObjectInputStream
特性 | DataInputStream | ObjectInputStream |
---|---|---|
主要用途 | 讀取基本數據類型 | 對象反序列化 |
讀取方法 | readInt(), readDouble()等 | readObject() |
數據格式 | 簡單二進制格式 | Java序列化協議 |
版本兼容 | 無版本概念 | 使用serialVersionUID |
典型用途 | 自定義二進制協議 | Java對象持久化 |
示例:
// DataInputStream讀取結構化二進制數據
try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {int version = dis.readInt();String name = dis.readUTF();double price = dis.readDouble();boolean inStock = dis.readBoolean();
}// ObjectInputStream讀取對象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("product.ser"))) {Product p = (Product) ois.readObject();System.out.println(p);
}
5.4.3.2?PrintStream vs PrintWriter
特性 | PrintStream | PrintWriter |
---|---|---|
繼承體系 | FilterOutputStream子類 | Writer子類 |
輸出目標 | 字節流 | 字符流 |
自動刷新 | 可配置(autoFlush) | 可配置(autoFlush) |
異常處理 | 設置錯誤標志(無異常拋出) | 可獲取IO異常 |
方法 | print(), println(), printf() | 相同方法集 |
示例:
// PrintStream使用(System.out就是PrintStream)
try (PrintStream ps = new PrintStream(new FileOutputStream("log.txt"), true, "UTF-8")) {ps.println("錯誤日志:");ps.printf("時間: %tF %<tT%n", new Date());ps.println("溫度: " + 25.6);
}// PrintWriter使用
try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter("report.txt")))) {pw.println("=== 測試報告 ===");pw.println("通過用例: " + 42);pw.println("失敗用例: " + 3);pw.printf("通過率: %.2f%%%n", 42.0/45*100);
}
?這個就當一個擴展吧,因為感覺這個流和平時用的System.out.print()挺像的。
5.5 流的選擇決策樹
一樣的,建議放大看,幾乎涵蓋了所有的應用場景。對于不同場景的敏感性是在不斷地敲代碼中提升的,與其去死記硬背,不如先面對問題,然后針對不同的問題再去尋求答案,找到最適配的方法;這樣效率才會大大提升!
🌟 我是 勵志成為糕手 ,感謝你與我共度這段技術時光!
? 如果這篇文章為你帶來了啟發:
? 【收藏】關鍵知識點,打造你的技術武器庫
💡 【評論】留下思考軌跡,與同行者碰撞智慧火花
🚀 【關注】持續獲取前沿技術解析與實戰干貨🌌 技術探索永無止境,讓我們繼續在代碼的宇宙中:
? 用優雅的算法繪制星圖
? 以嚴謹的邏輯搭建橋梁
? 讓創新的思維照亮前路🛰? 下期預告:《編程語言Java——核心技術篇(六)解剖Java反射:從Class對象到方法調用的魔鬼細節》
📡 保持連接,我們下次太空見!