• NIO流的学习以及Buffer的相关操作


    NIO的使用

    一)、什么叫NIO?

    定义:是一套新的Java I/O标准, 在java1.4中被纳入JDK中。

    二)、NIO的实现方法

    NIO是基于块的, 以块为基本单位处理数据。

    标准的I/O是基于流实现的,以字节为单位处理数据。

    三)、NIO的特性

    1).为所有的原始类型特供Buffer支持

        ByteBuffer
    
        CharBuffer
    
        DoubleBuffer
    
        FloatBuffer
    
        IntBuffer
    
        LongBuffer
    
        ShortBuffer
    

    2).字符集编码解码解决方案,使用java.nio.Charset

    1. .增加通道(Channel)对象,做为新的原始的I/O抽象

    4).支持锁和内存映射文件的文件访问接口

    5).提供了基于Selector的异步网络I/O

    四)、NIO的两个重要组件

    Buffer: 缓冲, 是一块连续的内存块,是NIO中读写数据的中转地。

    Channel: 通道, 表示缓冲数据的源头或目的地。

    Buffer和Channel的关系:

    Channel作为数据的源头:从Channel中写数据到Buffer

    Channel    --------->     Buffer
    

    Channel作为数据的目的地:从Buffer中写出数据到Channel

         Channel   <---------      Buffer
    

    五)、NIO的Buffer类族和Channel

    Buffer: 是一个抽象类,JDK为每一种Java原生类型都创建了一个Buffer.

    注: 除了ByteBuffer外,其它每一种Buffer都具有完全一样的操作。

    原因:ByteBuffer多用于绝大多数数标准I/O操作的接口。

    Channel: 是一个双向通道,既可读也可写。

    注:应用程序中不能直接对Channel进行读写操作,在读取Channel时,需要先将数据读入到相对应的Buffer中,然后在Buffer中进行读取。

    使用Buffer读取文件:

    public class Nio_Buffer_Channel {
        public static void main(String[] args) throws IOException {
            //获取一个输入流对象
            FileInputStream fin = new FileInputStream("d:/a.txt");
            //获取输入流对象的通道,作为数据的源头
            FileChannel fileChannel = fin.getChannel();
            //创建一个Buffer对象
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //从通道中读取数据到Buffer中
            fileChannel.read(buffer);
            //关闭通道
            fileChannel.close();
            buffer.flip();
        }
    }
    

    使用Buffer完成文件的复制:

    public class Nio_Buffer_Copy {
        public static void main(String[] args) throws IOException {
            //输出流对象
            FileOutputStream fout = new FileOutputStream("d:/c.txt");
            //输入流对象
            FileInputStream fin = new FileInputStream("d:/a.txt");
            //输出流的通道,数据的目的地
            FileChannel writeChannel = fout.getChannel();
            //输入流的通道,数据的源头
            FileChannel readChannel = fin.getChannel();
            //Buffer对象
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while(true){
                buffer.clear();
                //返回读取数据的大小
                int len = readChannel.read(buffer);
                if(len == -1){
                    break;
                }
                buffer.flip();
                writeChannel.write(buffer);
            }
        }
    }
    

    结果:

    0
    11
    0
    11
    0
    

    六)、深入学习Buffer

    主要属性:

    //标志位
    private int mark = -1;
    //写模式:当前缓冲区的位置,从Position的下一个位置写数据
    //读模式:当前缓冲区的读位置,将从此位置后,读取数据
    private int position = 0;
    //写模式:  缓冲区的实际上限,总是小于等于容量,通常等于容量
    //读模式: 代表可读取的总容量,和上次写入的数据量相等
    private int limit;
    //写模式: 缓冲区的总容量上限
    //读模式: 缓冲区的总容量上限
    private int capacity;
    

    对Buffer进行claer()和flip()操作时lmint和position的变化

    public class Filp_Clear {
        public static void main(String[] args) {
            //创建具有15个字节的Buffer对象
            ByteBuffer buffer = ByteBuffer.allocate(15);
            System.out.println("存入元素后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
            //向Buffer中存入数据
            for(int i = 0 ; i < 10 ; i++){
                buffer.put((byte)i);
            }
            //存入元素后position和limit的变化
            System.out.println("存入元素后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
            buffer.flip();
            //flip后position和limit的变化
            System.out.println("flip后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
            for(int i = 0 ; i < 5 ; i++){
                System.out.print(buffer.get()+" ");
            }
            System.out.println();
            //读取Buffer元素后position和limit的变化
            System.out.println("读取Buffer元素后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
            buffer.rewind();
            System.out.println("rewind==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
            buffer.flip();
            //第二次flip后position和limit的变化
            System.out.println("第二次flip后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
            buffer.clear();
            //clear后position和limit的变化
            System.out.println("clear后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
    
        }
    }
    

    运行结果:

    存入元素后position和limit的变化==>position:0 limit:15 capacity:15
    存入元素后position和limit的变化==>position:10 limit:15 capacity:15
    flip后position和limit的变化==>position:0 limit:10 capacity:15
    0 1 2 3 4 
    读取Buffer元素后position和limit的变化==>position:5 limit:10 capacity:15
    rewind==>position:0 limit:10 capacity:15
    第二次flip后position和limit的变化==>position:0 limit:0 capacity:15
    clear后position和limit的变化==>position:0 limit:15 capacity:15
    

    结果分析:

    1).当第一次创建Buffer对象时

        position = 0, capacity = limit = Buffer数组容量大小
    

    2).往Buffer添加数据

       position = 数组所占数据的大小,capacity = limit = Buffer数组容量大小
    

    3).buffer.flip()操作

        position = 0, limit = 数组中所占元素的大小,即原position值, capacity =  Buffer数组容量大小
    

    4).buffer.get()获取元素

       position = 获取元素的个数,limit = 数组中所占元素的大小,capacity =  Buffer数组容量大小
    

    5).再次buffer.flip()

        position = 获取元素的个数,limit = position值,  capacity =  Buffer数组容量大小
    
        注: 当执行flip操作,limit值总是等于上一次的position值
    

    6).buffer.clear

      position = 0, capacity = limit = Buffer数组容量大小
    

    总结:

       i.  put():  position = 数组元素个数 , limit 和 capacity 不变
    
       ii.  flip(): position = 0,   limit = 原position值,  capacity 不变
    
       iii. rewind(): position = 0, limit 和 capacity 不变
    
       iiii.  claer(): 回到初始状态,position = 0, capacity = limit = Buffer数组容量大小。
    

    七)、Buffer的相关操作

    1)、Buffer的创建:

    1.从堆中分配

    //从堆中分配
    ByteBuffer buffer = ByteBuffer.allocation(1024);
    //ByteBuffer类
    public static ByteBuffer allocate(int capacity) {
            if (capacity < 0)
                throw new IllegalArgumentException();
            return new HeapByteBuffer(capacity, capacity);
        }
        
    //HeapByteBuffer类 extend ByteBuffer 
     HeapByteBuffer(int cap, int lim) {            // package-private
            super(-1, 0, lim, cap, new byte[cap], 0);
            /*
            hb = new byte[cap];
            offset = 0;
            */
        }
    //super(-1, 0, lim, cap, new byte[cap], 0) 
    ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
                     byte[] hb, int offset)
        {
            super(mark, pos, lim, cap);
            this.hb = hb;
            this.offset = offset;
        }
    

    2.从既有数组中创建

    byte array[] = new byte[1024];
    ByteBuffer buffer = ByteBuffer.wrap(arry);
    public static ByteBuffer wrap(byte[] array) {
            return wrap(array, 0, array.length);
        }
    public static ByteBuffer wrap(byte[] array,
                                        int offset, int length)
        {
            try {
                return new HeapByteBuffer(array, offset, length);
            } catch (IllegalArgumentException x) {
                throw new IndexOutOfBoundsException();
            }
        }
     HeapByteBuffer(byte[] buf, int off, int len) { // package-private
    
            super(-1, off, off + len, buf.length, buf, 0);
            /*
            hb = buf;
            offset = 0;
            */
        }
    
    ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
                     byte[] hb, int offset)
        {
            super(mark, pos, lim, cap);
            this.hb = hb;
            this.offset = offset;
        }
    

    2)、重置和清空缓存区

    1.rewind(): 将position置为0,并清除标志位(mark)mark = -1

    作用:提取Buffer的有效数据。
    

    2.clear(): 将position重置为0,同时,将limit设置为capacity大小,清除标志位 (mark), msrk = -1

     作用:为重写Buffer做准备。
    

    3.flip(): 将limit设置到position所在位置,将position重置为0,并清除标志位mark = -1

      作用:读写转换时使用。
    

    注:这里的重置是指重置Buffer的各标志位,并不是清空Buffer的内容。

    3)、读/写缓冲区

    //返回当前position的数据,position后移一位
    public byte get();
    //读取Buffer的数据到dst,并恰当的移动position
    public ByteBuffer get(byte[] dst);
    //读取给定index上的数据,不改变position的位置
    public byte get(int index);
    //在当前位置写入给定数据,position后移一位
    public ByteBuffer put(byte b);
    //将当前数据写入index位置,position位置不变
    public ByteBuffer put(int index, byte b);
    //将给定的数据src写入到Buffer中,并恰当的移动position
    public final ByteBuffer put(byte[] src);
    

    4)、标志缓冲区

    标志(mark)缓冲区:类似于书签,可以随时记录当前位置,在任意时刻,可以回到这个位置。

    操作mark的方法:

    public final Buffer mark()
    public final Buffer reset()
    

    1.设置mark为当前position值

    buffer.mark();
    public final Buffer mark() {
            //将mark值置为当前的position值
            mark = position;
            return this;
        }
    

    2.获取mark值,将当前的position置为mark值

    buffer.reset();
    public final Buffer reset() {
            int m = mark;
            if (m < 0)
                throw new InvalidMarkException();
            position = m;
            return this;
        }
    

    测试:

    public class MarkTest {
        public static void main(String[] args) {
            ByteBuffer buffer = ByteBuffer.allocate(15);
            for(int i = 0 ; i < 10 ; i++){
                buffer.put(((byte)i));
    
            }
            //读写转换
            buffer.flip();
            for(int i = 0 ; i < buffer.limit() ; i++){
                System.out.print(buffer.get());
                if(i == 4){
                    //mark = 5,当 i = 4 时,get后,position后移的一位,所以mark = 5
                    buffer.mark();
                    System.out.print("mark = "+buffer.mark());
                }
            }
            System.out.println();
            System.out.println("原position值 = "+buffer.position());
            //重置position为mark值
            buffer.reset();
            System.out.println("reset后position值 = "+buffer.position());
            while(buffer.hasRemaining()){
                System.out.print(buffer.get());
            }
        }
    }
    

    结果:

    01234mark = java.nio.HeapByteBuffer[pos=5 lim=10 cap=15]56789
    原position值 = 10
    reset后position值 = 5
    56789
    

    5)、复制缓冲区

    复制缓冲区:以原缓冲区为基础,生成一个完全一样的新缓冲区。

    新缓冲区的特点:

    1.新生成的缓冲区和原缓冲区共享相同的内存数据。

    2.缓冲区的任意一方的数据改动都是相互可见的。

    3.新生成的缓冲区和原缓冲区独立维护各自的position、limit、mark。

    作用:增加了程序的灵活性,为多方同时处理数据提供了可能。

    生成复制缓冲区的方法:

    public ByteBuffer duplicate()
    

    测试:

    public class Duplicate_Buffer {
        public static void main(String[] args) {
            ByteBuffer buffer = ByteBuffer.allocate(15);
            for(int i = 0 ; i < 10 ; i++){
                buffer.put((byte)i);
            }
            //复制当前缓冲区
            ByteBuffer copyBuffer = buffer.duplicate();
            System.out.println("复制后的缓冲区");
            System.out.println(buffer);
            System.out.println(copyBuffer);
            //重置copyBuffer
            copyBuffer.flip();
            //重置copyBuffer后缓冲区变化
            System.out.println("重置copyBuffer后的缓冲区");
            System.out.println(buffer);
            System.out.println(copyBuffer);
            //向copyBuffer缓冲区中存入数据
            copyBuffer.put((byte)100);
            //buffer在相同的位置数据也发生了变化
            System.out.println("buffer.get(0) = "+buffer.get(0));
            System.out.println("copyBuffer.get(0) = "+ copyBuffer.get(0));
    
    
        }
    }
    

    结果:

    复制后的缓冲区
    java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
    java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
    重置copyBuffer后的缓冲区
    java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
    java.nio.HeapByteBuffer[pos=0 lim=10 cap=15]
    buffer.get(0) = 100
    copyBuffer.get(0) = 100
    

    结论:在对副本copyBuffer进行put操作后,原Buffer相同所在位置的数据也同样发

             生了变化。
    

    6)、缓冲区分片

    定义:在现有的缓冲区中,创建新的子缓冲区。

    特点:子缓冲区和父缓冲区共享数据。

    创建子缓冲区的方法:slice()

    ByteBuffer buffer = ByteBuffer.allocate(15);
            for(int i = 0; i < 10; i++){
                buffer.put((byte)i);
            }
            buffer.position(2);
            buffer.limit(6);
            //生成2-6的缓冲区
            ByteBuffer subBuffer = buffer.slice();
    
    public class Slice_Buffer {
        public static void main(String[] args) {
            ByteBuffer buffer = ByteBuffer.allocate(15);
            for(int i = 0; i < 10; i++){
                buffer.put((byte)i);
            }
            buffer.position(2);
            buffer.limit(6);
            //生成2-6的缓冲区
            ByteBuffer subBuffer = buffer.slice();
            System.out.println(subBuffer);
            System.out.println(subBuffer.get(0));
            System.out.println(subBuffer.get(3));
    
        }
    }
    

    结果:

    java.nio.HeapByteBuffer[pos=0 lim=4 cap=4]
    2
    5
    

    子缓冲区发生了变化,父缓冲区也随即发生变化:

    public class Slice_Buffer {
        public static void main(String[] args) {
            ByteBuffer buffer = ByteBuffer.allocate(15);
            for(int i = 0; i < 10; i++){
                buffer.put((byte)i);
            }
            buffer.position(2);
            buffer.limit(6);
            //生成2-6的缓冲区
            ByteBuffer subBuffer = buffer.slice();
            System.out.println(subBuffer);
            System.out.println(subBuffer.get(0));
            System.out.println(subBuffer.get(3));
            for(int i = 0; i < subBuffer.capacity(); i++){
                byte bb = subBuffer.get(i);
                bb*= 10;
                subBuffer.put(bb);
            }
            //重置父缓冲区,若不重置,输出20、30、40、50
            buffer.position(0);
            buffer.limit(buffer.capacity());
            while(buffer.hasRemaining()){
                System.out.print(buffer.get()+" ");
            }
            System.out.println();
        }
    }
    

    7)、只读缓冲区

    特点:只读,只读缓冲区和原始缓冲区共享内存块,原始缓冲区的修改,只读缓冲区也是可见的。

    创建只读缓冲区的方法:

    asRreadOnlyBuffer()
    
    public class AsReadOnlyBuffer_Test {
        public static void main(String[] args) {
            ByteBuffer buffer = ByteBuffer.allocate(15);
            for(int i = 0; i < 10; i++){
                buffer.put((byte)i);
            }
            //创建只读缓冲区
            ByteBuffer readBuffer = buffer.asReadOnlyBuffer();
            System.out.println(buffer);
            System.out.println(readBuffer);
            readBuffer.flip();
            while(readBuffer.hasRemaining()){
                System.out.print(readBuffer.get()+" ");
            }
            System.out.println();
            //检测对原始缓冲区进行修改,只读缓冲区可见
            buffer.put(2,(byte)20);
            readBuffer.flip();
            while(readBuffer.hasRemaining()){
                System.out.print(readBuffer.get()+" ");
            }
        }
    }
    

    结果:

    java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
    java.nio.HeapByteBufferR[pos=10 lim=15 cap=15]
    0 1 2 3 4 5 6 7 8 9 
    0 1 20 3 4 5 6 7 8 9 
    

    8)、文件映射到内存

    使用文件映射到内存读取文件中的数据使用RandomAccessFile对象读取

    RamdomAcessFile: 随机访问存取对象。

    特点: 专门处理文件的类 ,支持"随机访问"方式。

    随机:指可以跳转到文件的任意位置处读写数据 。

    由FileChannel.map()将文件映射到内存:

    //将文件的前1024个字节映射到内存
    MappedByteBuffer mbb = channel.map(FileChannel.MapMode.Read_WRITE, 0, 1024)
     
    
    public class MappedFile {
        public static void main(String[] args) throws IOException {
           //随机访问文件类
           RandomAccessFile fin = new RandomAccessFile("d:/a.txt","rw");
            FileChannel channel = fin.getChannel();
            MappedByteBuffer buffer = 
           //将文件的前1024个字节映射到内存
           channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
            while(buffer.hasRemaining()){
                System.out.print((char)buffer.get());
            }
        }
    }
    

    结果:

    hhhhhhhhhhh
    

    结论:使用文件映射内存的方式,将文本文件通过FileChannel映射到内存中,返回一个Buffer对象,从内存中读取文件的内容,通过修改Buffer,将实际数据写到对应的磁盘中。

    9)、处理结构化数据

    处理结构化数据的方法:

    1.散射:将数据读入到一组Buffer中,即将数据读到Buffer[]中。

    由scatteringByteBuffer接口提供操作方法:

    public long read(Buffer[] dsts) throws IOException;
    public long read(Buffer[] dsts, int offset, int length) throws IOException;
    

    注:通过散射读取数据,通道依次填充每个缓冲区。

    2.聚集:将数据写入到一组Buffer中,即将数据写入到Buffer[]中。

     由GatheringByteChannel接口提供操作方法:
    
    public long write(Buffer[] src) throws IOException;
    public long write(Buffer[] src, int offset, int length) throws IOException;
    

    散射/聚集的使用:

    对于一个固定格式的文件的读写,在已知文件具体结构情况下,可以构建若干个符合文件结构的Buffer,Buffer的大小恰好符合各段结构的大小。

    Jdk提供了各种通道,使用散射和聚集读写结构化数据:

    例:DatagramChannel、 FileChannel、SocketChannel

    注:这3个通道都实现了ScatteringByteChannel和GatheringByteChannel.

    通过聚集创建文本文件,格式为 书名作者:

    public class GatheringBuffer {
        public static void main(String[] args) throws IOException {
            //通过聚集创建文本文件,格式为 书名作者
            ByteBuffer bookBuf = ByteBuffer.wrap("java性能优化技巧".getBytes("Utf-8"));
            ByteBuffer autBuf = ByteBuffer.wrap("葛一鸣".getBytes("utf-8"));
            int booklen = bookBuf.capacity();
            int autlen = autBuf.capacity();
            ByteBuffer buffer[] = new ByteBuffer[]{bookBuf ,autBuf};
            File file = new File("d:/gather.txt");
            //文件不存在时,创建一个新文件
            if(!file.exists()){
                file.createNewFile();
            }
            FileOutputStream fou = new FileOutputStream(file);
            FileChannel channel = fou.getChannel();
            //聚集写文件
            channel.write(buffer);
            channel.close();
    
        }
    }
    

    通过散射读取固定格式的文本文件:

    前提:知道文件对应的格式长度,精确的构造Buffer

    public class GatheringBuffer {
        public static void main(String[] args) throws IOException {
            //通过聚集创建文本文件,格式为 书名作者
            ByteBuffer bookBuf = ByteBuffer.wrap("java性能优化技巧".getBytes("utf-8"));
            ByteBuffer autBuf = ByteBuffer.wrap("葛一鸣".getBytes("utf-8"));
            int booklen = bookBuf.capacity();
            int autlen = autBuf.capacity();
            ByteBuffer[] buffer = new ByteBuffer[]{bookBuf ,autBuf};
            File file = new File("d:/gather.txt");
            //文件不存在时,创建一个新文件
            if(!file.exists()){
                file.createNewFile();
            }
            FileOutputStream fou = new FileOutputStream(file);
            FileChannel channel = fou.getChannel();
            //聚集写文件
            channel.write(buffer);
            channel.close();
            //通过散射读取文件,格式:书名作者
            //构造精确的Buffer
            ByteBuffer scatter_bookBuffer = ByteBuffer.allocate(booklen);
            ByteBuffer scatter_autBuffer = ByteBuffer.allocate(autlen);
            ByteBuffer[] scatter_Buffer = new ByteBuffer[]{scatter_bookBuffer, scatter_autBuffer};
            FileInputStream fin = new FileInputStream("d:/gather.txt");
            FileChannel fileChannel = fin.getChannel();
            fileChannel.read(scatter_Buffer);
            String bookName = new String(scatter_Buffer[0].array(),"utf-8");
            String authorName = new String(scatter_Buffer[1].array(),"utf-8");
            System.out.println(bookName+authorName);
        }
    }
    

    结果:

    java性能优化技巧葛一鸣
    

    八)、MappedByteBuffer性能评估

    性能比较:

    传统的基于流的I/O操作 < NIO的ByteBuffer < NIO的MappedByteBuffer(将文件映射到内存)。

    九)、直接内存访问

    DirectBuffer: Buffer提供的直接访问系统物理内存的类。

    普通的ByteBuffer: 在堆上分配内存,其最大内存,受最大堆限制。

    DirectBuffer: 直接分配在物理内存中,并不占用堆空间。

    创建DirectBuffer对象:

    ByteBuffer.allocateDirect()
    

    DirectBuffer和ByteBuffer的区别:

    1.DirectBuffer的内存访问速度比ByteBuffer的快

    2.创建和销毁DirectBuffer的花费远比ByteBuffer的高

    DirectBuffer的使用:

    在需要频繁的创建和销毁Buffer时,不宜使用DirectBuffer, 但如果能将DirectBuffer进行复用,那么,在读写的情况下,可以大幅度的改善系统的性能。

    金麟岂能忍一世平凡 飞上了青天 天下还依然
  • 相关阅读:
    使用RF(robotframework)要安装哪些库
    MYSQL题目练习专用
    MySQL字段拼接
    WPF样式
    WPF数据模板
    WPF控件模板
    WPF布局
    面向对象程序设计原则
    设计模式之策略模式
    设计模式之简单工厂模式
  • 原文地址:https://www.cnblogs.com/Auge/p/11665897.html
Copyright © 2020-2023  润新知