• Java学习:NIO<一>


    一、NIO概述

    在JDK4中引入了NIO,可以最大限度的满足Java程序I/O的需求

    • java.nio包,定义了各种与buffer相关的类
    • java.nio.channel包,包含与Channel和Selector相关的类
    • java.nio.charset包,与字符集相关的类

    在NIO中有三大核心组件:Channel、Buffer、Selector
    传统IO与JavaNIO的区别:

    IONIO
    传统I/O是面向流的,每次可以从流中读取一个或多个字节,只能向后读取,不能向前移动;NIO是面向缓冲区的,把数据读取到一个缓冲区中,可以在缓冲区中向前/向后移动,增加了程序的灵活性
    IO流是线程阻塞的,在调用read()/write()读写数据时,线程阻塞,直到数据读取完毕或者数据完全写入,在读取过程中,线程不能做其它操作;NIO不是线程阻塞的,当线程从Channel中读取数据时,如果通道中没有可用的数据,线程不阻塞,可以做其它的任务
    /在NIO中,所有数据都需要通过Channel传输,通道可以直接将一块数据映射到内存中,channel是双向的,不仅可以读取数据,还能保存数据,程序不能直接读写channel通道,Channel只与Buffer缓冲区交互,数据通过channel写到buffer,程序从buffer中读数据

    二、Buffer

    • Buffer缓冲区实际上就是一个数组,把数组的内容与信息包装成一个Buffer对象,提供了一组访问这些信息的方法

    1、Buffer的属性

    属性作用
    capacity是指缓冲区可以存储多少个数据,容量在创建buffer缓冲区时指定大小,创建后不能再修改,如果缓冲区满了,需要清空后才能继续写数据
    position表示当前位置,即缓冲区写入/读取的位置,刚刚创建Buffer对象后,position初始化为0,写入一个数据,position就向后移动一个单元,它的最大值为capacity - 1,当Buffer从写模式切换到读模式,position会被重置为0,每读一个数据,position就向后移动一个单元
    limit是指第一个不能被读取或写入的位置,limit上限后面的单元既不能读也不能写,在Buffer缓冲区写模式下,limit表示能够写入多少个数据;在读取模式下,limit表示最多可以读取多少个数据
    mark设置一个标记位置,可以调用mark()方法,把标记设置在position位置,当调用reset()方法时,就把position设置为mark标记的位置

    2、Buffer的API

    • 在NIO关键的Buffer有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,这些Buffer覆盖了能够通过I/O发送的所有基本类型:byte、char、double、float、int、long、short等。实际使用较多的是ByteBuffer、CharBuffer
    API作用
    allocate(capacity)可以创建一个指定容量的缓冲区
    put()用于向缓冲区中存储数据
    get()用于从缓冲区中读取数据
    compact()当缓冲区还有未读完的数据,可以调用compact()方法进行压缩,将所有未读取的数据复制到Buffer的起始位置,把position设置到最后一个未读元素的后面。limit属性设置为capacity
    capacity()返回缓冲区大小
    limit()返回limit上限的位置
    mark()设置缓冲区的标志位置,这个值只能在0 ~ position之间,以后可以通过reset()方法返回到这个位置
    position()可以返回position当前位置
    remaining()返回当前position位置与limit之间的数据量
    rewind()将position设置为0,取消mark标志位
    clear()清空缓冲区,仅仅是修改position标志为0,设置limit为capacity,缓冲区数据还是存在的
    flip()可以把缓冲区由写模式切换到读模式,先把limit设置为position位置,再把position设置为0
    duplicate()缓冲区的复制,之前的缓冲区与实际的缓冲区引用的是同一个数组
    slice()分隔缓冲区,根据[position,limit)区间来创建新的缓冲区

    3、案例
    1)、创建buffer缓冲区

    package JavaNIO;
    
    import java.nio.CharBuffer;
    import java.util.Arrays;
    
    /*
        缓冲区的创建方式
        1)、分配操作创建缓冲区,allocate()分配一个私有的、指定容量大小的数组来存储元素
        2)、包装操作创建缓冲区,它使用提供的数组作为存储空间来存储缓冲区中的数据,不再分配其他空间
     */
    public class CreadBuffer {
        public static void main(String[] args) {
            //1)、分配操作创建缓冲区
            CharBuffer buffer = CharBuffer.allocate(16);
    
            //2)、包装操作创建缓冲区
            char[] myarray = new char[16];
            //把已存在的数组包装成一个buffer对象
            CharBuffer buffer1 = CharBuffer.wrap(myarray);
    
            //通过调用put方法向缓冲区中保存数据,也会直接影响到数组
            buffer1.put("hello");
            buffer1.flip();   //切换到读模式
            System.out.println(buffer1);
            System.out.println(Arrays.toString(myarray));
    
            //对数组做的任何修改,也会影响缓冲区对象
    
            /*
                不管是allocate()还是wrap()创建的缓冲区都是间接的,间接缓冲区会使用备份数组,
                hasArray()方法可以判断是否有一个可存取的备份数组
                如果hasArray()返回true,可以通过array()返回缓冲区对象使用的备份数组的引用
                备份数组的内容跟原始数组的内容是一致的
             */
            if (buffer1.hasArray()){
                char[] arr2 = buffer1.array();
                System.out.println(Arrays.toString(arr2));
            }
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    2)、相关API应用

    package JavaNIO;
    
    import java.nio.CharBuffer;
    
    public class Buffer {
        public static void main(String[] args) {
            //1、创建CharBuffer缓冲区对象
            CharBuffer buffer = CharBuffer.allocate(12);
    
            //2、打印capacity,limit,position
            System.out.println("capacity:" + buffer.capacity()
            + ",limit:" + buffer.limit() + ",position:" + buffer.position());
            //capacity:12,limit:12,position:0
    
            //3、向缓冲区存储数据
            buffer.put('成');
            buffer.put('吉');
            buffer.put('思');
            buffer.put('汗');
            System.out.println("capacity:" + buffer.capacity()
                    + ",limit:" + buffer.limit() + ",position:" + buffer.position());
            //capacity:12,limit:12,position:4
    
            //4、调用filp()方法把缓冲区切换为读模式
            buffer.flip();
            System.out.println("capacity:" + buffer.capacity()
                    + ",limit:" + buffer.limit() + ",position:" + buffer.position());
            //capacity:12,limit:4,position:0
    
            //5、调用get()方法直接读取缓冲区数据
            System.out.println(buffer.get());
            System.out.println("capacity:" + buffer.capacity()
                    + ",limit:" + buffer.limit() + ",position:" + buffer.position());
            //capacity:12,limit:4,position:1
    
            //6、再次存储数据,把数据保存在position位置
            buffer.put('X');
            System.out.println("capacity:" + buffer.capacity()
                    + ",limit:" + buffer.limit() + ",position:" + buffer.position());
            //capacity:12,limit:4,position:2
    
            //7、设置标记
            buffer.mark();
    
            //8、再读取一个字符
            System.out.println(buffer.get());
            System.out.println("capacity:" + buffer.capacity()
                    + ",limit:" + buffer.limit() + ",position:" + buffer.position());
            //capacity:12,limit:4,position:3
    
            //9、调用reset(),把position重置为mark标记位置
            buffer.reset();
            System.out.println("capacity:" + buffer.capacity()
                    + ",limit:" + buffer.limit() + ",position:" + buffer.position());
            //capacity:12,limit:4,position:2
    
            //10、调用compact()压缩,会把postion后的数据移动到起始位置,limit重置为capacity
            buffer.compact();
            System.out.println("capacity:" + buffer.capacity()
                    + ",limit:" + buffer.limit() + ",position:" + buffer.position());
    
            //11、调用clear清空
            buffer.clear();
            System.out.println("capacity:" + buffer.capacity()
                    + ",limit:" + buffer.limit() + ",position:" + buffer.position());
    
            //12、clear清空后,缓冲区的数据依然存在
            while (buffer.hasRemaining()){
                System.out.println(buffer.get());
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    3)、批量读取/写入

    package JavaNIO;
    
    import java.nio.CharBuffer;
    import java.util.Arrays;
    
    /*
        使用缓冲区就是为了提高数据传输效率,一次读写一个字符或一个字节效率并不高,可以进行批量的操作
        可以借助于数组,把缓冲区中的一块数据读取到数组中,也可以把数组中的部分内容保存到缓冲区中
     */
    public class BatchRead {
        public static void main(String[] args) {
            //创建CharBuffer
            CharBuffer buffer = CharBuffer.allocate(15);
            buffer.put("云想衣裳花想容,春风拂槛露华浓");
            //切换为读模式
            buffer.flip();
            System.out.println(buffer);
    
            //定义字符数组
            char[] dst = new char[8];
            //调用get()方法把缓冲区中的数据读到字符数组中
            //注意批量传输时大小总是固定的,如果没有指定传输的大小,意味着把数组填满
            CharBuffer remainBuffer = buffer.get(dst);
            System.out.println(Arrays.toString(dst));
            //[云, 想, 衣, 裳, 花, 想, 容, ,],相当于把前8位写入字符数组中
            System.out.println(remainBuffer);
            //春风拂槛露华浓,后面再调用get()方法,剩余的就是remainBuffer
    
            //继续把buffer缓冲区的内容读到字符数组
            //buffer.get(dst);这时候是会报错的
            //当缓冲区的数据量不足以填满整个数组时,会抛出异常,剩下的是"春风拂槛露华浓",只有7个字符
            //此时可以调用get的重载,可以指定缓冲区长度
            buffer.get(dst,0,buffer.remaining());
            System.out.println(dst);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    4)、直接字节缓冲区

    • 在硬盘中和操作系统中处理的数据都是01二进制,缓冲区中只有ByteBuffer字节缓冲区有资格参与IO操作,channel通道只能通过ByteBuffer作为它的参数
    • 直接字节缓冲区通常是I/O操作最好的选择,如果使用非直接字节缓冲区,可能会导致性能损耗。
    • 如果向通道传到一个非直接字节缓冲区,通道可能会创建一个临时的直接字节缓冲区,将非直接字节缓冲区的内容复制到临时的直接字节缓冲区,使用临时的直接字节缓冲区执行底层的I/O操作
    • 直接字节缓冲区是I/O的最佳选择,可能创建直接缓冲区比创建非直接缓冲区的成本要高,直接缓冲区使用的内存是通过调用本地操作系统的代码分配的,绕过了JVM的堆栈,现在JVM可能会执行缓冲区缓存的优化
    	//创建直接字节缓冲区
        ByteBuffer.allocateDirect()
    
    • 1
    • 2

    三、Channel

    1、channel概述

    • Channel是一种新的I/O访问方式,用于在字节缓冲区与通道另一侧的实体(可以是文件也可以是socket套接字)之间进行传输数据
    • channel可以双向读写数据,也可以实现异步读写
    • 程序不能直接访问channel,channel只能与buffer缓冲区进行交互,即把通道中的数据读到Buffer缓冲区中,程序从缓冲区中读取数据;在写操作时,程序把数据写入到buffer缓冲区中,再把buffer中数据写入到channel中
    • 常用的channel有FileChannel:读写文件;SocketChannel/ServerSocketChannel:读写socket套接字的;DatagramChannel:通过UDP读写网络数据

    2、Scatter/Gather

    • scatter(发散)、gather(聚集)是通道提供的重要功能(有时也称为矢量I/O),是指在多个缓冲区中实现一个简单的I/O操作
    • scatter:从channel中读取数据,把这些数据按顺序分散写入到多个Buffer缓冲区中
    ByteBuffer buf1 = ByteBuffer.allocate(48);
    ByteBuffer buf2 = ByteBuffer.allocate(1024);
    ByteBuffer[] bufArray = {buf1,buf2};
    Channel.read(bufArray)
    read()方法从channel中读取数据,按照在数组中的顺序依次存储到缓冲区中,注意必须在buf1缓冲区满后才能写入buf2.使用scatter/gather处理的数据大小都是固定的
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • gather:写操作时,将多个Buffer缓冲区的数据写入到同一个Channel中
  • 相关阅读:
    超火的低代码平台长什么样
    开源大数据比对平台设计与实践—dataCompare
    uniapp 学习笔记三十四 加载用户地址列表和默认地址的在线数据修改
    springboot停车场车辆定位管理可视化分析系统的设计与实现毕业设计源码101702
    图的若干定义及表示
    【算法】迷宫问题
    基于纳什博弈的多微网主体电热双层共享策略(matlab代码)
    【Pandas】Python处理数据透视(行转列、列转行)
    《Linux设备驱动开发详解》之udev用户空间设备管理
    .NET的基元类型包括哪些?Unmanaged和Blittable类型又是什么?
  • 原文地址:https://blog.csdn.net/nzbing/article/details/128070551