Interpretation of NIO's Buffer

Table of contents

Introduction to Buffer

 Basic usage of Buffer

Steps for usage

Example using Buffer

 Example using IntBuffer

Buffer capacity, position and limit

capacity

position

limit

Buffer type

Buffer allocation and reading and writing data 

Buffer allocation

Write data to Buffer

flip() method 

Read data from Buffer

 Several methods of Buffer

rewind() method

clear() and compact() methods

mark() and reset() methods

buffer operation

buffer fragmentation

read-only buffer 

direct buffer 

memory-mapped file I/O

ByteBuffer size allocation


Introduction to Buffer

Buffer in Java NIO is used to interact with NIO channels. Data is read from the channel into the buffer and written from the buffer into the channel.

 A buffer is essentially a block of memory that data can be written to and then read from. This piece of memory is packaged as a NIO Buffer object, and a set of methods are provided for easy access to this piece of memory. The buffer is actually a container object, more directly, it is actually an array . In the NIO library, all data is processed by the buffer. When data is read, it is directly read into the buffer; when data is written, it is also written into the buffer; whenever data in NIO is accessed, it is put into the buffer. In a stream-oriented I/O system, all data is directly written or read directly into the Stream object.

In NIO, all buffer types inherit from the abstract class Buffer, and the most commonly used one is ByteBuffer. For the basic types in Java, there is basically a specific Buffer type corresponding to them. The inheritance relationship between them is shown in the figure below Show:

 Basic usage of Buffer

Steps for usage

1. Use Buffer to read and write data, generally follow the following four steps:

(1) Write data to Buffer

(2) Call the flip() method

(3) Read data from Buffer

(4) Call the clear() method or the compact() method

When writing data to the buffer, the buffer will record how much data is written. Once the data is to be read, the Buffer needs to be switched from write mode to read mode through the flip() method. In read mode, all data previously written to the buffer can be read. Once all the data has been read, the buffer needs to be cleared so it can be written again. There are two ways to clear the buffer: call the clear() or compact() method. The clear() method will clear the entire buffer. The compact() method will only clear the data that has already been read. Any unread data is moved to the beginning of the buffer, and newly written data is placed after the unread data in the buffer.

Example using Buffer

  @Test
    public void testConect2() throws IOException {
        RandomAccessFile aFile = new RandomAccessFile("d:\\atguigu/01.txt", "rw");
        FileChannel inChannel = aFile.getChannel();

        //create buffer with capacity of 48 bytes
        ByteBuffer buf = ByteBuffer.allocate(48);
        int bytesRead = inChannel.read(buf); //read into buffer. 
        while (bytesRead != -1) {
            buf.flip(); //make buffer ready for read
            while(buf.hasRemaining()){
                System.out.print((char) buf.get()); // read 1 byte at a time
            }buf.clear(); //make buffer ready for writing
            bytesRead = inChannel.read(buf);
        }
        aFile.close();
    }

 Example using IntBuffer

     @Test
    public void testConect3() throws IOException {
        // 分配新的 int 缓冲区,参数为缓冲区容量
        // 新缓冲区的当前位置将为零,其界限(限制位置)将为其容量。
        // 它将具有一个底层实现数组,其数组偏移量将为零。
        IntBuffer buffer = IntBuffer.allocate(8);
        for (int i = 0; i < buffer.capacity(); ++i) {
            int j = 2 * (i + 1);
        // 将给定整数写入此缓冲区的当前位置,当前位置递增
            buffer.put(j);
        }
        // 重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为 0
        buffer.flip();
        // 查看在当前位置和限制位置之间是否有元素
        while (buffer.hasRemaining()) {
        // 读取此缓冲区当前位置的整数,然后当前位置递增
            int j = buffer.get();
            System.out.print(j + " ");
        }
    }

Buffer capacity, position and limit

In order to understand how a Buffer works, you need to be familiar with its three properties:

- Capacity

- Position

- limit

The meaning of position and limit depends on whether the Buffer is in read mode or write mode. No matter what mode the Buffer is in, the meaning of capacity is always the same.

Here is an explanation about capacity, position and limit in read and write mode:

capacity

