Java深入学习15:NIO详解

Java深入学习15:NIO详解

 一、Java NIO 简介

  java.nio全称java non-blocking IO(实际上是 new io),是指JDK 1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。已经被越来越多地应用到大型应用服务器,是解决高并发、I/O处理问题的有效方式。

  HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。

  在标准IO API中,你可以操作字节流和字符流,但在新IO中,你可以操作通道和缓冲,数据总是从通道被读取到缓冲中或者从缓冲写入到通道中。

二、Java NIO 与 IO 的主要区别

1-主要区别

2-IO是面向流的、阻塞的

  java1.4以前的io模型,一连接对一个线程。原始的IO是面向流的,不存在缓存的概念。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区

  Java IO的各种流是阻塞的,这意味着当一个线程调用read或 write方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入,该线程在此期间不能再干任何事情了。


3-NIO是面向块的、非阻塞的
  NIO是面向缓冲区的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性。

  Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

  通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。

三、缓冲区(Buffer)

1- 缓冲区(Buffer):在 Java NIO 中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据。根据数据类型不同(boolean 除外),提供了相应类型的缓冲区:

  ByteBuffer
  CharBuffer
  ShortBuffer
  IntBuffer
  LongBuffer
  FloatBuffer
  DoubleBuffer

上述缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区

2-- 更轻松地使用内存块,使用缓冲区读取和写入数据通常遵循以下四个步骤:

  1. 写数据到缓冲区;

  2. 调用buffer.flip()方法;

  3. 从缓冲区中读取数据;

  4. 调用buffer.clear()或buffer.compat()方法;

  当向buffer写入数据时,buffer会记录下写了多少数据,一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式,在读模式下可以读取之前写入到buffer的所有数据,一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。

3-缓冲区存取数据的两个核心方法:

  put() : 存入数据到缓冲区中

  get() : 获取缓冲区中的数据

4- 缓冲区中的四个核心属性:

  capacity : 容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。

  limit : 界限,表示缓冲区中可以操作数据的大小。(limit 后数据不能进行读写)

  position : 位置,表示缓冲区中正在操作数据的位置。

  mark : 标记,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置  

  0 <= mark <= position <= limit <= capacity

5-直接缓冲区与非直接缓冲区:

  非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中

  直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率

  字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则Java 虚拟机会尽最大努力直接在此缓冲区上执行本机I/O 操作。也就是说,在每次调用基础操作系统的一个本机I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。

  直接字节缓冲区可以通过调用此类的allocateDirect() 工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。

  直接字节缓冲区还可以通过FileChannel 的map() 方法将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer。Java 平台的实现有助于通过JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。

  字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其isDirect()方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。

6-缓冲区的基本属性和常用方法

 

 7-代码示例

import java.nio.ByteBuffer;

public class BufferTest {


    public static void main(String[] args) {
        //分配缓存
        //Allocates a new byte buffer.
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        System.out.println("---------------initial------------------");
        System.out.println("pos="+buffer.position()+" lim="+buffer.limit()+" cap="+buffer.capacity());//pos=0 lim=1024 cap=1024

        //写数据
        System.out.println("---------------put------------------");
        String str = "12345";
        buffer.put(str.getBytes());
        System.out.println("pos="+buffer.position()+" lim="+buffer.limit()+" cap="+buffer.capacity());//pos=5 lim=1024 cap=1024

        //flip。将limit设置成当前的position,将position设置成0,如果remark被设置过,则清除
        System.out.println("---------------flip------------------");
        buffer.flip();
        System.out.println("pos="+buffer.position()+" lim="+buffer.limit()+" cap="+buffer.capacity());//pos=0 lim=5 cap=1024

        //读取数据
        System.out.println("---------------get------------------");
        byte[] bytes = new byte[buffer.limit()];
        buffer.get(bytes,0,2);
        System.out.println(new String(bytes,0,2));//12
        System.out.println("pos="+buffer.position()+" lim="+buffer.limit()+" cap="+buffer.capacity());//pos=2 lim=5 cap=1024


        //mark()方法。position重设为0,mark被清除。
        System.out.println("---------------mark------------------");
        buffer.mark();//Sets this buffer's mark at its position.
        buffer.get(bytes,2,2);
        System.out.println(new String(bytes,2,2));//12
        System.out.println("pos="+buffer.position()+" lim="+buffer.limit()+" cap="+buffer.capacity());//pos=4 lim=5 cap=1024

        //reset()方法。reset后position会会推到mark标记的位置
        // Resets this buffer's position to the previously-marked position.
        System.out.println("---------------reset------------------");
        buffer.reset();
        System.out.println("pos="+buffer.position()+" lim="+buffer.limit()+" cap="+buffer.capacity());//pos=2 lim=5 cap=1024

        //rewind()方法。position初始化为0,mark被清除
        //Rewinds this buffer.  The position is set to zero and the mark is  discarded.
        System.out.println("---------------rewind------------------");
        buffer.rewind();
        System.out.println("pos="+buffer.position()+" lim="+buffer.limit()+" cap="+buffer.capacity());//pos=0 lim=5 cap=1024

        //判断缓冲区中是否还有剩余数据
        //Tells whether there are any elements between the current position and the limit.
        System.out.println("---------------remaining------------------");
        if(buffer.hasRemaining()){
            //position和limit之间的数量
            //Returns the number of elements between the current position and the limit.
            System.out.println(buffer.remaining());//5
        }

        //clear()方法,position初始化为0,limit初始化为capacity值,mark值被清除。但是clear方法并没有实际清除数据
        System.out.println("---------------clear------------------");
        buffer.clear();
        System.out.println("pos="+buffer.position()+" lim="+buffer.limit()+" cap="+buffer.capacity());//pos=0 lim=1024 cap=1024
        System.out.println((char)buffer.get());//1。(数据并没有被清除)
    }
}

参考资料

1-https://www.jianshu.com/p/362b365e1bcc

2-尚硅谷视频

猜你喜欢

转载自www.cnblogs.com/wobuchifanqie/p/12553337.html