Java NIO Learning Series a: Buffer

  The previous three articles summarize the standard Java IO system, File, RandomAccessFile, I / O flow system for the I / O system inherited from its starting system, to have a clear range of the number of class I / O system understanding, and then combined with the use of some conventional I / O to deepen the mastery of standard I / O system, interested students can look at:

  << the Java the I / O system is a series of learning: File and RandomAccessFile >>

  << the Java the I / O system Learning Series II: Input and Output >>

  << the Java the I / O system learning series three: I / O stream typically use >>

  I'll start from the beginning of this article summarizes part of the NIO, Java NIO (note, NIO here actually called New IO) is used to replace the standard Java IO and Java network API, which provides a range of different standard way to handle IO API IO, as introduced JDK1.4, and its object is to improve the speed.

  The reason is because it is possible to improve the speed closer to the structure used in the operating system performs I / O by: channels and buffers. We can imagine it as a coal mine, the channel is a mineral seam (data) includes, but is delivered to the buffer deposits truck. Trucks loaded with coal and go, and then we get the coal from the truck. In other words, we do not have direct and channel interaction, but interaction and buffers, and the buffers to the delivery channel. Or obtaining data from the buffer channel or send data to the buffer.

   IO API standard, a byte stream and a character stream. And it is using Channel (channel) and Buffer (buffer) in the Java NIO, the data read from the buffer to the channel, or written from the buffer to the channel. Java NIO libraries are core components are:

  • Buffer
  • Channel
  • Selector

  In this article we will focus summarize relevant knowledge Buffer (later article will continue to introduce That Channel Selector), this paper will revolve around the following aspects:

  Buffer Introduction

  Buffer internal structure  

  Buffer's main API

  ByteBuffer

  Buffer type

  to sum up

 

1. Buffer Introduction

  Buffer general and Channel Java NIO is paired. Channel data can be read from the Buffer, or write data to the Channel. Buffer is actually a representative of a memory block, you can read from or write data entered data. The memory block is packed into a Buffer objects, and provides a series of methods makes the operation more convenient block of memory.

  To read and write data by Buffer typically comprises four steps:

  1. Write data to the Buffer;
  2. Call buffer.flip ();
  3. Reading data from Buffer;
  4. Call buffer.clear () or buffer.compact ();

  When writing data to the Buffer, Buffer can record the number of write data. When data is read from the Buffer it would require () method by calling flip Buffer switching from write mode to read mode. Once read all the data needs to be emptied Buffer, it is write again. You can do this by calling clear () or compact () method:

  • clear () method clears the entire Buffer;
  • compact () method you just clear the data has been read from the Buffer, data is not read Buffer moved to the starting position, the read data can not immediately write new data;

  The following is a simple example of the use, and reading the ByteBuffer FileChannel pom.xml files, byte by byte, and outputs:

public class BufferDemo {

    public static void main(String[] args) {
        try {
            RandomAccessFile raf = new RandomAccessFile("pom.xml","r");
            FileChannel channel = raf.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(48);
            int byteReaded = channel.read(buffer);
            while(byteReaded != -1) {
                buffer.flip();
                while(buffer.hasRemaining()) {
                    System.out.print((char)buffer.get());
                }
                buffer.clear();
                byteReaded = channel.read(buffer);
            }
            raf.close();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }    
}

 

2. Buffer internal structure

  Mentioned above, it encapsulates a Buffer memory block, and provides a series of such a method can easily manipulate the data in memory. As for how to manipulate? Buffer provides four indexes. Buffer To understand how, we need to talk about these indexes:

  • capacity (capacity);
  • position (location);
  • limit (limit);
  • mark (marker);

   The meaning depends on the position and limit Buffer is in what mode (read or write mode), capacity of the meaning and regardless of the mode, and the mark is just a mark can be set by the mark () method. FIG describes the meaning of the three attributes representing the read-write mode, explained in detail below:

2.1 Capacity

  Buffer memory representative of a block, which is determined size, also called "capacity." You can write various kinds of data such as byte, long, chars, etc. to the buffer, Buffer is filled when it needs to be emptied (data can be read or emptied by data) to continue after the data is written.

2.2 Position

  当往Buffer中写数据时,写入的地方就是所谓的position,其初始值为0,最大值为capacity-1。当往Buffer中写入一个byte或者long的数据时,position会前移以指向下一个即将被插入的位置。

