Java核心技术读书笔记11-3 Java NIO介绍与核心功能概述

Posted 芝芝与梅梅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java核心技术读书笔记11-3 Java NIO介绍与核心功能概述相关的知识,希望对你有一定的参考价值。

1.什么是NIO?

Java NIO(New IO)在Java 1.4被引入,该API可以实现与传统IO完全不同的机制。对于NIO来说,其主要提供了新的对网络的多路复用IO技术。在面向网络的数据传输中,如果含有连接数很多而每次传输的数据量不是很大,那么这个场景会更适合使用NIO技术。

注意,Java NIO的NIO不是non blocking I/O(非阻塞IO)的缩写。一来,并非其所有读写时线程都是非阻塞的。二来,非阻塞IO需要持续占用处理机进行轮询,不适合于并发情况,Java NIO使用的是多路复用技术。

2.传统IO与NIO的区别

首先,在底层,传统I/O是面向流的,每次读写一个或多个字节。在流中,不可以对数据进行随机存取。
而对于NIO来说,其传输是对缓冲区以块为单位进行的,使用缓冲区后读写位置可以进行移动,传输的工具使用的是通道,相比于单向的流,通道可以双向读写。
其次,在IO模型上,传统I/O是同步阻塞IO模型,而NIO使用的是多路复用技术,关于IO底层原理和IO模型可以看:Java NIO预备知识:I/O底层原理与网络I/O模型
另外,NIO可以仅使用一个线程管理多个读写数据的通道,IO多路复用最大的优势就是系统开销小。
最后,使用传统的IO通过组合流可以使得对文件内容复杂处理需求会更容易,但NIO包含的一些API也支持更方便的处理整个文件。

3.NIO核心组件概述

NIO的三个核心组件:Selector、Channel与Buffer
3.1 Selector
在了解了什么是多路复用之后,我们知道实现该机制必须有一个能跟踪所有Socket状态的组件,在NIO中,这个组件就是Selector。

3.2 Channel
另外在NIO中,读写数据必须经过一个通道,也就是Channel。数据经过Channel进行传输,它有些像流,但通道是双向的,也支持NIO的非阻塞读写。

通道的一些实现类:
· FileChannel 从文件中读写数据
· DatagramChannel 通过UDP读写数据
· SocketChannel 通过TCP读写数据
· ServerSocketChannel 监听TCP连接,对每个TCP连接提供一个SocketChannel
建立不同的Channel将通道连接至硬盘文件或者网络。

3.3 Buffer
Buffer是在内存中的一段区域用作用户进程读写的缓冲区。使用Channel时,要么会将数据读入到Buffer(写操作),要么从Buffer中写出数据(读操作)。
因此对于缓冲区的读入操作可以调用Channel对象的read方法从Channel中读入数据,也可以调用缓冲区的put方法直接在程序中向其放入数据。
而对于缓冲区的写出操作可以调用Channel对象的write方法将缓冲区的数据写出到Channel,也可以调用缓冲区的get方法直接返回缓冲区的数据。

Buffer的一些实现类:
· ByteBuffer
· CharBuffer
· DoubleBuffer
· FloatBuffer
· IntBuffer
· LongBuffer
· ShortBuffer
覆盖了除了boolean类型外的基本数据类型,此外还存在一个MappedByteBuffer表示内存示例文件。

缓冲区的capacity、position与limit
capacity:其值都表示在构造缓冲区时分配的容量,根据缓冲区的不同表示容量为多少个Byte、多少个Char等等。如LongBuffer.allocate(8)表示分配了8个Long的大小。

position:其值表示当前对缓冲区的操作位置,position对缓冲区相当于以下标表示,范围为0~capacity-1。初始化、调用flip方法、clear方法都会将其置为0。调用compact方法可以清空已读取的数据,然后使所有未读取的数据前移填充空隙,并置position为最后一个元素的下标+1。每次使用read或put方法将元素放入到缓冲区都是从position指示的下标开始,元素放置完毕后position+1。每次使用get方法会返回当前position位置的元素并使得position+1。
此外,使用mark/reset方法也可标记position位置以及将position置为标记。

注意每个元素单元是一个缓冲区类型,这与数组类似,例如charBuffer的每个元素单元是一个char,position后移代表向后移动一个单元。

limit:其值表示当前缓冲区内有多少元素可读。在初始化、调用clear、compact、rewind方法后会将limit置为capacity,只有调用flip方法才会将limit置为调用前的position位置。所以当缓冲区读入数据完毕,希望从缓冲区写出数据时(无论是使用get或者write)最好使用flip方法,使缓冲区做好写出准备。

