NIO-数据存储结构:缓冲区Buffer

2021-06-29  本文已影响0人  砌月东谷

NIO称为非阻塞IO,它主要有缓冲区,通道和选择器组成

Buffer的底层是一个数组,用于存储数据,NIO提供了7中类型的缓冲区,用于存储不同类型的数据:ByteBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、CharBuffer,它们都继承java.nio.buffer

Buffer有5个重要的属性

  1. int position:下一个将要被读或写的元素位置,也就是说position永远指向BUffer中最后一次操作元素的下一个位置,初始指向第0个元素
  2. int limit :限制buffer中能够存放的元素个数
  3. int capacity:Buffer的最大容量,并且创建后不能更改
  4. int mark:标记,可以在BUffer设置一个标记,之后可以通过reset()方法返回到该标记的位置
  5. long address:堆外内存的地址,只有在直接缓冲区中才会被使用

操作Buffer的七个方法

  1. ByteBuffer allocate(int capacity):分配大小为capacity的非直接缓冲区,单位byte
  2. ByteBuffer allocateDirect(int capacity):分配大小为capacity的直接缓冲区,单位byte
  3. ByteBuffer put:向缓冲区存放数据
  4. get:从缓冲区读取数据
  5. ByteBuffer asReadOnlyBuffer():将一个Buffer转为一个只读buffer,之后不能再对转换后的buffer进行写操作
  6. ByteBuffer slice():将原Buffer从position到limit之间的部分数据交给一个新的buffer引用,也就是说,此方法返回的buffer所引用的数据,是原buffer的一个自己,而且,新的buffer引用和原buffer引用共享相同的数据
  7. ByteBuffer wrap(byte[] array):返回一个内容为array的buffer,此外,如果修改缓冲区的内容,array也会随着改变,反之亦然

