I/O(Input/Output,输入输出)把程序连接到文件、网络和其他进程。它最容易出错的地方不是 API 名称,而是字符编码、 资源关闭、超时和“数据太大不能一次放进内存”。
Table of contents
Open Table of contents
先做一个流式文件复制器
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.*;
public class 文件复制器 {
static void 复制(Path source, Path target) throws IOException {
try (var input = FileChannel.open(source, StandardOpenOption.READ);
var output = FileChannel.open(target, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
var buffer = ByteBuffer.allocateDirect(16 * 1024);
while (input.read(buffer) != -1) {
buffer.flip();
while (buffer.hasRemaining()) output.write(buffer);
buffer.clear();
}
}
}
}
**通道(Channel)**是数据读写连接,**缓冲区(Buffer)**是数据暂存区。读取后调用 flip(),把缓冲区从写入模式切换
为读取模式;写完后调用 clear(),准备下一轮。write() 不保证一次写完,所以要检查 hasRemaining()。
普通业务代码优先使用 Files.copy、Files.newBufferedReader 等高层 API。只有需要理解 I/O 边界、定制协议或测得高层
API 不满足需求时,才直接操作通道和缓冲区。
字节不等于字符
文件和网络传输的是字节;文本需要按**字符编码(Charset)**在字符与字节之间转换。显式使用 UTF-8:
var text = java.nio.file.Files.readString(path, java.nio.charset.StandardCharsets.UTF_8);
不要依赖操作系统默认编码。Java 18 起标准 API 默认字符集统一为 UTF-8,但显式编码仍能让协议和文件契约一目了然, 也便于维护旧环境。
给网络请求设置边界
var client = java.net.http.HttpClient.newBuilder()
.connectTimeout(java.time.Duration.ofSeconds(2)).build();
var request = java.net.http.HttpRequest.newBuilder(uri)
.timeout(java.time.Duration.ofSeconds(3)).GET().build();
var response = client.send(request,
java.net.http.HttpResponse.BodyHandlers.ofString(java.nio.charset.StandardCharsets.UTF_8));
连接超时限制建立连接的等待,请求超时限制整个请求。两者都要配置。响应体大小不受这段代码限制;面对不可信服务时, 应使用流式处理并限制字节数。
阻塞与非阻塞
阻塞 I/O会让当前线程等待操作完成;非阻塞 I/O允许线程在暂时无数据时处理其他任务。非阻塞不等于自动更快, 它用状态机复杂度换取更少的等待线程。普通文件工具和低并发内部调用先用阻塞 API;连接数量很大且等待占主导时,再评估 异步或事件驱动模型。
下一步
阅读注解、反射与动态代理,理解框架如何在运行时装配行为。