一段包含Channel与Buffer的基本代码示例:

    public static void main(String[] args) throws IOException {
        //文件内容为:aaaaaaaa\\nbbbb 文件共有八个字节长度的a加一个字节换行符\\n以及四个字节的b共十三个字节
        RandomAccessFile raf = new RandomAccessFile(new File("file/Test.txt"), "rw"); //建立一个随机访问文件
        FileChannel inChannel = raf.getChannel(); //构建一个连通文件的Channel
        ByteBuffer buf = ByteBuffer.allocate(8); //构建一个字节缓冲区,缓冲区大小为8字节
        int read = inChannel.read(buf); //缓冲区从通道中读取数据,读取字节数最大为缓冲区大小,不足则为实际字节数,该方法返回读入字节的个数
        while (read != -1){ //当缓冲区存在读入数据时
            System.out.println("读取到字节数:" + read); //缓冲区大小为8,会读取两次,第一次为输出8 第二次输出5
            buf.flip(); //使缓冲区准备写出数据,置limit = position,position=0
            //该方法返回的值为position < limit,显然,返回true代表当前position位置仍有元素可读
            //剩余的元素个数即为 limit - position
            while (buf.hasRemaining()){
                System.out.print((char)buf.get()); //输出position指向的字节
            }
            System.out.println();
            buf.clear(); //清空缓冲区,position = 0, limit = capacity
            read = inChannel.read(buf); //再次试着读入
        }
        raf.close();
    }

Butter的equals与compareTo方法
equals方法比较两个缓冲区中范围为[position,limit)的元素,要求必须个数,类型与值全部相同才返回true。该方法由Object方法重写
compareTo方法,同样比较两个缓冲区[position,limit)范围的元素。该方法依次遍历两个缓冲区范围内的元素并调用Byte.compare(byte a, byte b)方法进行比较。其中a为调用者当前遍历的元素,b为参数缓冲区的当前遍历的元素。若两个元素相同,则比较下一个,否则直接返回a - b。若比较到最后发现两个缓冲区长度不匹配则返回两个缓冲区范围的长度之差,若两个缓冲区大小、元素完全相同,则最后返回0。该方法为Comparable接口实现方法。

Channel对多个Butter的读写
Scattering Reads
使用Channel对象的read方法可以将Butter数组作为参数,然后把Channel中的数据读入到这些缓冲区中,当数组中第一个缓冲区读满后才可读入第二个缓冲区,以此类推。该方法不适合与动态消息而是适合定长的数据,例如固定长度的消息头和消息体就可以分别使用相应长度缓冲区来接收。

Gathering Writes
使用Channel对象的write方法可以也将Butter数组作为参数,相反地,该方法可以按顺序将数组中Buffer在[position,limit)范围的数据写出到Channel中。
示例代码如下:

    public static void main(String[] args) throws IOException {
        RandomAccessFile raf = new RandomAccessFile(new File("file/Test.txt"), "rw"); //建立一个随机访问文件
        FileChannel inChannel = raf.getChannel(); //构建一个连通文件的Channel
        ByteBuffer b1 = ByteBuffer.allocate(8);
        ByteBuffer b2 = ByteBuffer.allocate(5);
        ByteBuffer[] byteBuffers = {b1, b2};
        //Channel数据读入到两个缓冲区中
        inChannel.read(byteBuffers);
        printBufByte(b1, "b1");
        printBufByte(b2, "b2");

        //从两个缓冲区中写出数据到Channel中
        b1.put((byte)\'b\');
        b1.put((byte)\'1\');
        b2.put((byte)\'b\');
        b2.put((byte)\'2\');
        b1.flip(); //更新指针
        b2.flip(); 
        inChannel.write(byteBuffers);
        inChannel.write(b1);
    }

通道间的数据传输
如果现在有一个FileChannel,那么可以使用这个Channel直接与其它Channel进行读写数据,也就是说可以直接将该FileChannel连接的文件内容写出到别的Channel连接的源中,或反过来。使用的方法为transerFrom与transerTo。

    public static void main(String[] args) throws IOException {
        RandomAccessFile raf1 = new RandomAccessFile(new File("file/Test.txt"), "rw"); //建立一个随机访问文件
        RandomAccessFile raf2 = new RandomAccessFile(new File("file/Test2.txt"), "rw"); //建立一个随机访问文件
        FileChannel inChannel1 = raf1.getChannel(); //构建一个连通文件的Channel
        FileChannel inChannel2 = raf2.getChannel(); //代表另一个Channel,实际上该Channel可以是任何其它的Channel
        //inChannel1向inChannel2中传送数据
        inChannel1.transferTo(1, inChannel1.size(), inChannel2); //第一个参数代表从inChannel1何处开始传送,此处为从第一个字节,第二个参数为最多传输多少个字节
        //inChannel1从inChannel2中接收传送的数据
        inChannel1.transferFrom(inChannel2, 0, inChannel2.size()); //参数含义与transerTo相同
    }


以上是关于Java核心技术读书笔记11-3 Java NIO介绍与核心功能概述的主要内容,如果未能解决你的问题,请参考以下文章

Java NIO读书笔记

Java核心技术读书笔记11- 1 Java流与读写器

[读书笔记]java核心技术

Java核心技术读书笔记12-Java SE 8引入的流

读书笔记--Java核心技术--基础篇

读书笔记--Java核心技术--高级特征