NIO

http://tutorials.jenkov.com/java-nio/index.html 原文地址
Java NIO (New IO) is an alternative IO API for Java (from Java 1.4), meaning alternative to the standard Java IO and Java Networking API's. Java NIO offers a different way of working with IO than the standard IO API's

  • Java NIO: Channels and Buffers
    标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。

  • Java NIO: Non-blocking IO ( 非阻塞IO )
    Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。

  • Java NIO: Selectors ( 选择器 )
    Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。

Channel

jdk中的注释 :

A nexus for I/O operations.
A channel represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing.

Java NIO Channels 和Stream类似,但又有些不同:
Channel既可以从通道中读取数据,又可以写数据到通道。但流通常是只读或只写的。
Channel可以异步地读写。
Channel总是读数据到一个Buffer,或者从一个Buffer中写数据到Channel。如下图所示:


ReadAndWrite.png

Channel的实现:

ChannelImple.png

FileChannel : 从文件中读写数据
DatagramChannel : 通过UDP读写网络中的数据
SocketChannel : 能通过TCP读写网络中的数据
ServerSocketChannel : 可以监听指定端口TCP连接,对每一个新进来的连接都创建一个SocketChannel。

Buffer

使用Buffer读写数据一般遵循四个步骤 : 1. Channel.Read数据到Buffer中,或者直接Buffer.put(数据)。 2. 调用 Buffer.flip()切换Buffer到读模式。 3. Buffer.get()读取字节或者Channel.write(buffer)将Buffer中数据写入Channel。4. 调用Buffer.clear()或者Buffer.compact()。clear()方法清空整个缓冲区。compact()方法只会清除已读过的数据,未读数据移到Buffer的起始处。 下面是一个负值文件的example :

        String infile = "nio/src/main/resources/CopyFile.java";
        String outfile = "nio/src/main/resources/CopyFile.java.copy";
        File file = new File(infile);

        FileInputStream fin = new FileInputStream(file);
        FileOutputStream fout = new FileOutputStream(outfile);

        FileChannel finChannel = fin.getChannel();
        FileChannel foutChannel = fout.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        while (true) {
            buffer.clear();

            int read = finChannel.read(buffer);
            if (read == -1) break;

            buffer.flip(); 
            foutChannel.write(buffer);
        }
  • Buffer 的 capacity position limit

capacity : Buffer内存块的上限值,一旦Buffer满了需要清空数据才能继续往Buffer写数据。
position : 写模式时,标识当前写的位置,初始值为0,最大值小于capacity。读模式时,标识当前读位置。写模式切换到读模式,position会被重置为0。
limit : 在写模式下,limit==capacity,读模式下,limit表示能读到的数据最高位置。因此,切换Buffer到读模式时,limit会被设置成写模式下position值。

  • Buffer的类型

    BufferImpl.png

  • Buffer api

      //Buffer内存分配
      ByteBuffer buffer = ByteBuffer.allocate(1024);    //分配一个1024capacity的ByteBuffer
      
      //向Buffer中写入数据
      finChannel.read(buffer);  //read data from channel into buffer
      buffer.put((byte) 2); //put data into buffer

      //flip()
      buffer.flip(); //将Buffer从写模式切换到读模式

      //从Buffer中读取数据
      buffer.get(); //直接从Buffer中get数据
      foutChannel.write(buffer); //将Buffer中数据写入到Channel

      //rewind() 将position设置回0,可以用此重复读Buffer。
      buffer.rewind();

      //clear() 与compact()
      buffer.clear() //position设置回0,limit等于capacity,进入写模式。
      buffer.compact() //将未读数据拷贝到起始处,position设置到没有元素的位置,新写入数据不会覆盖未读数据。

      //mark() reset()
      buffer.mark(); //标记当前position
      buffer.get();  //get()一次 position后移
      buffer.reset(); //reset()position 复位到mark的位置

      //equals() 与 compareTo()
      buffer1.equals(buffer2); //比较Buffer重的剩余未读元素(position到limit之间的元素)相等
      buffer1.compareTo(buffer2);  //比较元素 第一个不相等的元素比较大小 或者 元素全部相等 元素个数多少比较

Scatter/Gather

A scattering read from a channel is a read operation that reads data into more than one buffer.
分散读是指从Channel中读取数据写入到多个Buffer中,下面是示例图和Code :


ScatteringReads.png
        ByteBuffer header = ByteBuffer.allocate(128);
        ByteBuffer body = ByteBuffer.allocate(1024);

        ByteBuffer[] buffers = {header, body};
        channel.read(buffers);

A gathering write to a channel is a write operation that writes data from multiple buffers into a single channel.
聚集写是指从多个Buffer中向一个Channel里写数据,下面是示例图和Code :


GatheringWrites.png
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body };

channel.write(bufferArray);

Channel to Channel Transfers

  • transFrom()

In Java NIO you can transfer data directly from one channel to another, if one of the channels is a FileChannel. The FileChannel class has a transferTo() and a transferFrom() method which does this for you.

        FileChannel finChannel = fin.getChannel();
        FileChannel foutChannel = fout.getChannel();

        long position = 0;
        long size = finChannel.size();

        foutChannel.transferFrom(finChannel,position,size);
  • transTo

The transferTo() method transfer from a FileChannel into some other channel. Here is a simple example:

        FileChannel finChannel = fin.getChannel();
        FileChannel foutChannel = fout.getChannel();

        long position = 0;
        long size = finChannel.size();

        finChannel.transferTo(1,size,foutChannel);

Selector

A Selector is a Java NIO component which can examine one or more NIO Channel's, and determine which channels are ready for e.g. reading or writing. This way a single thread can manage multiple channels, and thus multiple network connections.

  • 为什么使用Selector

仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。但是,需要记住,现代的操作系统和CPU在多任务方面表现的越来越好,所以多线程的开销随着时间的推移,变得越来越小了。实际上,如果一个CPU有多个内核,不使用多任务可能是在浪费CPU能力。

  • Selector api
    SocketChannel channel = SocketChannel.open();
        //创建Selector
        Selector selector = Selector.open();

        //向Selector注册通道
        //Channel必须处于非阻塞模式下才能与Selector使用,这意味着FileChannel不能使用Selector
        channel.configureBlocking(false);

        //将Channel注册到Selector上 并表示是可读的 返回key
        SelectionKey key = channel.register(selector, (SelectionKey.OP_READ | SelectionKey.OP_WRITE));

//        通道四种interest状态 如果不止一种状态可以用 | 传多个
//        SelectionKey.OP_READ;
//        SelectionKey.OP_ACCEPT;
//        SelectionKey.OP_CONNECT;
//        SelectionKey.OP_WRITE;

        //SelectionKey interestSet
        int interestOps = key.interestOps(); //interest集合 返回了之前注册的 OP_READ | OP_WRITE == 5
        int readyOps = key.readyOps(); //ready集合 是已经准备就绪的操作集合 一次Selection后会首先访问这个ready set

        //可通过key获取 channel 和 selector
        SelectableChannel selectableChannel = key.channel();
        Selector selector1 = key.selector();

        //可以给key添加附加对象 标识区分key
        Object attach = key.attach(new Buffer[]{});
        Object attachment = key.attachment();

        //通过Selector选择通道
        //向Selector注册了通道后,可以调用select()方法,返回的int值表示多少通道已经就绪,这是个同步阻塞方法,若无就绪通道会等待直到有就绪通道.
        //selector.wakeup(); 可以唤醒阻塞select()方法 立马返回
        int ready = selector.select();
        //在确认ready channel > 0 后 获取就绪通道注册的keys
        Set selectionKeys = selector.selectedKeys();

        //当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。
        //这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()方法访问这些对象。
        Iterator keyIterator = selectionKeys.iterator();
        while (keyIterator.hasNext()){
            SelectionKey next = keyIterator.next();
            if (next.isAcceptable()){
                SelectableChannel readyAcceptChannel = next.channel(); //获取通道
            }
            keyIterator.remove();
        }

        selector.close();//关闭selector 注册的keys全部失效 channel不会失效

FileChannel

A Java NIO FileChannel is a channel that is connected to a file. Using a file channel you can read data from a file, and write data to a file.

SocketChannel

A Java NIO SocketChannel is a channel that is connected to a TCP network socket.

        //Open a SocketChannel
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost",8080));

        //Reading data from a SocketChannel into Buffer
        ByteBuffer buffer = ByteBuffer.allocate(48);
        socketChannel.read(buffer);

        //Writing data from Buffer into SocketChannel
        buffer.flip();
        while (buffer.hasRemaining()){
            socketChannel.write(buffer);
        }

        //Configure the SocketChannel be non-blocking mode,the method may return before a connection is established.
        //Then call the finishConnect() method to determine whether connection is established.
        socketChannel.configureBlocking(false);
        socketChannel.connect(new InetSocketAddress("localhost",8080));
        if (socketChannel.finishConnect()) {
            //TODO
        }

        //Closing a SocketChannel
        socketChannel.close();

ServerSocketChannel

A Java NIO ServerSocketChannel is a channel that can listen for incoming TCP connections.

        //Opening a ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //Binds the {@code ServerSocket} to a specific address (IP address and port number).
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));

        while (true){
            //Listening for Incoming Connections
            //The accept() method blocks until an incoming connection arrives,and returns a SocketChannel.
            SocketChannel socketChannel = serverSocketChannel.accept();
            //TODO ...

            //In non-blocking mode the accept() method returns immediately,
            // if no incoming connection had arrived,you will have to check if the returned channel is null.
            serverSocketChannel.configureBlocking(false);
            if (socketChannel!=null){
                //TODO ...
            }

            //Closing a ServerSocketChannel
            serverSocketChannel.close();
        }

DatagramChannel

A Java NIO DatagramChannel is a channel that can send and receive UDP packets.

Pipe

A Java NIO Pipe is a one-way data connection between two threads. A Pipe has a source channel and a sink channel. You write data to the sink channel. This data can then be read from the source channel.Here is an illustration of the Pipe principle :


Pipe Internals.png
        //Creating a Pipe.
        Pipe pipe = Pipe.open();

        //Writing to a Pipe.
        Pipe.SinkChannel sinkChannel = pipe.sink();//To write to a pipe you need to access the sink channel.

        ByteBuffer buffer = ByteBuffer.allocate(48);
        buffer.put(new byte[]{1,2,3,4,5});
        buffer.flip();

        while (buffer.hasRemaining()){
            sinkChannel.write(buffer); //Wiriting data from buffer into pipe.sinkChannel
        }

        //Reading from a Pipe
        Pipe.SourceChannel sourceChannel = pipe.source(); //To read from a pipe you need to access the channel.
        ByteBuffer buffer2 = ByteBuffer.allocate(48);
        int read = sourceChannel.read(buffer2);//Reading data from pipe.sourceChannel into buffer.
        buffer.flip();
        buffer2.flip();
        System.out.println(buffer.equals(buffer2)); //true

NIO和IO比较

Differences.png
  • Stream VS Buffer

Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

  • Blocking vs. Non-blocking IO

Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

  • Selectors

ava NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

你可能感兴趣的:(NIO)