Java IO介绍
Java IO主要是指的是java.io
和java.nio
两个包下提供的对IO操作的功能(由于网络通信也是一种IO,所以也会是Java IO的范畴之中),主要包括:
- 通过数据流、序列化以及文件系统的输入和输出
- 提供字符集、解码器、编码器,用于在字节与Unicode字符之间进行转换
- 提供访问文件、文件属性、文件系统的能力
- 提供异步IO、多路复用IO(multiplexed)、和非阻塞IO(non-blocking I/O)的API用于构建弹性伸缩的服务
IO模型分类
IO模型是通用的,一般主要分类如下
同步
同步IO模型主要分阻塞IO(blocking IO)和非阻塞IO(non-blocking IO),如下:
▪ 同步阻塞IO
即同步阻塞IO,线程会被操作系统挂起,如java.io
基于流模型的IO,也就是常说的BIO
▪ 非阻塞IO
即同步非阻塞IO,线程不被操作系统挂起,如java.nio
包下基于多路复用,同步非阻塞的IO实现,也就是常说的NIO
异步IO
异步IO模型,调用者发起一个请求,然后立即做其他的事情,通过信号通知或者回调来告知调用者,如java.nio
下关于Asynchronous
的相关实现
Java IO Stream
image.png主要分针对二进制操作的字节流和针对读取文本信息的字符流两大块
我们知道,Java IO整个模型操作都是基于装饰者模式,也就是说其实所有的IO实现都是基于(InputStream/OutputStream),不用的实现用于解决不同的问题,例如:
- 一些
Buffered
开头的是一些带缓冲区的IO实现,目的是减少磁盘的读写次数 -
File
相关的针对文件读写的操作的实现
Sample
- 读取一个文件文本内容
try (InputStream in = IODemo.class.getClassLoader().getResourceAsStream("file/demo.json")) {
StringBuilder content = new StringBuilder();
byte[] buffer = new byte[1024];
int i;
while ((i = in.read(buffer)) > 0) {
content.append(new String(buffer, 0, i, StandardCharsets.UTF_8));
}
System.out.println(content.toString());
} catch (IOException e) {
e.printStackTrace();
}
- 拷贝一个文件
try (InputStream in = IODemo.class.getClassLoader().getResourceAsStream("file/demo.json");
FileOutputStream out = new FileOutputStream("/Users/sevenlin/demo.json")) {
byte[] buffer = new byte[1024];
int i;
while ((i=in.read(buffer))>0){
out.write(buffer,0,i);
}
} catch (IOException e) {
e.printStackTrace();
}
Java NIO
Java NIO 是一种同步非阻塞IO的实现,并且底层通过操作系统的多路复用IO机制实现,目的是为了提高IO性能,不同操作系统有不同的实现,如Linux通过epoll,而windows则通过IOCP,mac通过poll
主要概念如下:
- Buffer,一个用于特定基本数据类型的容器,该缓冲区实现支持通过
capacity
控制容量,limit(限制)
限制可用区,position(位置)
控制读写的位置,这几个之间的关系:0 <= mark <= position <= limit <= capacity
,三个关键方法-
clear()
,清楚缓冲区,以便再次读取新数据,它将limit
设置为capacity
大小,将position
设置为 0 -
flip()
,反转缓冲区, 使缓冲区为一系列新的通道写入或相对获取 操作做好准备:它将limit
设置为当前位置,然后将position
设置为 0。 -
rewind()
,重置缓冲区,使缓冲区为重新读取已包含的数据做好准备:它使limit
保持不变,将position
设置为 0。
-
- Channel,用于IO操作的连接,可以理解为类似Linux系统的文件描述符,通道表示到实体,如硬件设备、文件、网络套接字或可以执行一个或多个不同 I/O 操作(如读取或写入)的程序组件的开放的连接。
- Selector,对
SelectableChannel
管道的多路复用选择器,这个实现多路复用的关键所在,内部维护三个建集:keys
,selectedKeys
,cancelledkeys
- SelectionKey,
SelectableChannel
在Selector
中注册的注册标记,由Selector
管理- OP_READ,用于读取操作
- OP_WRITE,用于写入操作
- OP_CONNECT,用于Socket的连接操作
- OP_ACCEPT,用于Socket的接收操作
Sample
- 读取一个文件文本内容
String path = NioDemo.class.getClassLoader().getResource("file/demo.json").getPath();
try (FileChannel channel = new FileInputStream(path).getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
StringBuilder content = new StringBuilder();
while (channel.read(buffer) > 0) {
buffer.flip();
content.append(new String(buffer.array(), 0, buffer.limit(), StandardCharsets.UTF_8));
buffer.clear();
}
System.out.println(content.toString());
} catch (IOException e) {
e.printStackTrace();
}
- 拷贝一个文件
String path = NioDemo.class.getClassLoader().getResource("file/demo.json").getPath();
try (FileChannel in = new FileInputStream(path).getChannel();
FileChannel out = new FileOutputStream("/Users/sevenlin/demo.json").getChannel()) {
for (long limit = in.size();limit>0;){
long transferred = in.transferTo(in.position(), in.size(), out);
limit -= transferred;
}
} catch (IOException e) {
e.printStackTrace();
}
- 一个比较完整的例子
public class NioServer {
public static void main(String[] args) throws Exception {
// 声明ServerSocket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8088));
serverSocketChannel.configureBlocking(false);
// 注册Select
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 同步等待
selector.select();
selector.selectedKeys().forEach(key -> {
try (SocketChannel c = ((ServerSocketChannel)key.channel()).accept()) {
c.write(Charset.defaultCharset().encode("Hello World"));
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
public static class Client {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try (Socket socket = new Socket(InetAddress.getLocalHost(), 8088)) {
InputStream in = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
bufferedReader.lines().forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
}
Java NIO2(AIO)
异步IO模型主要通过两种方式实现读写操作结果的处理
- Future
URL resource = AioDemo.class.getClassLoader().getResource("file/demo.json");
Path path = Paths.get(resource.toURI());
ByteBuffer buffer = ByteBuffer.allocate(1024);
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);
Future<Integer> future = channel.read(buffer, 0);
future.get();//wait
if(future.isDone()){
buffer.flip();
System.out.println(new String(buffer.array(),buffer.position(),buffer.limit()));
buffer.clear();
}
- CompletionHandler(回调)
URL resource = AioDemo.class.getClassLoader().getResource("file/demo.json");
Path path = Paths.get(resource.toURI());
ByteBuffer buffer = ByteBuffer.allocate(1024);
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);
channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
buffer.flip();
System.out.println(new String(buffer.array(),buffer.position(),buffer.limit()));
buffer.clear();
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
//wait for done
Thread.currentThread().join();
对比
- BIO(blocking IO)是最常见的,编程也相对比较简单,
read
、write
操作都是同步阻塞的,此时当前线程只能等待做不了其他事情,想要提高IO并发,通常做法就是通过线程池管理线程,开启多个线程并处处理多个输入输出。 - NIO(non-blocking IO)引入了Buffer能够方便我们对已经读取的内容进行遍历/访问,通过Selector的多路复用选择,在并发常见下能够有效减少线程的开销,通过File的
transfer
操作可以避免拷贝文件的时候操作系统用户态与内核态之间切换,提高性能 - NIO2(AIO,asynchronous IO),通过Future和回调可以改变传统的IO读取编程方式
网友评论