java nio

nio

Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式

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

Channel

  • Channel

    • FileChannel
    • DatagramChannel
    • SocketChannel
    • ServerSocketChannel

         FileInputStream fis=new FileInputStream("123");
         FileChannel channel=fis.getChannel();
      

      Channel是与Buffer可以进行双向的读写操作的结构,关于如何读写可以看下面的Buffer

Buffer

  • Buffer
    • ByteBuffer
    • CharBuffer
    • DoubleBuffer
    • FloatBuffer
    • IntBuffer
    • LongBuffer
    • ShortBuffer
    • MappedByteBuffer

缓冲区可以进行读写操作,最大大小的开辟是确定的

     IntBuffer ib=IntBuffer.allocate(20);

Buffer里面的三个重要属性

  • capacity 表示buffer的容量,是定值
  • position 表示当前的读写指针
  • limit 表示当前读写的最大的位置,就是说position不能超过limit

不论读还是写,都是从postion位置开始操作,limit相当于一个postion的阈值,Buffer的主要函数都是通过修改position和limit的值来实现功能的

  1. flip()
    从写状态变为读状态,limit=position,position=0
  2. rewind()
    将position变成0,主要是用于你读数据的时候,可以回过头重新读一遍
  3. clear()
    完全清空,position=0,limit=max,变为写状态,但是实际上原来的数据没有抹掉
  4. compact()
    方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。
  5. mark()与reset()方法
    通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position,用来标记一个position位置的作用
  6. equals()与compareTo()方法
    比较的是从 position到limit之间的元素,他们之间的元素才是程序承认的有效元素
  7. hasremaining 还有没有有效数据

读写方法

  1. 从Channel写到Buffer
    int bytesRead = inChannel.read(buf); //从channel读到buffer
  2. 通过put方法写Buffer的例子:
    buf.put(127);// 这就比较简单了,直接put,有很多的复写的方法,大体类似

  3. 从Buffer读取数据到Channel的
    int bytesWritten = inChannel.write(buf);

  4. 使用get()方法从Buffer中读取数据
    byte aByte = buf.get();

Scatter/Gather

Scatter/Gather是针对于Channel的一个特性,可以把Channel数据分发给多个Buffer,或者把多个Buffer合并到一个Channel
他的价值在于,可以把固定的包头给单独的拿出来放到某一个Buffer中处理,注意在分发时,只有第一个BUffer填满了,才会去填第二个Buffer,特别要主要position和limit的值的变化

    //Scatter
    ByteBuffer header = ByteBuffer.allocate(128);
    ByteBuffer body   = ByteBuffer.allocate(1024);
    ByteBuffer[] bufferArray = { header, body };
    channel.read(bufferArray);

    //Gather
    ByteBuffer header = ByteBuffer.allocate(128);
    ByteBuffer body   = ByteBuffer.allocate(1024);
    //write data into buffers
    ByteBuffer[] bufferArray = { header, body };
    channel.write(bufferArray);

通道之间的数据传输

FileChannel有两个方法transferTo/transferFrom,来完成通道之间数据的传递,参数可以是任意通道。

    long position = 0;
    long count = fromChannel.size();//最大传输的数据量,实际上可能没有这么大,有多少传多少
    fromChannel.transferTo(position, count, toChannel);

    long position = 0;
    long count = fromChannel.size();////最大传输的数据量,实际上可能没有这么大,有多少接收多少
    toChannel.transferFrom(position, count, fromChannel);

FileChannel

可以使用FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。如:
channel.truncate(1024);

FileChannel实例的size()方法将返回该实例所关联文件的大小。如:
long fileSize = channel.size();

FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。 force()方法有一个boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。 下面的例子同时将文件数据和元数据强制写到磁盘上:
channel.force(true);

Pipe

Pipe是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取

    Pipe p=Pipe.open();//打开Pipe
    //Pipe入口
    Pipe.SinkChannel sinkChannel=p.sink();
    //Pipe出口
   Pipe.SourceChannel sourceChannel=p.source();
   //入口端写入
    String s="Write to Pipe ......";
    ByteBuffer bf=ByteBuffer.allocate(2000);
    bf.put(s.getBytes());
    bf.flip();
    while (bf.hasRemaining()) {
        sinkChannel.write(bf);
    }
   //出口端写出
    ByteBuffer bf1=ByteBuffer.allocate(2000);
    sourceChannel.read(bf1);
    System.out.println(new String(bf1.array()));

SocketChannel

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("IP",port));

读数据

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

写数据

    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.clear();
    buf.put(newData.getBytes());
    buf.flip();
    while(buf.hasRemaining()) {
        channel.write(buf);
    }

非阻塞模式
socketChannel.configureBlocking(false);
非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,可以询问选择器哪个通道已经准备好了读取,写入等

ServerSocketChannel

  • socket()返回对应的Socket对象
  • accept()接受到此通道套接字的连接。返回的是一个SocketChannel,在分阻塞模式的时候,accept没有连接就会返回null

Selector

Selector相当于一个事件管理器,可以向它注册不同连接的多个事件,并以一定间隔询问Selector是否有注册的时间发生,再进行后续的处理。这样只需要一个阻塞的线程,就可以管理所有的连接了。
Selector注册的Chnannel必须是非阻塞的,因此FileChannel肯定不能用Selector了。主要应用还是Socket。

Selector管理的事件包含两个要素: 1. 事件所对应的连接,以SocketChannel作为描述。 2. 事件的类型,SelectionKey.OP_* 常量之一。 两个要素唯一确定一个发生的事件。

主要的逻辑包括

  • 将Channel注册到Selector上,并且说明我这个Channel要监听的事件的类型
  • 调用int n = selector.select(),如果n>0说明有监听的事件发生了
  • 调用selector.selectedKeys()返回一个集合,每一个事件对应一个 SelectionKey 对象。通过 SelectionKey 获取对应的 SocketChannel 以及事件的类型,就能对SocketChannel做你想要做的必须的操作。

其他的知识点

事件的类型
1、SelectionKey.OP_CONNECT
2、SelectionKey.OP_ACCEPT
3、SelectionKey.OP_READ
4、SelectionKey.OP_WRITE

select()方法会阻塞,直到至少有一个channel的注册事件已就绪。
select(long timeout)和select()一样,但阻塞时间最大为timeout 毫秒。
selectNow()不会阻塞,不管有没有channel就绪,都立刻返回。

示例代码

原文地址:https://www.cnblogs.com/Coder-Pig/p/6599109.html