java nio(nio机制buffer及buffer优化)

本文详细解析了NIO中Buffer的工作原理,包括状态变量(position、limit、capacity)的作用及变化过程,get()和put()等访问方法的使用,并介绍了Buffer的分配、包装、切片等高级应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深入Buffer

    下面,我们看下NIObuffer的两个重要的组成部分:

buffer的状态变量和buffer的访问方法;

    状态变量是buffer内部计数系统的关键,在每一次的read/write过程中,buffer的状态变量都是变化的。通过记录和跟踪这些状态变化,buffer就可以在内部完成操作资源的控制;   

    当你从channel中读取数据的时候,数据被存放到buffer中;有的时候,你直接把获得的buffer数据集传递给另外一个channel

但是大部分情况下,你需要检查buffer中的数据,此时你可以使用get()方法;如果你需要把原数据存放到buffer,可以使用put()方法;

   

 

虽然初看起来NIO的内部计数系统比较复杂,但是你很快会发现很多工作实际上是为你做的;那些你本应该自己用byte数组和index来实现的动作,NIO内部完成了;

   

状态变量:

       buffer的状态变量有三个:position,limit,capacity;

       

       Position:

        我们已经说过,buffer实际上一个byte数组的封装,当你从channel中读入数据时,你实际上将数据放置在底层的array中。position记录了你已经写入数组的数据量.更准确的说,是指向数组中下一个数据将要放置的位置。所以,如果你读入3byte,bufferposition的值为3(数组的第四个元素的位置);

        同时,写入数据时,postion记录了你已经写过的数据量,确切地说,是指向数组中下一个将写的数据的位置.例如,如果你已经写了5byte,position的值为5(数组的第6个元素的位置)

       Limit:

       limit变量表明写数据时剩余将要获取的数据个数(bufferàwrite,或者读数据时还可以存放数据的空间量(readàbuffer)。不管写出还是读入数据,

状态变量满足以下关系:position<=limit;

       Capacity(容量)

           底层数组的大小(size);

           满足:limit<=capacity;

   

       变量变化过程观察:

1)capacity=8

2)设置limit的值 limit=8;

3)set position=0;

 

读过程:

1)  读取3数据:limit不变,position=3

2)  再读取2个数据:

limit不变;position+=2;

写过程:

1flip完成2个操作(limit置于当前position位置后,position0

limit = position;

position = 0;

 

2)第一次读操作,读4数据:

limit不变,position=4;

1)  第二次读取,1数据;

limit不变,position+=1;

读数据的clear操作:limit=capacity, position=0; (准备新的数据读取)

buffer的访问方法:

       当你需要直接处理buffer中的数据时,使用buffer的访问方法,包括get()以获取buffer中的数据,put()以向buffer中添加数据;

       get4个相关的方法:

           byte get();  

           ByteBuffer get(byte[] dst); 效果同(get(byte[] dest,0,length))

           Bytebuffer get(byte[] dst, int offset,int length)

           byte get(int index);

       对方法2,3是将ByteBuffer中的数据从buffer.position开始,读取length个数据,放置在dest数组中以偏移量offset位置开始的地方之后的空间里;如果length>buffer.remaining()(limit-position),抛出BufferUnderflowException运行时异常;

      

       put5个相关方法:

           ByteBuffer put(byte b);

           ByteBuffer put(byte[] src);

           ByteBuffer put(byte[] src,int offset,int length);

           ByteBuffer put(ByteBuffer src);

           ByteBuffer put(int index,byte b);

 

        方法5byte b放置到ByteBuffer的指定位置;方法2,3从数组中获取数据放置到buffer;方法4ByteBuffer之间数据的复制;

      

        以上方法是ByteBuffer的相关方法;其他类型都有相似的处理行为;

   

       Buffer中使用putget方法的例子:

       CreateBuffer.java        CreateArrayBuffer.java

       ReadAndShow.java         WriteSomeBytes.java

   

 

get()put()方法可以指定类型,如下:

       getByte();getChar();getShort();getInt();getLong();getFloat();

       getDouble();

       putByte();putChar();putShort();putInt();putLong();putFloat();

       putDouble();

 

        TypesInByteBuffer.java

 

不同Bufferget()put()例子;

       UseFloatBuffer.java

 

 

 

 

---------------------------------------------------------------

        static public void main( String args[] ) throws Exception {

    FloatBuffer buffer = FloatBuffer.allocate( 10 );

 

    for (int i=0; i<buffer.capacity(); ++i) {

      float f = (float)Math.sin( (((float)i)/10)*(2*Math.PI) );

      //System.out.println("-------------"+f);

      buffer.put( f );

    }

 

    buffer.flip();

 

    while (buffer.hasRemaining()) {

      float f = buffer.get();

      System.out.println( f );

    }

  }

---------------------------------------------------------------

 

重新回顾下buffer工作的循环过程:

 

---------------------------------------------------------------------

    while (true) {

buffer.clear();

int r = fcin.read( buffer );

if (r==-1) {

break;

}

buffer.flip();

fcout.write( buffer );

}

---------------------------------------------------------------------

    read()write()封装了大部分的操作细节,所以操作起来非常方便,clearflip分别对应buffer的读操作和写操作;

   

 

