在 Java 網路程式設計中,使用 PrintWriter 進行 Socket 通訊時,開發者常遇到:伺服端呼叫 write() 發送資料,客戶端 readLine() 卻讀不到資料;改用 println() 即正常。本文深入剖析根本原因。
| 方法 | 行為 | 自動換行 | 自動 Flush |
|---|---|---|---|
write() | 直接寫入字元,不做任何處理 | 否 | 否 |
print() | 等同 write() | 否 | 否 |
println() | 在 print() 基礎上呼叫 newLine() | 是(系統相關換行符) | 是(內部呼叫 flush()) |
BufferedReader.readLine() 以換行符(\n、\r、\r\n)作為訊息邊界。write() 不自動添加換行符,導致客戶端持續等待行結束符,造成「讀不到資料」假象。
println() 內部呼叫 newLine(),進而觸發 flush(),強制將緩衝區資料推送至作業系統網路層。write() 無自動 flush,資料滯留於使用者空間緩衝區。
早期 JDK 中 SocketOutputStream 未覆寫 flush(),繼承自 FileOutputStream 的空實作,導致即使手動呼叫 flush() 也無效。此問題在較新 JDK 版本已修復,但最佳實踐仍建議使用行導向方法。
PrintWriter out = new PrintWriter(socket.getOutputStream(), true); // 第二參數 true 開啟 autoFlush
out.println("訊息內容"); // 自動換行 + 自動 flush
PrintWriter out = new PrintWriter(socket.getOutputStream());
out.write("訊息內容\r\n"); // 明確添加換行符
out.flush(); // 強制發送
DataInputStream/DataOutputStream:適合結構化資料ObjectInputStream/ObjectOutputStream:適合物件序列化// 伺服端
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
out.println("Hello Client"); // 正常工作
// 客戶端
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = in.readLine(); // 成功讀取 "Hello Client"
write() 失效的核心原因在於:缺少換行符導致 readLine() 陷塞,以及 缺少自動 flush 導致資料未真正發送。println() 同時解決兩個問題,是 Socket 文字通訊的正確選擇。理解底層機制,才能避免此類陷阱。