Java的普通IO是面向流的IO,从流中读取数据,将数据写入流中。是一个字节一个字节的读取和写入,而NIO是jdk1.4后推出的面向块的IO流,通过加入缓冲区和管道,以数据块为单位对数据进行IO。
缓冲区Buffer:
本质是一个由数组实现的容器,数据从文件或从其他地方读取首先需要装入到缓冲区中,写入也是先写入到缓冲区,而不是直接从Stream中读取或写入。
管道Channel:
数据传输的通道,从缓冲区里读取数据然后进行传输,从管道中读取数据也是先将数据写入到缓冲区中。管道是双向的,可读可写。
传输过程:
读:文件———Channel———Buffer
写:Buffer———Channel———文件
代码演示:
读:
//读NIO
public static void readNIO() throws Exception {
FileInputStream fileInputStream = new FileInputStream("D:\\test.txt");
//获取管道
FileChannel fileChannel = fileInputStream.getChannel();
//创建字节缓冲区,容量为1024字节
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//从管道里读数据装入缓冲区
fileChannel.read(byteBuffer);
// 重设buffer,将limit设置为读取到的当前位置position,position设置为0,即开始位置。
byteBuffer.flip();
//查看在position和limit之间是否有元素
while (byteBuffer.hasRemaining()){
//获取当前位置的一个字节
byte b = byteBuffer.get();
System.out.println((char)b);
}
fileInputStream.close();
}
写:
public static void writeNIO() throws Exception {
byte message[] = { 'h', 'e', 'l', 'l','o', ' ','w', 'o','r', 'l','d', '!' };
FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.txt");
FileChannel fileChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//重设buffer,将limit设置为capacity,position设置为0,即开始位置。可重新写入数据
byteBuffer.clear();
//写入到缓冲区
for (int i = 0; i < message.length; i++) {
byteBuffer.put(message[i]);
}
byteBuffer.flip();
//从缓冲区写入到管道
fileChannel.write(byteBuffer);
fileOutputStream.close();
}
读写:
//读写NIO
public static void readandwriteNIO() throws Exception {
FileInputStream fileInputStream = new FileInputStream("D:\\test.txt");
//获取管道
FileChannel fileChannel = fileInputStream.getChannel();
//创建字节缓冲区,容量为1024字节
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//从管道里读数据装入缓冲区
fileChannel.read(byteBuffer);
// 获取输出channel
FileOutputStream fileOutputStream = new FileOutputStream("D:\\test2.txt");
FileChannel fileChannel2 = fileOutputStream.getChannel();
//将position和limit重置
byteBuffer.clear();
fileChannel2.write(byteBuffer);
fileInputStream.close();
fileOutputStream.close();
}
读和写过程并没有告诉管道需要读写多少数据,因为buffer中自带计数的东西。
position:数据读取的下一个位置,比如现在已经读到了第三个,那么position就指向第四个。
limit:数据还剩多少没读,或者还有多少空间可以写。
capacity:buffer的总的容量。
position总是小于等于limit。limit总是小于等于capacity。
当然,NIO不止这些,IO是一个线程服务于一个流,而NIO一个线程可以服务于多个流,将多个流注册到一个Selector上,Selector会对这些流进行监听,当某个流有读写事件时,会对其进行处理。一个线程不必停留在一个流上,可以处理其他流,所以说NIO是非阻塞的。
NIO是同步的,因为始终有一个线程服务于这些流,这个线程并没有去做其他工作。真正的异步是操作系统负责把数据拷贝到用户空间,然后通知这个线程,这里的消息通知机制就是异步。而NIO是自己启一个线程去监控stream里面有没有数据。
同步和异步说的是消息的通知机制,阻塞非阻塞说的是线程的状态 。同步异步关注点是你问内核数据有没有准备好,还是内核主动通知你数据有没有准备好。阻塞非阻塞关注点是你的处理线程在数据没有准备好的时候是一直在等待状态还是可以去做其他的事情。
引用别人的同步异步,阻塞非阻塞的生动解释:
1 老张把水壶放到火上,原地不动等水开。(同步阻塞)
---------->老张觉得自己有点傻
2 老张把水壶放到火上,去客厅看毛骗,时不时去看看水开没有。(同步非阻塞)
---------->老张觉得自己有点傻
于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的响声。
3 老张把响水壶放到火上,立等水开。(异步阻塞)
--------->老张觉得自己有点傻
4 老张把响水壶放到火上,去客厅看毛骗,水壶响之前不再去看它,响了再去拿壶。(异步非阻塞)
---------->嗯,老张觉得自己棒棒哒
Selector :是NIO中最关键的一个部分,Selector的作用就是用来轮询每个注册的Channel,一旦发现Channel有注册的事件发生,便获取事件然后进行处理。