更多的Buffer细节:

    有了前面的探讨,你可以尽可能简单地用NIO中的方法实现原来的IO方法;下面将要讨论使用buffers中的复杂的方面,譬如bufferallocation,wrapping,slicing;同时我们将会探讨一些nio中的新特性;还有如何创建不同的buffer对象来满足不同的需求:read-onlybuffer,可以保护数据不被修改;directbuffer,可以直接和底层的OS进行映射;最后,还有memory-buffer;

   

    Bufferallocationwrapping

    NIO中的读写之前,你需要用一个buffer,为了创建buffer,你需要先allocation这个buffer。为了达成这个目的,我们需要用一个静态方法allocate();

    ByteBuffer buffer = ByteBuffer.allocation(1024);

    也可以把一个存在的array转变为一个buffer,例如:

 

-------------------------------------------------------------------

    byte array[] = new byte[1024];

    ByteBuffer buffer = ByteBuffer.wrap(array);

eg: CreateArrayBuffer.java

     public class CreateArrayBuffer

{

  static public void main( String args[] ) throws Exception {

    byte array[] = new byte[1024];

 

    ByteBuffer buffer = ByteBuffer.wrap( array );

 

    buffer.put( (byte)'a' );

    buffer.put( (byte)'b' );

    buffer.put( (byte)'c' );

 

    buffer.flip();

 

    System.out.println( (char)buffer.get() );

    System.out.println( (char)buffer.get() );

    System.out.println( (char)buffer.get() );

  }

}

 

-------------------------------------------------------------------

 

 

    Bufferslicing:

    slice()方法为一个存在的buffer对象创建了一个sub-buffer;也即:它创建了一个新的和原来buffer共享部分数据的buffer;

    slice()是源buffer的一个子buffer,他们共享底层的数组;slice buffer是一个很棒的抽象特性,当你需要处理buffer的一部分时,你可以使用slice buffer,而不需要传入起止位置的参数;slice buffer就像是buffer的一个窗口;

 

    buffer.position( 3 );

    buffer.limit( 7 );

 

    ByteBuffer slice = buffer.slice();

 

    for (int i=0; i<slice.capacity(); ++i) {

      byte b = slice.get( i );

      b *= 11;

      slice.put( i, b );

    }

 

    buffer.position( 0 );

    buffer.limit( buffer.capacity() );//buffer.clear();

 

    while (buffer.remaining()>0) {

      System.out.println( buffer.get() );

    }

--------------------------------------------------------------------

   

只读buffer:

    调用bufferasReadOnlyBuffer();将创建一个新的只读的buffer对象;

    无法把一个只读的buffer对象转为可写的buffer对象;

    直接和间接的buffers对象:

      

        direct buffer是一种被特定的方式allocatebuffer,可以提升I/O的速度;

       direct buffer的定义可以是独立的,jdk文档中关于direct buffer的说明如下:

       Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer's content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system's native I/O operations.

              你还可以用memory-mapped files的方式来使用direct buffer

       定义code:

      

        public class FastCopyFile

{

  static public void main( String args[] ) throws Exception {

    if (args.length<2) {

      System.err.println( "Usage: java FastCopyFile infile outfile" );

      System.exit( 1 );

    }

 

    String infile = args[0];

    String outfile = args[1];

 

    FileInputStream fin = new FileInputStream( infile );

    FileOutputStream fout = new FileOutputStream( outfile );

 

    FileChannel fcin = fin.getChannel();

    FileChannel fcout = fout.getChannel();

 

    ByteBuffer buffer = ByteBuffer.allocateDirect( 1024 );

 

    while (true) {

      buffer.clear();

 

      int r = fcin.read( buffer );

 

      if (r==-1) {

        break;

      }

 

      buffer.flip();

 

      fcout.write( buffer );

    }

  }

}

---------------------------------------------------------------------

    Memory-mapped file I/O

       Memory-mapped file I/O 是一种比I/O stream channel-based-I/O还要快得多的处理读写的方式;

       Memory-mapped file I/O的实现,是通过把file中的数据处理地看起来好像memory array中的内容的方式;实际上,Memory-mapped file并没有把整个文件一次性的读进memory,它只把实际读写的部分map到内存中;

       现代的OS在需要的时候,通过把部分的文件和部分的memory相映射来实现文件系统;javamemory-mapping系统仅仅在需要的情况下为底层的os提供访问权限;

       memory-mapping files中进行写动作时危险的;

   

   

    将一个文件映射到memory中的方法:

   

    -----------------------------------------------------------------

       MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE,0,1024);

       把文件中0~1024个字节映射到memory;   

 

       public class UseMappedFile

{

  static private final int start = 0;

  static private final int size = 1024;

 

  static public void main( String args[] ) throws Exception {

    RandomAccessFile raf = new RandomAccessFile( "usemappedfile.txt", "rw" );

    FileChannel fc = raf.getChannel();

 

    MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE,

      start, size );

 

    mbb.put( 0, (byte)97 );

    mbb.put( 1023, (byte)122 );

 

    raf.close();

  }

}

 

    -----------------------------------------------------------------

      

       map的方法会返回一个MappedByteBuffer的对象,MappedByteBufferByteBuffer的子集;你尽可以使用能够在buffer上使用的一切方法对buffer进行操作,同时,os会在你需要的时候在底层替你照顾好mapping的动作;

 

    

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值