NIO学习笔记 (1) – 入门
1. I/O 和 NIO的主要区别
IO | NIO |
---|---|
面向流 | 面向缓冲 |
阻塞IO | 非阻塞IO |
无 | 选择器 |
2. 面向流与面向缓冲
Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。java IO面向流就意味每次都是从流中读取多个字节(字符最后也会转换为字节)。NIO中的Buffer
是一个对象, 它包含一些要写入或者刚读出的数据。 在 NIO 中加入 Buffer
对象,体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中,您将数据直接写入或者将数据直接读到 Stream
对象中。
在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。
缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
在NIO中总共有7中缓存类型:
ByteBuffer
, CharBuffer
, ShortBuffer
, IntBuffer
, LongBuffer
, FloatBuffer
, DoubleBuffer
一个简单的IntBuffer读写例子:
public class NioTest {
public static void main(String[] args) {
// 定义一个容量为10 的Int类型的Buffer
IntBuffer intBuffer = IntBuffer.allocate(10);
// 随机像该缓存中写入20个随机数
for(int i=0;i<intBuffer.capacity();i++){
intBuffer.put(new SecureRandom().nextInt(20));
}
// 读写切换
intBuffer.flip();
// 读出缓存中数据
while (intBuffer.hasRemaining()){
System.out.println(intBuffer.get());
}
}
}
从上诉例子中能看到 intBuffer.flip();
这里是调用读写切换的意思。这个缓存是可读可写的,不像传统IO中只能进行读或者写。
而flip()
底层干了上啥?
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
在Buffer中定义了这么几个字段:
capacity
: 表示这个缓存的容器,该容器是不会变的,也就是**IntBuffer.allocate(10);**中的定义的 10;
limit
: 缓冲区还有多少数据能够取出或者缓冲区还有多少容量用于存放数据;
position
:相当于一个游标(cursor),记录我们从哪里开始写数据,从哪里开始读数据。
**三者关系:**capacity代表了Buffer的容量是不变的,limit与position的差总是表示Buffer总可以读的数据,或者Buffer中可以写数据的容量。还有position总是小于等于limit,limit总是小于等于capacity。
当buffer是初始状态的时候,capacity为10,limit为10, position为0。
当buffer被写入一个值时position会向下移动到下一个可插入数据的Buffer单元,也就是position +1,position最大可为capacity – 1(因为position的初始值为0). limit 不变,还是为10.
当数据写入完之后调用intBuffer.flip();
之后capacity为10,limit为10, position为0,此时读和上面写的原理一样。
但是有种情况就是在buffer中只写入了5个字节,那么写入完毕之后转换为读模式时 capacity为10,limit为5, position为0,这个样在下次转换为写模式是 limit 就只能为5,将会越来越小,NIO提供了一个初始数据的方法:intBuffer.clear()
,初始化buffer中的数据。
3. 阻塞与非阻塞IO
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
Channel
是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流。
正如前面提到的,所有数据都通过 Buffer
对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
4. 简单NIO例子
NIO 的大概流程是:首先将文件中的内容写入到Buffer 中 -> 通过管道将Buffer中的内容读入到其他地方
public class NioTest2 {
public static void main(String[] args) throws IOException {
// 读取文件
FileInputStream fileInputStream = new FileInputStream("NioTest2.txt");
// 获取管道
FileChannel fileChannel = fileInputStream.getChannel();
// 创建缓存
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
// 将信息写入管道
fileChannel.read(byteBuffer);
// 转换读写
byteBuffer.flip();
// 将换缓存中的数据读出
while (byteBuffer.remaining()>0){
byte b = byteBuffer.get();
System.out.println((char)b);
}
fileInputStream.close();
}
}
参考: