字节流、字符流与缓冲
学习目标
- 能按字节或字符选择 InputStream、OutputStream、Reader 和 Writer。
- 会组合缓冲层和显式 Charset,避免逐字节读写与乱码。
- 能处理 try-with-resources、EOF、mark/reset、压缩流和数据流边界。
核心知识点
专业术语
- InputStream/OutputStream:以
int或byte[]读写原始字节。 - Reader/Writer:按字符读写,并通过 Charset 把字节解码或编码。
- Buffered:用内存缓冲减少底层系统调用,不改变数据语义。
- EOF(End of File):读取结束信号,字节流通常用
-1表示。
白话解释与边界
图片、压缩包和协议帧使用字节流;文本才在确定编码后使用字符流。BufferedInputStream 和 BufferedReader 只是加速层,不会把字节自动变成正确字符。所有流都要关闭,写出流尤其要 flush/close,否则末尾缓冲内容可能尚未落盘。
常用用法
InputStream.read:读取一个字节或一批字节
read() 返回的是 0–255 或 -1,不能直接当作有符号 byte;大量数据应使用字节数组重载,避免逐字节调用造成过多系统调用。
// 说明:InputStream.read:读取一个字节或一批字节。
import java.io.ByteArrayInputStream;
import java.io.InputStream;
public class InputStreamReadDemo {
public static void main(String[] args) throws Exception {
try (InputStream input = new ByteArrayInputStream(new byte[]{65, 66})) {
// 输入:try (InputStream input = new ByteArrayInputStream(new byte[]{65, 66})) {;资源变量 input 接收 new ByteArrayInputStream(new byte[]{65, 66}),try 结束时自动关闭。
// 关键变化:try (InputStream input = new ByteArrayInputStream(new byte[]{65, 66})) {;创建资源 input,构造表达式为 new ByteArrayInputStream(new byte[]{65, 66});try 结束时关闭该资源。
int first = input.read();
// 关键变化:int first = input.read();;从 input 读取一个字节,返回值写入 first;读到末尾时返回 -1。
byte[] rest = input.readAllBytes();
// 关键变化:byte[] rest = input.readAllBytes();;从 input 读取剩余字节,byte[] 返回值写入 rest;读取位置到达末尾。
System.out.println((char) first + " / " + rest.length);
// 输出:A / 1
}
}
}OutputStream.write:写出字节
write(int) 只写最低 8 位;写文本前先用明确 Charset 编码。
// 说明:OutputStream.write:写出字节。
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class OutputStreamWriteDemo {
public static void main(String[] args) throws Exception {
try (OutputStream output = new ByteArrayOutputStream()) {
// 输入:try (OutputStream output = new ByteArrayOutputStream()) {;资源变量 output 接收 new ByteArrayOutputStream(),try 结束时自动关闭。
// 关键变化:try (OutputStream output = new ByteArrayOutputStream()) {;创建资源 output,构造表达式为 new ByteArrayOutputStream();try 结束时关闭该资源。
output.write("Java".getBytes(StandardCharsets.UTF_8));
output.flush();
// 关键变化:output.flush();;刷新 output,缓冲字节写入目标;缓冲区状态更新。
System.out.println(output.toString());
// 输出:Java
}
}
}这里的 ByteArrayOutputStream 不需要真正关闭,但统一使用 try-with-resources 便于替换成文件或网络流。
缓冲字节传输:减少底层读写次数
缓冲流减少底层访问次数;如果底层已经是内存流,性能收益很小,但模式在替换为文件或 Socket 时仍然通用。
// 说明:缓冲字节传输:减少底层读写次数。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
public class BufferedByteStreamDemo {
public static void main(String[] args) throws Exception {
var source = new ByteArrayInputStream(new byte[]{1, 2, 3});
var target = new ByteArrayOutputStream();
try (var input = new BufferedInputStream(source);
// 输入:try (var input = new BufferedInputStream(source);;资源变量 input 接收 new BufferedInputStream(source,try 结束时自动关闭。
// 关键变化:try (var input = new BufferedInputStream(source);;创建资源 input,构造表达式为 new BufferedInputStream(source;try 结束时关闭该资源。
var output = new BufferedOutputStream(target)) {
input.transferTo(output);
// 关键变化:input.transferTo(output);;从 input 向 output 传输剩余字节,返回传输字节数;读取位置到达末尾。
}
System.out.println(target.size());
// 输出:3
}
}InputStream.transferTo:直接转发字节
transferTo 适合完整转发,但不会替调用方关闭输入或输出,也不会限制总字节数;网络输入要配合大小上限和超时。
// 说明:InputStream.transferTo:直接转发字节。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class TransferToDemo {
public static void main(String[] args) throws Exception {
var input = new ByteArrayInputStream(new byte[]{10, 20});
var output = new ByteArrayOutputStream();
long count = input.transferTo(output);
// 关键变化:long count = input.transferTo(output);;把 input 的剩余字节传给 output,返回实际传输字节数 2 并写入 count。
System.out.println(count + " / " + output.size());
// 输出:2 / 2
}
}读写字符:使用字符流传输文本
字符流按 UTF-16 char 处理,不等于按 Unicode 码点处理;表情等补充字符可能占用两个 char。
// 说明:读写字符:使用字符流传输文本。
import java.io.StringReader;
import java.io.StringWriter;
public class CharacterStreamDemo {
public static void main(String[] args) throws Exception {
try (var reader = new StringReader("你好"); var writer = new StringWriter()) {
// 输入:try (var reader = new StringReader("你好"); var writer = new StringWriter()) {;资源变量 reader 接收 new StringReader("你好"); var writer = new StringWriter(),try 结束时自动关闭。
// 关键变化:try (var reader = new StringReader("你好"); var writer = new StringWriter()) {;创建资源 reader,构造表达式为 new StringReader("你好"); var writer = new StringWriter();try 结束时关闭该资源。
writer.write(reader.read());
char[] rest = new char[1];
reader.read(rest);
// 关键变化:reader.read(rest);;从 reader 读取剩余字符“好”到 rest[0],返回字符数 1;读取位置前进。
writer.write(rest);
System.out.println(writer);
// 输出:你好
}
}
}转换字节与字符:显式指定 Charset
字节和字符边界必须明确指定同一 Charset;不要依赖 new InputStreamReader(input) 的平台默认编码。
// 说明:转换字节与字符:显式指定 Charset。
import java.io.ByteArrayInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class CharsetBridgeDemo {
public static void main(String[] args) throws Exception {
byte[] bytes = "你好".getBytes(StandardCharsets.UTF_8);
try (var reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8))) {
// 输入:try (var reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8))) {;资源变量 reader 接收 new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8)),try 结束时自动关闭。
// 关键变化:try (var reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8))) {;创建资源 reader,构造表达式为 new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8));try 结束时关闭该资源。
System.out.println(reader.readLine());
// 输出:你好
}
}
}BufferedReader(Reader):为字符输入增加缓冲
BufferedReader 接收已有的 Reader 并在其上增加缓冲,构造时不读取数据,关闭外层 reader 时会继续关闭传入的 source。
// 说明:BufferedReader(Reader):为字符输入增加缓冲。
import java.io.BufferedReader;
import java.io.StringReader;
StringReader source = new StringReader("first\nsecond");
try (BufferedReader reader = new BufferedReader(source)) {
// 输入:try (BufferedReader reader = new BufferedReader(source)) {;资源变量 reader 接收 new BufferedReader(source),try 结束时自动关闭。
// 关键变化:try (BufferedReader reader = new BufferedReader(source)) {;创建资源 reader,构造表达式为 new BufferedReader(source);try 结束时关闭该资源。
// reader 从 source 成批填充内部缓冲,readLine() 只返回本行字符。
System.out.println(reader.readLine());
// 输出:first
}BufferedReader.readLine:按行读取
readLine 不返回行尾换行符;行很长或输入不可信时仍要考虑内存上限。
// 说明:BufferedReader.readLine:按行读取。
import java.io.BufferedReader;
import java.io.StringReader;
public class BufferedReaderDemo {
public static void main(String[] args) throws Exception {
try (BufferedReader reader = new BufferedReader(new StringReader("java\nsql"))) {
// 输入:try (BufferedReader reader = new BufferedReader(new StringReader("java\nsql"))) {;资源变量 reader 接收 new BufferedReader(new StringReader("java\nsql")),try 结束时自动关闭。
// 关键变化:try (BufferedReader reader = new BufferedReader(new StringReader("java\nsql"))) {;创建资源 reader,构造表达式为 new BufferedReader(new StringReader("java\nsql"));try 结束时关闭该资源。
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
// 输出:java
}
}
}
}需要流式组合时可用 reader.lines(),同样要关闭 Reader。
缓冲文本输出:选择异常透明或格式化写入
BufferedWriter 适合受控文本写出;PrintWriter 便于格式化,但默认会吞掉部分 I/O 异常并通过 checkError() 报告,不能误当成异常透明的 Writer。
// 说明:缓冲文本输出:选择异常透明或格式化写入。
import java.io.BufferedWriter;
import java.io.StringWriter;
import java.io.PrintWriter;
public class TextWriterDemo {
public static void main(String[] args) throws Exception {
var target = new StringWriter();
try (var writer = new BufferedWriter(target)) {
// 输入:try (var writer = new BufferedWriter(target)) {;资源变量 writer 接收 new BufferedWriter(target),try 结束时自动关闭。
// 关键变化:try (var writer = new BufferedWriter(target)) {;创建资源 writer,构造表达式为 new BufferedWriter(target);try 结束时关闭该资源。
writer.write("java");
writer.newLine();
// 关键变化:writer.newLine();;调用 newLine,实参为 无显式参数;向 writer 写入平台换行符;文本内容追加换行。
writer.write("api");
}
try (var print = new PrintWriter(target)) {
// 输入:try (var print = new PrintWriter(target)) {;资源变量 print 接收 new PrintWriter(target),try 结束时自动关闭。
// 关键变化:try (var print = new PrintWriter(target)) {;创建资源 print,构造表达式为 new PrintWriter(target);try 结束时关闭该资源。
print.println("done");
// 关键变化:print.println("done");;向 print 写入 "done" 并换行;输出流新增一行。
}
System.out.println(target.toString().replace(System.lineSeparator(), "|"));
// 输出:java|api|done|
}
}Charset:显式编码与解码
优先使用 StandardCharsets 常量,避免拼写错误和不必要的受检异常;协议、文件格式和数据库连接都应记录字符集约定。
// 说明:Charset:显式编码与解码。
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class CharsetDemo {
public static void main(String[] args) {
Charset charset = StandardCharsets.UTF_8;
byte[] bytes = "Java".getBytes(charset);
System.out.println(new String(bytes, charset));
// 输出:Java
}
}不常用但需要知道
回看输入:标记位置后复位
是否支持 mark、回看范围多大由具体流决定;不确定时使用 BufferedInputStream 或自己缓存数据,不能假定所有网络流都可 reset。
import java.io.ByteArrayInputStream;
public class MarkResetDemo {
public static void main(String[] args) throws Exception {
try (var input = new ByteArrayInputStream(new byte[]{1, 2, 3})) {
// 输入:try (var input = new ByteArrayInputStream(new byte[]{1, 2, 3})) {;资源变量 input 接收 new ByteArrayInputStream(new byte[]{1, 2, 3}),try 结束时自动关闭。
// 关键变化:try (var input = new ByteArrayInputStream(new byte[]{1, 2, 3})) {;创建资源 input,构造表达式为 new ByteArrayInputStream(new byte[]{1, 2, 3});try 结束时关闭该资源。
System.out.println(input.read());
// 输出:1
input.mark(2);
// 关键变化:input.mark(2);;调用 mark,实参为 2;记录 input 当前 position 作为 mark;后续 reset 可回到该位置。
System.out.println(input.read() + ", " + input.read());
// 输出:2, 3
input.reset();
// 关键变化:input.reset();;调用 reset,实参为 无显式参数;将 input 的 position 恢复到最近 mark。
System.out.println(input.read());
// 输出:2
}
}
}读写基本类型:使用配对二进制端点
Data 流是 Java 定义的二进制格式,不是通用 JSON 或跨语言协议;读写顺序、类型和端序必须由双方固定。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
public class DataStreamDemo {
public static void main(String[] args) throws Exception {
var bytes = new ByteArrayOutputStream();
try (var out = new DataOutputStream(bytes)) {
// 输入:try (var out = new DataOutputStream(bytes)) {;资源变量 out 接收 new DataOutputStream(bytes),try 结束时自动关闭。
// 关键变化:try (var out = new DataOutputStream(bytes)) {;创建资源 out,构造表达式为 new DataOutputStream(bytes);try 结束时关闭该资源。
out.writeInt(20);
// 关键变化:out.writeInt(20);;调用 writeInt,实参为 20;向 out 写入整数 20;数据流追加 4 字节。
out.writeUTF("java");
// 关键变化:out.writeUTF("java");;调用 writeUTF,实参为 "java";向 out 写入 UTF 字符串 "java";数据流追加带长度的文本。
}
try (var in = new DataInputStream(new ByteArrayInputStream(bytes.toByteArray()))) {
// 输入:try (var in = new DataInputStream(new ByteArrayInputStream(bytes.toByteArray()))) {;资源变量 in 接收 new DataInputStream(new ByteArrayInputStream(bytes.toByteArray())),try 结束时自动关闭。
// 关键变化:try (var in = new DataInputStream(new ByteArrayInputStream(bytes.toByteArray()))) {;创建资源 in,构造表达式为 new DataInputStream(new ByteArrayInputStream(bytes.toByteArray()));try 结束时关闭该资源。
System.out.println(in.readInt() + " / " + in.readUTF());
// 输出:20 / java
}
}
}压缩字节流:使用 GZIP 输入输出端点
压缩流改变的是传输格式,不是安全加密;解压不可信输入要限制展开大小,避免压缩炸弹。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class GzipStreamDemo {
public static void main(String[] args) throws Exception {
var compressed = new ByteArrayOutputStream();
try (var out = new GZIPOutputStream(compressed)) {
// 输入:try (var out = new GZIPOutputStream(compressed)) {;资源变量 out 接收 new GZIPOutputStream(compressed),try 结束时自动关闭。
// 关键变化:try (var out = new GZIPOutputStream(compressed)) {;创建资源 out,构造表达式为 new GZIPOutputStream(compressed);try 结束时关闭该资源。
out.write("java".getBytes());
}
try (var in = new GZIPInputStream(new ByteArrayInputStream(compressed.toByteArray()))) {
// 输入:try (var in = new GZIPInputStream(new ByteArrayInputStream(compressed.toByteArray()))) {;资源变量 in 接收 new GZIPInputStream(new ByteArrayInputStream(compressed.toByteArray())),try 结束时自动关闭。
// 关键变化:try (var in = new GZIPInputStream(new ByteArrayInputStream(compressed.toByteArray()))) {;创建资源 in,构造表达式为 new GZIPInputStream(new ByteArrayInputStream(compressed.toByteArray()));try 结束时关闭该资源。
System.out.println(new String(in.readAllBytes()));
// 输出:java
}
}
}SequenceInputStream:串联多个输入流
它适合兼容需要一个 InputStream 的旧 API;大量片段或可组合的文件处理优先考虑 NIO 流和 Channel。
import java.io.ByteArrayInputStream;
import java.io.SequenceInputStream;
public class SequenceStreamDemo {
public static void main(String[] args) throws Exception {
try (var input = new SequenceInputStream(
new ByteArrayInputStream(new byte[]{1}),
new ByteArrayInputStream(new byte[]{2}))) {
// 输入:try (var input = new SequenceInputStream( new ByteArrayInputStream(new byte[]{1}), new ByteArrayInputStream(new byte[]{2}))) {;资源变量 input 接收 new SequenceInputStream( new ByteArrayInputStream(new byte[]{1}), new ByteArrayInputStream(new byte[]{2})),try 结束时自动关闭。
// 关键变化:try (var input = new SequenceInputStream( new ByteArrayInputStream(new byte[]{1}), new ByteArrayInputStream(new byte[]{2}))) {;创建资源 input,构造表达式为 new SequenceInputStream( new ByteArrayInputStream(new byte[]{1}), new ByteArrayInputStream(new byte[]{2}));try 结束时关闭该资源。
System.out.println(input.read() + ", " + input.read());
// 输出:1, 2
}
}
}PushbackInputStream:把字节推回输入
推回缓冲区容量有限,适合词法分析或协议前缀探测;复杂回看不要无限 unread。
import java.io.ByteArrayInputStream;
import java.io.PushbackInputStream;
public class PushbackStreamDemo {
public static void main(String[] args) throws Exception {
try (var input = new PushbackInputStream(new ByteArrayInputStream(new byte[]{'A', 'B'}))) {
// 输入:try (var input = new PushbackInputStream(new ByteArrayInputStream(new byte[]{'A', 'B'}))) {;资源变量 input 接收 new PushbackInputStream(new ByteArrayInputStream(new byte[]{'A', 'B'})),try 结束时自动关闭。
// 关键变化:try (var input = new PushbackInputStream(new ByteArrayInputStream(new byte[]{'A', 'B'}))) {;创建资源 input,构造表达式为 new PushbackInputStream(new ByteArrayInputStream(new byte[]{'A', 'B'}));try 结束时关闭该资源。
int first = input.read();
// 关键变化:int first = input.read();;从 input 读取一个字节,返回值写入 first;读到末尾时返回 -1。
input.unread(first);
// 关键变化:input.unread(first);;调用 unread,实参为 first;把 first 放回 input 的输入前端;下次 read 先返回该值。
System.out.println((char) input.read());
// 输出:A
}
}
}简单案例
ByteArrayInputStream:从内存字节创建输入流
它适合把已有字节数组交给只接受 InputStream 的 API。
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
byte[] data = "OK".getBytes(StandardCharsets.UTF_8);
try (var in = new ByteArrayInputStream(data)) {
// 输入:try (var in = new ByteArrayInputStream(data)) {;资源变量 in 接收 new ByteArrayInputStream(data),try 结束时自动关闭。
// 关键变化:try (var in = new ByteArrayInputStream(data)) {;创建资源 in,构造表达式为 new ByteArrayInputStream(data);try 结束时关闭该资源。
System.out.println(in.read());
// 输出:79
System.out.println(in.available());
// 输出:1
}ByteArrayOutputStream:在内存中累计输出字节
它适合体积可控的数据;toByteArray 返回独立副本。
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
try (var out = new ByteArrayOutputStream()) {
// 输入:try (var out = new ByteArrayOutputStream()) {;资源变量 out 接收 new ByteArrayOutputStream(),try 结束时自动关闭。
// 关键变化:try (var out = new ByteArrayOutputStream()) {;创建资源 out,构造表达式为 new ByteArrayOutputStream();try 结束时关闭该资源。
out.write("OK".getBytes(StandardCharsets.UTF_8));
System.out.println(out.size());
// 输出:2
System.out.println(out.toString(StandardCharsets.UTF_8));
// 输出:OK
}InputStreamReader:按字符集把字节转成字符
构造时显式传入字符集,避免依赖不同机器的平台默认编码。
import java.io.*;
import java.nio.charset.StandardCharsets;
var bytes = new ByteArrayInputStream("中文".getBytes(StandardCharsets.UTF_8));
try (var reader = new InputStreamReader(bytes, StandardCharsets.UTF_8)) {
// 输入:try (var reader = new InputStreamReader(bytes, StandardCharsets.UTF_8)) {;资源变量 reader 接收 new InputStreamReader(bytes, StandardCharsets.UTF_8),try 结束时自动关闭。
// 关键变化:try (var reader = new InputStreamReader(bytes, StandardCharsets.UTF_8)) {;创建资源 reader,构造表达式为 new InputStreamReader(bytes, StandardCharsets.UTF_8);try 结束时关闭该资源。
System.out.println((char) reader.read());
// 输出:首字符“中”
System.out.println(reader.getEncoding());
// 输出:编码为 UTF8
}File.exists:检查路径是否存在
检查与后续操作之间可能发生竞态;能直接执行目标操作时不要把它当权限保证。
import java.io.File;
File current = new File(".");
System.out.println(current.exists());
// 输出:true
System.out.println(current.isDirectory());
// 输出:current.isDirectory() = false
System.out.println(new File(current, "missing.file").exists());
// 输出:missing.file.exists() = falseFileOutputStream:打开文件字节输出
构造后必须可靠关闭;第二个参数为 true 时才是追加写入。
import java.io.FileOutputStream;
import java.nio.file.Files;
var path = Files.createTempFile("note-", ".txt");
try (var out = new FileOutputStream(path.toFile())) { out.write(65); }
System.out.println(Files.readString(path));
// 输出:A
Files.delete(path);
// 关键变化:Files.delete(path);;删除存在的路径 path,返回 void;路径不存在时抛出 NoSuchFileException。BufferedReader.close:关闭缓冲读取链
关闭最外层读取器会向下关闭其包装的底层 Reader,优先交给 try-with-resources。
import java.io.*;
var source = new StringReader("line");
var reader = new BufferedReader(source);
System.out.println(reader.readLine());
// 输出:line
reader.close();
// 关键变化:reader.close();;关闭 reader,释放底层资源;后续读写进入关闭状态。
try { reader.readLine(); }
// 关键变化:try { reader.readLine(); };从 reader 读取下一行并返回文本,EOF 时返回 null;读取位置向前推进。
catch (IOException e) { System.out.println("closed"); }
// 输出:closedFile.transferTo 不存在:文件传输使用 InputStream.transferTo
java.io.File 没有 transferTo;打开输入、输出流后才能复制字节。
// 说明:transferTo 把 in 中的 0x41、0x42 两个字节复制到 out;copied 记录字节数,toString() 用平台默认编码解码为 AB。
import java.io.*;
var in = new ByteArrayInputStream(new byte[] {65, 66});
var out = new ByteArrayOutputStream();
long copied = in.transferTo(out);
// 关键变化:long copied = in.transferTo(out);;把 in 中的 2 个字节传给 out,返回传输字节数 2 并写入 copied。
System.out.println(copied);
// 输出:2
System.out.println(out.toString());
// 输出:ABIOException:构造带原因的 I/O 异常
封装底层失败时保留 cause,让上层仍能追踪根因。
import java.io.IOException;
var cause = new IllegalStateException("disk unavailable");
var error = new IOException("save failed", cause);
System.out.println(error.getMessage());
// 输出:save failed
System.out.println(error.getCause().getMessage());
// 输出:disk unavailablePrintWriter:创建格式化字符输出器
它适合格式化文本,但写入错误默认不抛出,必要时调用 checkError。
import java.io.*;
var buffer = new StringWriter();
try (var writer = new PrintWriter(buffer)) {
// 输入:try (var writer = new PrintWriter(buffer)) {;资源变量 writer 接收 new PrintWriter(buffer),try 结束时自动关闭。
// 关键变化:try (var writer = new PrintWriter(buffer)) {;创建资源 writer,构造表达式为 new PrintWriter(buffer);try 结束时关闭该资源。
writer.printf("id=%d", 7);
}
System.out.println(buffer);
// 输出:id=7
System.out.println(buffer.toString().length());
// 输出:4StringWriter:创建内存字符输出器
它常用于接收只支持 Writer 的 API 输出,不需要底层文件。
import java.io.StringWriter;
var writer = new StringWriter();
writer.write("hello");
writer.append('!');
// 关键变化:writer.append('!');;向 StringWriter 追加字符 '!',返回 writer 自身;文本内容变为 hello!。
System.out.println(writer.getBuffer().length());
// 输出:6
System.out.println(writer.toString());
// 输出:hello!StringWriter.toString:取得累计文本
调用会返回当前缓冲区内容,之后仍可继续向 writer 写入。
import java.io.StringWriter;
var writer = new StringWriter();
writer.write("A");
String first = writer.toString();
writer.write("B");
System.out.println(first);
// 输出:A
System.out.println(writer.toString());
// 输出:ABPaths.get:把文本片段组合为 Path
它不创建真实文件,只按当前文件系统规则构造路径对象。
import java.nio.file.Paths;
var path = Paths.get("logs", "app.log");
// 关键变化:var path = Paths.get("logs", "app.log");;把路径片段 "logs" 和 "app.log" 组合为 Path 并写入 path;路径对象创建完成。
System.out.println(path.getFileName());
// 输出:app.log
System.out.println(path.getNameCount());
// 输出:2
System.out.println(path.isAbsolute());
// 输出:falseZipOutputStream:创建 ZIP 压缩输出流
每个条目要先 putNextEntry,写完后关闭条目与整个流。
import java.io.*;
import java.util.zip.*;
var bytes = new ByteArrayOutputStream();
try (var zip = new ZipOutputStream(bytes)) {
// 输入:try (var zip = new ZipOutputStream(bytes)) {;资源变量 zip 接收 new ZipOutputStream(bytes),try 结束时自动关闭。
// 关键变化:try (var zip = new ZipOutputStream(bytes)) {;创建资源 zip,构造表达式为 new ZipOutputStream(bytes);try 结束时关闭该资源。
zip.putNextEntry(new ZipEntry("a.txt"));
// 关键变化:zip.putNextEntry(new ZipEntry("a.txt"));;调用 putNextEntry,实参为 new ZipEntry("a.txt");向 zip 打开 new ZipEntry("a.txt");后续写入进入该 ZIP 条目。
zip.write(65); zip.closeEntry();
}
System.out.println(bytes.size() > 0);
// 输出:trueimport java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.StringReader;
import java.io.StringWriter;
public class StreamDemo {
public static void main(String[] args) throws Exception {
StringWriter storage = new StringWriter();
try (var writer = new BufferedWriter(storage)) {
// 输入:try (var writer = new BufferedWriter(storage)) {;资源变量 writer 接收 new BufferedWriter(storage),try 结束时自动关闭。
// 关键变化:try (var writer = new BufferedWriter(storage)) {;创建资源 writer,构造表达式为 new BufferedWriter(storage);try 结束时关闭该资源。
writer.write("Java");
writer.newLine();
// 关键变化:writer.newLine();;调用 newLine,实参为 无显式参数;向 writer 写入平台换行符;文本内容追加换行。
writer.write("I/O");
}
try (var reader = new BufferedReader(new StringReader(storage.toString()))) {
// 输入:try (var reader = new BufferedReader(new StringReader(storage.toString()))) {;资源变量 reader 接收 new BufferedReader(new StringReader(storage.toString())),try 结束时自动关闭。
// 关键变化:try (var reader = new BufferedReader(new StringReader(storage.toString()))) {;创建资源 reader,构造表达式为 new BufferedReader(new StringReader(storage.toString()));try 结束时关闭该资源。
System.out.println(reader.readLine());
// 输出:Java
System.out.println(reader.readLine());
// 输出:I/O
}
}
}案例展示了字符、缓冲、行和资源关闭的组合;改成文件时再加 Files.newBufferedReader/newBufferedWriter 和显式 Charset。
易混点
InputStream.read()的-1是 EOF,不能把byte的负值当成结束。- 缓冲流提高访问效率,但不改变字符集,也不自动解决半包或消息边界。
Reader按char读,不等于按 Unicode 码点读;需要码点时使用codePoints()。PrintWriter方便但可能吞异常,文件关键写入不要忽略checkError或底层 IOException。
课后小问
为什么
InputStreamReader必须传入 Charset? 答案:它负责把字节解码成字符,缺少明确编码就会依赖平台默认值。 解析:文件和网络协议应显式约定 UTF-8 等编码,读写两侧必须一致。为什么写完
BufferedWriter还要关闭或 flush? 答案:数据可能仍在内存缓冲中,没有 flush/close 就未必真正写到下层。 解析:try-with-resources 既会 flush 又会关闭资源,并能在异常路径执行清理。
本节小结
- 字节流处理原始数据,字符流处理编码后的文本。
- 缓冲层减少系统调用,不能替代 Charset、协议边界或错误处理。
readLine、transferTo、压缩流和数据流各有明确格式与资源边界。- 所有流都要关闭,写出流还要确认缓冲内容已提交。
快速回顾
- 二进制用 InputStream/OutputStream,文本用 Reader/Writer。
StandardCharsets.UTF_8是明确编码的常用选择。read()返回-1表示 EOF,批量读写优先使用数组或 transferTo。- mark/reset、DataStream、GZIP 和 PrintWriter 都有额外语义与限制。
阿源的知识库