Java核心-基础IO
【Java核心-基础】IO
.
Java 的 IO 方式大致可分为 3 类:BIO、NIO、NIO 2(AIO)
这 3 类 IO 方式在 同步/异步 和 阻塞/非阻塞 方面有明显区别。
同步 vs 异步
同步:后续任务必须等当前调用返回后再执行。
异步:后续任务不需要等待当前调用返回,依靠事件、回调等机制实现任务间次关系。
阻塞 vs 非阻塞
阻塞:执行阻塞操作时,当前线程会处于阻塞状态,无法执行其它任务,只有条件就绪后才能继续。
非阻塞:不管操作是否结束,当前线程直接返回,由后台其它线程继续处理。
BIO 指传统的 java.io 包,也包括 java.net 下的部分API(如,Socket、ServerSocket、HttpURLConnection)。
BIO 基于流模型,以同步、阻塞的方式交互(B:Blocking)。
优点:简单、直观。
缺点:IO 效率 和 扩展性 存在局限性,容易成为性能瓶颈。
InputStream / OutputStream 用于读写 字节
Reader / Writer 增加了编解码功能,用于读写 字符
BufferedInputStream / BufferedOutputStream 带缓存区,对批量数据进行一次操作,可以减少对磁盘等硬件的读写频率,提高IO效率
写数据后需要 flush
Closeable 接口提供的 close() 方法可以释放相关资源
如,释放 FileInputStream 所获取的文件描述符(FileDescriptor)
可以利用 try-with-resources 和 try-finally 机制来确保调用了 close()方法
Java 1.4 引入了 NIO
提供了 Channel、Selector、Buffer 等机制,可以构建多路复用、同步非阻塞的IO程序(N:New、Non-Blocking)。
它提供了更接近操作系统底层的高性能数据操作方式。
JDK 的这部分功能在不同操作系统上实现有较大不同,应在真实的运行环境上测试。如,开发环境是 Windows,部署环境是 Linux,则应以 Linux 环境的调试结果为准。
Java 1.7 引入了 NIO 2(AIO)
提供了异步非阻塞的IO方式(A:Asynchronous)。
缺点:单个请求中数据量较大时,对后续事件的响应会被延迟。所以多路复用适用于大量请求大小有限的场景。
Buffer 是高效的数据容器
除 boolean 外,所有原始数据类型都有相应的 Buffer 实现。
Channel 用于支持 批量 式 IO
类似 Linux 上的文件描述符,比 File、Socket 更接近操作系统底层。
Channel 可以充分利用操作系统底层机制,获得特定场景的性能优化。如,DMA(Direct Memory Access)。
Selector 用于支持 多路复用
它可以监测到注册在 Selector 上的多个 Channel 中,是否有 Channel 处于就绪状态,实现单线程对多 Channel 的高效管理。
针对不同操作系统,Selector 的实现了不同;Linux 上依赖于 Epoll;Windows 上依赖于 iocp
此处以 Client-Server Socket 通信为例来说明 NIO 的应用差异。
用 BIO 实现
针对每个来自 Client 的请求,都创建一个对应线程执行,或交由线程池处理。
缺点:扩展性差。大量客户端请求时,产生大量连接,服务端对每个连接的处理需要线程上下文切换,开销过高。
Java代码
ServerSocket serverSocket = new ServerSocket(80);
while (true) {
Socket socket = serverSocket.accept();
Thread requestHandler = new Thread(()->{
// 处理来自客户端的请求(socket)
});
requestHandler.start();
}
用 NIO 实现
每一个来自 Client 的请求都被汇聚到 Selector,用单线程轮询定位就绪的 Channel,再处理相应的请求。
Selector 类似于一个 调度员
同步:每个准备好的 channel 处理是依次进行的;
非阻塞:不需要每个连接(channel)都有一个对应的线程在等它就绪 。
只有 select 阶段是阻塞的,可以避免大量客户端连接导致线程频繁切换的问题。
示例代码只注册了一个 Channel。
真实应用中一般会创建 多个 Channel,注册到同一个 Selector,也就是 多路复用。
为了避免单个 Selector (单线程)执行监听任务成为瓶颈,也可以尝试用 多个 Selector。
Java代码
try (Selector selector = Selector.open();
ServerSocketChannel socketChannel = ServerSocketChannel.open()) {
socketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), 80));
socketChannel.configureBlocking(false);
// 注册到 Selector,并说明关注点(accept)
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(); // 等待就绪的 Channel(这是阻塞操作)
Set selectionKeys = selector.selectedKeys();
Iterator iter = selectionKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
// 处理请求
ServerSocketChannel channel = (ServerSocketChannel)key.channel();
...
iter.remove();
}
}
} catch (IOException e) {
// 处理异常
}
用 AIO 实现
这只是个简单的示例,真实应用中会更复杂
Java代码
try {
AsynchronousServerSocketChannel asyncChannel
= AsynchronousServerSocketChannel.open();
asyncChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), 80));
asyncChannel.accept(null,
new CompletionHandler() {
@Override
public void completed(AsynchronousSocketChannel result,
Void attachment) {
// 接收下一次连接
asyncChannel.accept(null, this);
// 处理本次连接
...
}
@Override
public void failed(Throwable exc, Void attachement) {
// 处理连接失败的情况
}
})
} catch (IOException e) {
// 处理异常
}
Java核心-基础IO相关教程