  当从Buffer中读取数据时,读取数据的地方就是所谓的position。当执行flip将Buffer从写模式切换到读模式时,position会被重置为0。随着不断从Buffer读取数据,position也会不断后移指向下一个将被读取的数据。

2.3 Limit

  在写模式下,Buffer的limit是指能够往Buffer中写入多少数据,其值等于Buffer的capacity。

  在读模式下,Buffer的limit是指能够从Buffer读取多少数据出来。因此当从写模式切换到读模式下时,limit就被设置为写模式下的position的值(这很好理解,写了多少才能读到多少)。

 2.4 Mark

  mark其实就是一个标记,可以通过mark()方法设置,设置值为当前的position。

 

  下面是用于设置和复位索引以及查询它们值的方法:


 

  capacity()      返回缓冲区容量
  clear()      清空缓冲区,将position设置为0,limit设置为容量。我们可以调用此方法覆写缓冲区
  flip()       将limit设置为position,position设置为0。此方法用于准备从缓冲区读取已经写入的数据
  limit()        返回limit值
  limit(int lim)    设置limit值
  mark()       将mark设置为position
  position()     返回position值
  position(int pos)  设置position值
  remaining()    返回(limit - position)
  hasRemaining()  若有介于position和limit之间的元素,则返回true


 

3. Buffer的主要API

  除了如上和索引相关的方法之外,Buffer还提供了一些其他的方法用于写入、读取等操作。

3.1 给Buffer分配空间

  要获得一个Buffer对象就可以通过Buffer类的allocate()方法来实现,如下分别是分配一个48字节的ByteBuffer和1024字符的CharBuffer:

ByteBuffer buf = ByteBuffer.allocate(48);
CharBuffer buf = CharBuffer.allocate(1024);

3.2 往Buffer中写数据

  有两种方式往Buffer中写入数据:

  • 从Channel中往Buffer写数据;
  • 通过Buffer的put()方法写入数据;
int bytesRead = inChannel.read(buf); // read into buffer
buf.put(127);

  put()方法有多个重载版本,比如从指定位置写入数据,或写入字节数组等。

3.3 flip()

  flip()方法将Buffer从写模式切换到读模式。调用flip()方法会将position设为0,limit设为position之前的值。

3.4 从Buffer读数据

  也有两种方法从Buffer读取数据:

  • 从Buffer中读数据到Channel中;
  • 调用Buffer的get()方法读取数据;
int bytesWritten = inChannel.write(buf); // read from buffer into channel
byte aByte = buf.get();

3.5 rewind()

  rewind()方法将position设置为0,可以从头开始读数据。

3.6 clear()和compact()

  当从Buffer读取数据结束之后要将其切换回写模式,可以调用clear()、compact()这两个方法,两者之间的区别如下:

  调用clear(),会将position设为0,limit设为capacity,也就是说Buffer被清空了,但是里面的数据仍然存在,只是这时没有标记可以告诉你哪些数据是已读,哪些是未读。

  如果读取到一半需要写入数据,但是未读的数据稍后还需要读取,这时可以使用compact(),其会将所有未读取的数据复制到Buffer的前面,将position设置到这些数据后面,limit设置为capacity,所以此时是从未读的数据后面开始写入新的数据。

3.7 mark()和reset()

  调用mark()方法可以标志一个指定的位置(即设置mark值),之后调用reset()方法时position又会回到之前标记的位置。

 

4. ByteBuffer

   ByteBuffer是一个比较基础的缓冲器,继承自Buffer,是可以存储未加工字节的缓冲器,并且也是唯一直接与通道交互的缓冲器。可以通过ByteBuffer的allocate()方法来分配一个固定大小的ByteBuffer,并且其还有一个方法选择集,用于以原始的字节形式或基本类型输出和读取数据。但是,没办法输出或读取对象,即使是字符串对象也不行。这种处理虽然很低级,但却正好,因为这是大多数操作系统中更有效的映射方式。

  ByteBuffer也分为直接和非直接缓冲器,通过allocate()创建的就是非直接缓冲器,而通过allocateDirect()方法就可以创建出一个缓冲器直接缓冲器,这是一个与操作系统有更高耦合性的缓冲器,也就意味着它能够带来更高的速度,但是分配的开支也会更大。

