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

Wesley13
• 阅读 875

深入 Buffer :

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

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记录了你已经写入数组的数据量.更准确的说,是指向数组中下一个数据将要放置的位置。所以,如果你读入3个byte,buffer的position的值为3(数组的第四个元素的位置);

同时,写入数据时,postion记录了你已经写过的数据量,确切地说,是指向数组中下一个将写的数据的位置.例如,如果你已经写了5个byte,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;

读过程:

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

再读取2个数据:

limit不变;position+=2;

写过程:

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

limit = position;

position = 0;

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

limit不变,position=4;

第二次读取,读1数据;

limit不变,position+=1;

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

buffer的访问方法:

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

get的4个相关的方法:

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运行时异常;

put的5个相关方法:

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);

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

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

Buffer中使用put和get方法的例子:

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

不同Buffer的get()和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()封装了大部分的操作细节,所以操作起来非常方便,而clear和flip分别对应buffer的读操作和写操作;

更多的Buffer细节:

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

Buffer的allocation和wrapping:

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() ); 

  } 

}

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

Buffer的slicing:

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:

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

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

直接和间接的buffers对象:

direct buffer是一种被特定的方式allocate的buffer,可以提升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 

{ 
  public static 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相映射来实现文件系统;java的memory-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的对象,MappedByteBuffer是ByteBuffer的子集;你尽可以使用能够在buffer上使用的一切方法对buffer进行操作,同时,os会在你需要的时候在底层替你照顾好mapping的动作;

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java的NIO
java的NIO        java的NIO主要有3个特性Channel、buffer、selector来保证I/O高可复用性,其中最重要的是buffer和selector操作。详细教材查看jakobjenkov教材:http://tutorials.jenkov.com/javanio/index.html(https://www
Wesley13 Wesley13
3年前
java NIO中的buffer和channel
缓冲区(Buffer):一,在JavaNIO中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据根据数据类型不同(boolean除外),提供了相应类型的缓冲区:ByteBufferCharBufferShortBufferIntBufferLongBufferFloatBufferDoubleBuffer上述缓冲区的管理方式几乎一致,
Wesley13 Wesley13
3年前
Java NIO —— Buffer(缓冲区)
Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区。注意:Buffer是非线程安全类。缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIOBuffer对象,并提供了一组方法,用来方便的访问该块内存。NIO有以下几种Buffer类型:ByteBufferMappedByteBuff
Stella981 Stella981
3年前
NIO之Buffer的clear()、rewind()、flip()方法的区别
Java的NIO中有关Buffer的几种常用方法比如clear,rewind和flip到底有哪些区别。下面给大家这三种方法的源码,方便大家记忆。clear()方法用于写模式,其作用为情况Buffer中的内容,所谓清空是指写上限与Buffer的真实容量相同,即limitcapacity,同时将当前写位置置为最前端下标为0处。代码如下:1.pu
Wesley13 Wesley13
3年前
Mysql设置sort_buffer_size
sort\_buffer\_sizemysqlshowvariableslike‘%sort\_buffer\_size%’; ——————————— |Variable\_name|Value| ——————————— |innodb\_sort\_buffer\_size|1048576
Stella981 Stella981
3年前
NIO的Buffer&Channel&Selector
java的NIO和AIO(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fwww.cnblogs.com%2Fwadeluffy%2Fp%2F5855362.html)
Wesley13 Wesley13
3年前
Java NIO之Buffer的使用
!(https://oscimg.oschina.net/oscnet/b33fedf9b7a4c9aa36e8feb17ce3cef8dd2.jpg)作者 |bmilk来源| cnb
Wesley13 Wesley13
3年前
Java IO之NIO原理解析以及代码演示
一、JavaNIO几个核心部分ChannelBufferSelector二、IO和NIO的区别IO基于流(Streamoriented),而NIO基于Buffer(Bufferoriented)在一般的JavaIO操作中,我们以流式的方式顺序地从