初识 Java NIO

一、初识nio

  在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 类, 引入了一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在 Java 堆和 Native 堆中来回复制数据。

  NIO 是一种同步非阻塞的 IO 模型。同步是指线程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,可以同时做其他任务。同步的核心就是 Selector,Selector 代替了线程本身轮询 IO 事件,避免了阻塞同时减少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区,当 IO 事件就绪时,可以通过写道缓冲区,保证 IO 的成功,而无需线程阻塞式地等待。

二、NIO中的重要概念

  1、缓冲区(buffer)

  NIO是基于缓冲区的IO方式。当一个链接建立完成后,IO的数据未必会马上到达,为了使数据到达时能够正确完成IO操作,在BIO(阻塞IO)中,等待IO的线程必须被阻塞,以全天候地执行IO操作。为了解决这种IO方式低效的问题,引入了缓冲区的概念,当数据到达时,可以预先被写入缓冲区,再由缓冲区交给线程,因此线程无需阻塞地等待。

  常用冲区类型

   ByteBuffer、MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer  

  缓冲区常用方法:

   allocate() - 分配一块缓冲区  

   put() -  向缓冲区写数据

   get() - 向缓冲区读数据  

   filp() - 将缓冲区从写模式切换到读模式  

   clear() - 从读模式切换到写模式,不会清空数据,但后续写数据会覆盖原来的数据,即使有部分数据没有读,也会被遗忘;  

   compact() - 从读数据切换到写模式,数据不会被清空,会将所有未读的数据copy到缓冲区头部,后续写数据不会覆盖,而是在这些数据之后写数据

   mark() - 对position做出标记,配合reset使用

   reset() - 将position置为标记值    

  缓冲区的一些属性:

   capacity - 缓冲区大小,无论是读模式还是写模式,此属性值不会变;

   position - 写数据时,position表示当前写的位置,每写一个数据,会向下移动一个数据单元,初始为0;最大为capacity - 1;切换到读模式时,position会被置为0,表示当前读的位置

   limit - 写模式下,limit 相当于capacity 表示最多可以写多少数据,切换到读模式时,limit 等于原先的position,表示最多可以读多少数据。

  2、通道

  通道是 I/O 传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。对于离开缓冲区的传输,您想传递出去的数据被置于一个缓冲区,被传送到通道。对于传回缓冲区的传输,一个通道将数据放置在您所提供的缓冲区中。

  例如:有一个服务器通道 ServerSocketChannel serverChannel,一个客户端通道 SocketChannel clientChannel;服务器缓冲区:serverBuffer,客户端缓冲区:clientBuffer。当服务器想向客户端发送数据时,需要调用:clientChannel.write(serverBuffer)。当客户端要读时,调用 clientChannel.read(clientBuffer);当客户端想向服务器发送数据时,需要调用:serverChannel.write(clientBuffer)。当服务器要读时,调用 serverChannel.read(serverBuffer)。

  常用通道类型

   FileChannel:从文件中读写数据。  

   DatagramChannel:能通过UDP读写网络中的数据。  

   SocketChannel:能通过TCP读写网络中的数据。  

   ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

  3、选择器(selector)

  通道和缓冲区的机制,使得线程无需阻塞地等待IO事件的就绪,但是总是要有人来监管这些IO事件。这个工作就交给了selector来完成,这就是所谓的同步。要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪,这就是所说的轮询。一旦这个方法返回,线程就可以处理这些事件。

  通道向选择器注册时,需要指定感兴趣的事件,选择器支持以下事件:

   SelectionKey.OP_CONNECT

   SelectionKey.OP_ACCEPT

   SelectionKey.OP_READ

   SelectionKey.OP_WRITE

三、小实例

  使用nio简单实现了文件的复制。

public class Test {
    
    public static void main(String[] args) {
        FileInputStream fin = null;
        FileOutputStream fout = null;
        FileChannel fic = null;
        FileChannel foc = null;
        try {
            fin = new FileInputStream("F:\\1.txt");
            fout = new FileOutputStream("F:\\2.txt");
            
            //从FileInputStream创建用于输入的FileChannel
            fic = fin.getChannel();
            
            //从FileOutputStream创建用于输出的FileChannel
            foc = fout.getChannel();
            
            //建立buffer缓冲区,2的8次方
            ByteBuffer buf = ByteBuffer.allocate(1024<<8);
            
            //根据read返回实际独处的字节数,终止循环
            //缓冲区从fic读取数据
            while(fic.read(buf)>0) {
                // 缓冲区翻转用于输出数据到focus
                buf.flip();
                foc.write(buf);
                //清空缓冲区用于下次读取
                buf.clear();
            }
            
            //安全释放资源
            if(fic != null)
                fic.close();
            if(foc != null)
                foc.close();
            if(fin != null)
                fin.close();
            if(fout != null)
                fout.close();
            
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            
        }
    }

}

PS:因本人能力有限,如有误还请谅解;

猜你喜欢

转载自www.cnblogs.com/WHL5/p/9418422.html