  尽管ByteBuffer只能保存字节类型的数据,但是它具有可以从其所容纳的字节中产生出各种不同基本类型值的方法。下面的例子展示怎样使用这些方法来插入和抽取各种数值:

public class GetData {    
    private static final int BSIZE = 1024;
    public static void main(String[] args){
        ByteBuffer bb = ByteBuffer.allocate(BSIZE);
        int i = 0;
        while(i++ < bb.limit())
            if(bb.get() != 0)
                System.out.println("nonzero");
        System.out.println("i = " + i);
        bb.rewind();
        // store and read a char array:
        bb.asCharBuffer().put("Howdy!");
        char c;
        while((c = bb.getChar()) != 0)
            System.out.print(c + " ");
        System.out.println();
        bb.rewind();
        // store and read a short:
        bb.asShortBuffer().put((short)471142);
        System.out.println(bb.getShort());
        bb.rewind();
        // sotre and read an int:
        bb.asIntBuffer().put(99471142);
        System.out.println(bb.getInt());
        bb.rewind();
        // store and read a long:
        bb.asLongBuffer().put(99471142);
        System.out.println(bb.getLong());
        bb.rewind();
        // store and read a float:
        bb.asFloatBuffer().put(99471142);
        System.out.println(bb.getFloat());
        bb.rewind();
        // store and read a double:
        bb.asDoubleBuffer().put(99471142);
        System.out.println(bb.getDouble());
        bb.rewind();
    }
}

 

5. Buffer类型

  Java NIO中包含了如下几种Buffer:

  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

  这些Buffer类型代表着不同的数据类型,使得可以通过Buffer直接操作如char、short等类型的数据而不是字节数据。其中MappedByteBuffer略有不同,后面会专门总结。

  通过ByteBuffer我们只能往Buffer直接写入或者读取字节数组,但是通过对应类型的Buffer比如CharBuffer、DoubleBuffer等我们可以直接往Buffer写入char、double等类型的数据。或者利用ByteBuffer的asCharBuffer()、asShorBuffer()等方法获取其视图,然后再使用其put()方法即可直接写入基本数据类型,就像上面的例子。

  这就是视图缓冲器(view buffer)可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。ByteBuffer依然是实际存储数据的地方,“支持”着前面的视图,因此对视图的任何修改都会映射成为对ByteBuffer中数据的修改。这使得我们可以很方便地向ByteBuffer插入数据。视图还允许我们从ByteBuffer一次一个地(与ByteBuffer所支持的方式相同)或者成批地(通过放入数组中)读取基本类型值。在下面的例子中,通过IntBuffer操纵ByteBuffer中的int型数据:

public class IntBufferDemo {    
    private static final int BSIZE = 1024;
    public static void main(String[] args){
        ByteBuffer bb = ByteBuffer.allocate(BSIZE);
        IntBuffer ib = bb.asIntBuffer();
        // store an array of int:
        ib.put(new int[]{11,42,47,99,143,811,1016});
        // absolute location read and write:
        System.out.println(ib.get(3));
        ib.put(3,1811);
        // setting a new limit before rewinding the buffer.
        ib.flip();
        while(ib.hasRemaining()){
            int i = ib.get();
            System.out.println(i);
        }
    }
}

  上例中先用重载后的put()方法存储一个整数数组。接着get()和put()方法调用直接访问底层ByteBuffer中的某个整数位置。这些通过直接与ByteBuffer对话访问绝对位置的方式也同样适用于基本类型。

 

6. 总结

  本文简单总结了Java NIO(Java New IO),其目的在于提高速度。Java NIO类库中主要包括Buffer、Channel、Selector,本文主要总结了Buffer相关的知识点:

  • Buffer叫缓冲器,她是和Channel(通道)交互的,可以从channel中读数据到buffer中,或者从buffer往channel中写数据;
  • Buffer内部封装了一块内存,提供了一系列API使得可以方便地操作内存中的数据。其内部是通过capacity、position、limit、mark等变量来跟踪标记封装的数据的;
  • ByteBuffer是最基本的Buffer,是唯一可以直接与通道交互的缓冲器,其可以直接操纵字节数据或字节数组;
  • 除了ByteBuffer之外,Buffer还有许多别的类型如:MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer;
  • 虽然只有ByteBuffer能够直接和通道交互,但是可以从ByteBuffer获取多种不同的视图缓冲器,进而同时具备了直接操作基本数据类型和与通道交互的能力;

  基础知识的总结也许是比较枯燥的,但是如果你已经看到这里说明你很有耐心,如果觉得对你有帮助的话,不妨点个赞关注一下吧^_^

 

Guess you like

Origin www.cnblogs.com/volcano-liu/p/11001753.html