在父类BUffer中,提供了4个常用方法

  1. flip():将写模式转成读模式或将读模式转为写模式
  2. rewind():用于重复读
  3. clear():清空BUffer,clear的“清空”是指buffer的各属性还原到初始值,clear()方法并不会删除
  4. mark()、reset():标记和重置,可以在BUffer中的某一个位置,通过mark()方法设置一个mark标记,之后可以通过reset方法返回mark标记的位置
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.Pipe;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class NIODemo {
    public static void test1() {
        ByteBuffer buffer = ByteBuffer.allocate(100) ;
        System.out.println("---allocate----");
        System.out.println("position:"+buffer.position());
        System.out.println("limit:"+buffer.limit());
        System.out.println("capacity(定义之后,不会再改变):"+buffer.capacity());

        //向Buffer中存放数据
        System.out.println("---put()----");
        buffer.put("helloworld".getBytes()) ;
        System.out.println("position:"+buffer.position());
        System.out.println("limit:"+buffer.limit());

        //切换到读模式
        System.out.println("---flip()----");
        buffer.flip();
        System.out.println("position:"+buffer.position());
        System.out.println("limit:"+buffer.limit());

        //从Buffer中读取数据
        System.out.println("---get()----");
        byte[] bs = new byte[buffer.limit()];
        buffer.get(bs);//读取数据,同时会后移position
        System.out.println("读取到的数据:"+ new String(bs)  );
        System.out.println("position:" + buffer.position());
        System.out.println("limit:" + buffer.limit());


        System.out.println("----slice()---");
        buffer = ByteBuffer.allocate(8);
        //buffer:0,1,2,3,4,5,6,7
        for (int i = 0; i < buffer.capacity(); i++) {
            buffer.put((byte)i);
        }
        buffer.position(2);
        buffer.limit(6);
        //sliceBuffer:2,3,4,5;获取从position到limit之间buffer的引用。
        ByteBuffer sliceBuffer = buffer.slice();
        //sliceBuffer与原Buffer共享相同的数据;即修改sliceBuffer中的数据时,buffer也会改变。
        for (int i = 0; i < sliceBuffer.capacity(); i++) {
            byte b = sliceBuffer.get(i);
            b += 100 ;
            sliceBuffer.put(i,b);
        }
        //测试
        System.out.println("当修改了sliceBuffer之后,查看buffer:");
        buffer.position(0) ;
        buffer.limit(buffer.capacity());
        while (buffer.hasRemaining()) {//{x,x,x,x,x,x}  buffer.hasRemaining():判断是否有剩余元素
            System.out.print( buffer.get() +",");
        }
        System.out.println();

        System.out.println("----mark--------");
        ByteBuffer buffer2 = ByteBuffer.allocate(100) ;
        buffer2.put("abcdefg".getBytes()) ;

        //在此时的position位置处,做一个标记mark
        buffer2.mark() ;
        System.out.println("position:" + buffer2.position());
        System.out.println("mark:" + buffer2.mark().position());
        /*
         通过get(byte[] dst, int offset, int length)方法,读取buffer中的“cde”。
         注意,此方法可以直接从Buffer中的指定位置offset开始读取数据,而不需要flip()或rewind()。
        */
        buffer2.get(bs,2,3) ;
        buffer2.reset();//恢复到mark的位置 2
        System.out.println("position:" + buffer2.position());
        System.out.println("mark:" + buffer2.mark().position());

        //判断缓冲区是否有剩余数据
        if(buffer2.hasRemaining()) {
            System.out.println("Buffer中的剩余空间数:" +buffer2.remaining()   );
        }

        //重复读rewind() : 1.postion=0,2.取消mark()
        System.out.println("---rewind()----");
        buffer2.rewind() ;
        System.out.println("position:"+buffer2.position());

        //clear()"清空"缓冲区
        System.out.println("-------clear()--------");
        ByteBuffer buffer3 = ByteBuffer.allocate(100) ;
        buffer3.put("abc".getBytes()) ;
        buffer3.clear() ;//"清空"缓冲区 :position=0,但数据并没有真正被删除,只是处于废弃状态
        System.out.println("position:"+buffer3.position());
        System.out.println("limit:"+buffer3.limit());
        System.out.println( "clear()之后,仍然可以获取到Buffer中的数据:"+(char)buffer3.get(1)  );
    }

    //使用非直接缓冲区复制文件
    public static void test2() throws IOException{
        long start = System.currentTimeMillis();
        FileInputStream input= new FileInputStream("e:\\JDK_API.CHM");
        FileOutputStream out= new FileOutputStream("e:\\JDK_API_COPY.CHM");
        //获取通道
        FileChannel inChannel = input.getChannel() ;
        FileChannel outChannel =  out.getChannel() ;
        ByteBuffer buffer = ByteBuffer.allocate(1024) ;

        while(inChannel.read(buffer) != -1){
            buffer.flip() ;
            outChannel.write(buffer );
            buffer.clear() ;
        }
        if(outChannel!=null) outChannel.close();
        if(inChannel!=null) inChannel.close();
        if(out!=null) out.close();
        if(input!=null) input.close();
        long end = System.currentTimeMillis();
        System.out.println("复制操作消耗的时间(毫秒):"+(end-start));
    }

    //使用直接缓冲区复制文件
    public static void test2_2() throws IOException{
        long start = System.currentTimeMillis();
        FileInputStream input= new FileInputStream("e:\\JDK_API.CHM");
        FileOutputStream out= new FileOutputStream("e:\\JDK_API_COPY.CHM");
        //获取通道
        FileChannel inChannel = input.getChannel() ;
        FileChannel outChannel =  out.getChannel() ;
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024) ;

        while(inChannel.read(buffer) != -1){
            buffer.flip() ;
            outChannel.write(buffer );
            buffer.clear() ;
        }
        if(outChannel!=null) outChannel.close();
        if(inChannel!=null) inChannel.close();
        if(out!=null) out.close();
        if(input!=null) input.close();
        long end = System.currentTimeMillis();
        System.out.println("复制操作消耗的时间(毫秒):"+(end-start));
    }


    public static  void test3() throws IOException{
        long start = System.currentTimeMillis();
        //用文件的输入通道
        FileChannel inChannel
                = FileChannel.open(Paths.get("e:\\JDK_API.CHM"), StandardOpenOption.READ);
        //用文件的输出通道
        FileChannel outChannel = FileChannel.open(Paths.get("e:\\JDK_API2.CHM"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
        //输入通道和输出通道之间的内存映射文件(内存映射文件处于堆外内存中)
        MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
        //直接对内存映射文件进行读写
        byte[] dst = new byte[inMappedBuf.limit()];
        inMappedBuf.get(dst);
        outMappedBuf.put(dst);
        inChannel.close();
        outChannel.close();
        long end = System.currentTimeMillis();
        System.out.println("复制操作消耗的时间(毫秒):"+(end-start));
    }
    //在直接缓冲区中,将输入通道的数据直接转发给输出通道
    public static void test4() throws IOException{
        long start = System.currentTimeMillis();
        FileChannel inChannel = FileChannel.open(Paths.get("e:\\JDK_API.CHM"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("e:\\JDK_API.CHM3"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
        inChannel.transferTo(0, inChannel.size(), outChannel);
        /*
         也可以使用输出通道完成复制,即上条语句等价于以下写法:
         outChannel.transferFrom(inChannel, 0, inChannel.size());
        */
        inChannel.close();
        outChannel.close();
        long end = System.currentTimeMillis();
        System.out.println("复制操作消耗的时间(毫秒):"+(end-start));
    }

    public static void testPipe() throws IOException {
        //创建管道
        Pipe pipe = Pipe.open();
        ByteBuffer buf = ByteBuffer.allocate(1024);
        //通过SinkChannel,向Pipe中写数据
        Pipe.SinkChannel  sinkChannel = pipe.sink();
        buf.put("helloworld".getBytes());
        buf.flip();
        sinkChannel.write(buf);
        // 通过SourceChannel,从Pipe中读取数据
        Pipe.SourceChannel sourceChannel = pipe.source();
        buf.flip();
        int len = sourceChannel.read(buf);
        System.out.println( new String(buf.array(),0,len));
        sourceChannel.close();
        sinkChannel.close();
    }

    public static void test5() {
        //分配直接缓冲区
        ByteBuffer buf = ByteBuffer.allocateDirect(1024) ;
        System.out.println(   buf.isDirect()  );
    }

    public static void main(String[] args) throws IOException {
        test1() ;
    }
}


上一篇下一篇

猜你喜欢

热点阅读