Java NIO

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
        }

你可能感兴趣的:(Java NIO)