Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。Non-Blocking应该是最好的理解
随着 JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为 NIO.2。因为 NIO 提供的一些功能,NIO已经成为文件处理中越来越重要的部分。
NIO和传统IO的区别
IO: 面向流 单向的 面向缓冲区:通道可以是单双向的
阻塞IO
NIO:面向缓冲区(Buffer Oriented):通道可以是单向的,也可以是双向的
非阻塞IO(Non Blocking IO)
选择器(Selectors)
NIO的核心
NIO的核心在于:通道和缓冲区(Buffer),通道表示IO源daoIO设备(例如:文件,套接字)的连接,若需要时使用NIO需要获取IO设备中的通道以及用于容纳的数据缓冲区,对数据进行处理
缓冲区底层就是数组
简而言之:Channel 负责传输,Buffer负责存储
名词解释
NIO 是一种同步非阻塞的 IO 模型。同步是指线程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,可以同时做其他任务。同步的核心就是 Selector,Selector 代替了线程本身轮询 IO 事件,避免了阻塞同时减少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区,当 IO 事件就绪时,可以通过写道缓冲区,保证 IO 的成功,而无需线程阻塞式地等待。
Buffer:
为什么说NIO是基于缓冲区的IO方式呢?因为,当一个链接建立完成后,IO的数据未必会马上到达,为了当数据到达时能够正确完成IO操作,在BIO(阻塞IO)中,等待IO的线程必须被阻塞,以全天候地执行IO操作。为了解决这种IO方式低效的问题,引入了缓冲区的概念,当数据到达时,可以预先被写入缓冲区,再由缓冲区交给线程,因此线程无需阻塞地等待IO。
通道:
当执行:SocketChannel.write(Buffer),便将一个 buffer 写到了一个通道中。如果说缓冲区还好理解,通道相对来说就更加抽象。网上博客难免有写不严谨的地方,容易使初学者感到难以理解。
引用 Java NIO 中权威的说法:通道是 I/O 传输发生时通过的入口,而缓冲区是这些数 据传输的来源或目标。对于离开缓冲区的传输,您想传递出去的数据被置于一个缓冲区,被传送到通道。对于传回缓冲区的传输,一个通道将数据放置在您所提供的缓冲区中。
例如 有一个服务器通道 ServerSocketChannel serverChannel,一个客户端通道 SocketChannel clientChannel;服务器缓冲区:serverBuffer,客户端缓冲区:clientBuffer。
当服务器想向客户端发送数据时,需要调用:clientChannel.write(serverBuffer)。当客户端要读时,调用 clientChannel.read(clientBuffer)
当客户端想向服务器发送数据时,需要调用:serverChannel.write(clientBuffer)。当服务器要读时,调用 serverChannel.read(serverBuffer)
这样,通道和缓冲区的关系似乎更好理解了。在实践中,未必会出现这种双向连接的蠢事(然而这确实存在的,后面的内容还会涉及),但是可以理解为在NIO中:如果想将Data发到目标端,则需要将存储该Data的Buffer,写入到目标端的Channel中,然后再从Channel中读取数据到目标端的Buffer中。
Selector:
通道和缓冲区的机制,使得线程无需阻塞地等待IO事件的就绪,但是总是要有人来监管这些IO事件。这个工作就交给了selector来完成,这就是所谓的同步。
Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。
要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪,这就是所说的轮询。一旦这个方法返回,线程就可以处理这些事件。
Buffer常用的属性
容量 (capacity) :表示 Buffer 最大数据容量,一旦声明后,不能更改。
通过Buffer中的capacity()获取。缓冲区capacity不能为负。
限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit后的数据不可读写。
通过Buffer中的limit()获取。缓冲区的limit不能为负,并且不能大于其capacity。
位置 (position):当前要读取或写入数据的索引。
通过Buffer中的position()获取。缓冲区的position不能为负,并且不能大于其limit。
标记 (mark):标记是一个索引,通过 Buffer 中的 mark() 方法将mark标记为当前position位置。
之后可以通过调用 reset() 方法将 position恢复到标记的mark处。
标记、位置、限制、容量遵守以下不变式:
0 <= mark <= position <= limit <= capacity
import org.junit.Test;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
/**
* 一.Nio中传输中的两个重要的概念:
* Buffer:缓冲区 负责数据的存储(读写)
* channel :通道 代表了数据源与IO节点 (文件,网络socket) 之间的链接,负责传输Buffer
*
* 二。 java.nio.Buffer
* |--------ByteBuffer
* |--------CharBuffer
*
* |--------IntBuffer
* |--------DoubleBuffer
* |--------ShortBuffer
* |--------LongBuffer
* |--------DoubleBuffer
底层对应类型的数组
xxxBuffer的常用属性
capacity :容量
limit : 限制 默认的时候与capation 的值一样
position : 位置
mark : 标记
*/
class BufferTest {
@Test
public void test1(){
ByteBuffer byteBuffer = ByteBuffer.allocate(10);//底层为10 的byte[]数组
System.out.println(byteBuffer.capacity());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.position());
// System.out.println(byteBuffer.mark());
byteBuffer.put("ddd".getBytes());
System.out.println("***************");
System.out.println(byteBuffer.capacity());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.position());//每一次put都会将值移动一次
System.out.println("**********flip()********");
byteBuffer.flip();//将存入数据模式变成取出数据模式 已经存入的数据posstion又变成0,从头继续读
System.out.println(byteBuffer.capacity());
System.out.println(byteBuffer.limit());
System.out.println("----"+byteBuffer.position());
System.out.println("**********get()********");
System.out.println((char)byteBuffer.get()); //每get一次posstion+1
System.out.println((char)byteBuffer.get());
System.out.println(byteBuffer.capacity());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.position());
byteBuffer.rewind();//重置position
byteBuffer.clear();//清空 回到用户最初的状态 10,10,0
System.out.println((char)byteBuffer.get());
}
}
非直接缓存区代码实例
public void test1() throws Exception {
//1.提供相应的输入输出流
FileInputStream fis = new FileInputStream("D:\\nexus.war");
FileOutputStream fos = new FileOutputStream("D:\\nexus.war2.tar");
//创建相应的Channel 通过我们的流去创建对应的通道,然后通过通道继续读写数据
FileChannel inchannel = fis.getChannel();
FileChannel outchannel = fos.getChannel();
//3.提供缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while ((inchannel.read(byteBuffer)) != -1) {
byteBuffer.flip();//切换为读数据的模式
//如果这里不切换的话,缓存区会满
outchannel.write(byteBuffer);
byteBuffer.clear();//清空,读完当前的缓存区然后再继续
}
fis.close();
fos.close();
inchannel.close();
outchannel.close();
}
直接缓存区代码实例:
public void test2() throws Exception {
long start = System.currentTimeMillis();
//1.创建Channel
// FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
// FileChannel outChannel = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
FileChannel inChannel = FileChannel.open(Paths.get("D:\\nexus.war"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("D:\\nexus2.war"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//2.创建得到直接缓冲区
MappedByteBuffer inMappedBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
//3.数据的读写
byte[] dst = new byte[inMappedBuffer.limit()];
inMappedBuffer.get(dst);//将数据写入到dst中
outMappedBuffer.put(dst);//从dst中将数据取出
//4.关闭资源
inChannel.close();
outChannel.close();
long end = System.currentTimeMillis();
System.out.println("直接缓冲区:" + (end - start));//1573-1575
}