As a memory block, Buffer has a fixed size value, also called "capacity". You can only write capacity bytes, long, char and other types into it. Once the Buffer is full, it needs to be cleared (by reading or clearing the data) before continuing to write data into it.

position

1) When writing data into the Buffer, position indicates the current position of the written data, and the initial value of position is 0. When a byte, long, etc. data is written to the Buffer, the position will move down to the next Buffer unit where the data can be inserted. The position can be up to capacity - 1 (because the initial value of position is 0).

2) When reading data into the Buffer, position indicates the current position of the read data. For example, when position=2, it means that 3 bytes have been read, or the reading starts from the third byte. When switching to the read mode through ByteBuffer.flip(), the position will be reset to 0. When the Buffer reads data from the position, the position will move down to the next data Buffer unit that can be read.

limit

1) When writing data, limit indicates how much data can be written to the Buffer at most. In write mode, limit is equal to the capacity of Buffer.

2) When reading data, limit indicates how much readable data (not null data) is in the Buffer, so all data written before can be read (limit is set to the amount of written data, this value is in write mode position).

Buffer type

- ByteBuffer

- MappedByteBuffer

- CharBuffer

- DoubleBuffer

- FloatBuffer

- IntBuffer

- LongBuffer

- ShortBuffer

These Buffer types represent different data types. In other words, the bytes in the buffer can be manipulated by char, short, int, long, float or double types.

Buffer allocation and reading and writing data 

Buffer allocation

To get a Buffer object, you must first allocate it. Each Buffer class has an allocate method. Below is an example of a ByteBuffer that allocates a capacity of 48 bytes.

ByteBuffer buf = ByteBuffer.allocate(48);

 This is allocating a CharBuffer that can store 1024 characters:

CharBuffer buf = CharBuffer.allocate(1024);

Write data to Buffer

There are two ways to write data to Buffer:

(1) Write from Channel to Buffer.

(2) Write to Buffer through the put() method of Buffer.

Example of writing from Channel to Buffer:

int bytesRead = inChannel.read(buf); //read into buffer.

An example of writing a Buffer through the put method:

buf.put(127);

There are many versions of the put method, allowing you to write data into the Buffer in different ways. For example, write to a specified location, or write a byte array to Buffer

flip() method 

The flip method switches the Buffer from write mode to read mode. Calling the flip() method will set the position back to 0 and set the limit to the value of the previous position. In other words, position is now used to mark the reading position, and limit indicates how many bytes, chars, etc. have been written before (how many bytes, chars, etc. can be read now).

Read data from Buffer

There are two ways to read data from Buffer:

(1) Read data from Buffer to Channel.

(2) Use the get() method to read data from the Buffer.

An example of reading data from Buffer to Channel:

//read from buffer into channel. 
int bytesWritten = inChannel.write(buf);

 An example of using the get() method to read data from Buffer

byte aByte = buf.get();

There are many versions of the get method, allowing you to read data from the Buffer in different ways. For example, read from the specified
position, or read data from Buffer to byte array. 

 Several methods of Buffer

rewind() method

Buffer.rewind() sets the position back to 0, so you can reread all the data in the Buffer. limit remains unchanged, and still indicates how many elements (byte, char, etc.) can be read from the Buffer.

clear() and compact() methods

Once the data in the Buffer is read, the Buffer needs to be ready to be written again. This can be done with the clear() or compact() methods.

If the clear() method is called, position will be set back to 0 and limit will be set to the value of capacity. In other words, the Buffer is emptied. The data in the Buffer is not cleared, but these marks tell us where to start writing data into the Buffer.

If there is some unread data in the Buffer, call the clear() method, and the data will be "forgotten", which means that there is no longer any mark that will tell you which data has been read and which has not.

If there are still unread data in the Buffer, and these data are needed later, but you want to write some data first, then use the compact() method.

The compact() method copies all unread data to the beginning of the Buffer. Then set the position to just after the last unread element. The limit attribute is still set to capacity like the clear() method. Now the Buffer is ready to write data, but will not overwrite unread data.

mark() and reset() methods

A specific position in the Buffer can be marked by calling the Buffer.mark() method. It can be restored to this position later by calling the Buffer.reset() method. For example:

buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing. 
buffer.reset(); //set position back to mark.

buffer operation

buffer fragmentation

In NIO, in addition to allocating or wrapping a buffer object, you can also create a sub-buffer based on the existing buffer object, that is, cut out a slice on the existing buffer as a new buffer, but The existing buffer and the created sub-buffer share data at the underlying array level, that is to say, the sub-buffer is equivalent to a view window of the existing buffer. Call the slice() method to create a subbuffer.

    @Test
    public void testConect3() throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        // 缓冲区中的数据 0-9
        for (int i = 0; i < buffer.capacity(); ++i) {
            buffer.put((byte) i);
        }
        // 创建子缓冲区
        buffer.position(3);
        buffer.limit(7);
        ByteBuffer slice = buffer.slice();
        // 改变子缓冲区的内容
        for (int i = 0; i < slice.capacity(); ++i) {
            byte b = slice.get(i);
            b *= 10;
            slice.put(i, b);
        }
        buffer.position(0);
        buffer.limit(buffer.capacity());
        while (buffer.remaining() > 0) {
            System.out.print(buffer.get()+" ");
        }
    }

read-only buffer 

Read-only buffers are very simple, you can read them, but you cannot write to them . You can convert any regular buffer into a read-only buffer by calling the buffer's asReadOnlyBuffer() method. This method returns a buffer that is exactly the same as the original buffer and shares data with the original buffer, except that it is only read. If the content of the original buffer changes, the content of the read-only buffer also changes:

    @Test
    public void testConect4() throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        // 缓冲区中的数据 0-9
        for (int i = 0; i < buffer.capacity(); ++i) {
            buffer.put((byte) i);
        }

        // 创建只读缓冲区
        ByteBuffer readonly = buffer.asReadOnlyBuffer();
        // 改变原缓冲区的内容
        for (int i = 0; i < buffer.capacity(); ++i) {

            byte b = buffer.get(i);
            b *= 10;
            buffer.put(i, b);
        }
        readonly.position(0);
        readonly.limit(buffer.capacity());
        // 只读缓冲区的内容也随之改变
        while (readonly.remaining() > 0) {
            System.out.println(readonly.get());
        }
    }

If you try to modify the content of the read-only buffer, a ReadOnlyBufferException will be reported. Read-only buffers are useful for protecting data. When passing a buffer to a method of an object, there is no way of knowing whether the method will modify the data in the buffer. Creating a read-only buffer ensures that the buffer will not be modified. You can only convert regular buffers to read-only buffers, not convert read-only buffers to writable buffers.

direct buffer 

A direct buffer is a buffer that allocates memory in a special way to speed up I/O. The description in the JDK documentation is: Given a direct byte buffer, the Java virtual machine will do its best to directly allocate it Perform native I/O operations. That is, it tries to avoid copying the contents of the buffer to or from an intermediate buffer before (or after) each call to the underlying operating system's native I/O operation . To allocate a direct buffer, you need to call the allocateDirect() method instead of the allocate() method, which is no different from a normal buffer.

Copy file example:

    @Test
    public void testConect5() throws IOException {
        String infile = "d:\\atguigu\\01.txt";
        FileInputStream fin = new FileInputStream(infile);
        FileChannel fcin = fin.getChannel();
        String outfile = String.format("d:\\atguigu\\02.txt");
        FileOutputStream fout = new FileOutputStream(outfile);
        FileChannel fcout = fout.getChannel();
        // 使用 allocateDirect,而不是 allocate
        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 is a method of reading and writing file data that can be much faster than regular stream-based or channel-based I/O. Memory-mapped file I/O is done by making the data in the file appear as the contents of a memory array, which at first sounds like nothing more than reading the entire file into memory, but it is not. In general, only the portion of the file that is actually read or written is mapped into memory.

static private final int start = 0;
static private final int size = 1024;
static public void main(String args[]) throws Exception {
    RandomAccessFile raf = new RandomAccessFile("d:\\atguigu\\01.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();
}

ByteBuffer size allocation

Guess you like

Origin blog.csdn.net/m0_62436868/article/details/130851697