Detailed explanation, buffer in NIO

  • J3 - white start
  • Technology (NIO # buffer)

The emergence of NIO is to solve the shortcomings of traditional IO, and the buffer in the three components of NIO is one of the components to improve efficiency.

Buffer plays a very important role in NIO, because the data is placed in the buffer, and CRUD operations on the data are operations on the buffer, so whether the buffer operation is correct or not is directly related to the correctness of the final result.

Let's start to understand it!

1, Introduction to buffer class system

JDK1.4 abstracts an abstract class of buffer, which is a top-level class. There are seven direct subclasses below, as shown in the figure:

Among the eight basic data types in Java, there are seven types except boolean, and the most used are ByteBuffer and CharBuffer.

Abstract classes are templates. Subclasses work according to the specifications of the parent class. They can be implemented by the parent class or rewritten by themselves. So let's take a look at the Buffer class first.

2, Buffer class use

This class is abstract and cannot be instantiated directly, and its seven subclasses are also abstract. How can we use it?

Use the wrapper class returned by the static method provided by the subclass to implement: wrap, allocate, etc.

The API list of Buffer class is shown in the figure below:

For these API s, I will focus on the four properties of the marked red area, which can be said to be the core of the buffer.

// sign
private int mark = -1;
// position
private int position = 0;
// limit
private int limit;
// capacity
private int capacity;

2.1 four core attributes

The Buffer source code describes the size relationship of the four attributes:

0 <= mark <= position <= limit <= capacity

1) Capacity: capacity

This is easy to understand. It means the size of the buffer, that is, the number of elements that can be stored in the buffer. capacity cannot be negative and cannot be modified after definition.

int capacity() // Returns the size of this buffer

Take a look at the case:

@Test
public void test04() {
    byte[] b = new byte[]{1, 2, 3, 4, 5};
    char[] c = new char[]{'a', 'b', 'c', 'd', 'e'};
    ByteBuffer byteBuffer = ByteBuffer.wrap(b);
    CharBuffer charBuffer = CharBuffer.wrap(c);
    // Other types of buffer s are similar, so they will not be demonstrated one by one
    log.info("ByteBuffer Implementation type:{}", byteBuffer.getClass().getName());
    log.info("CharBuffer Implementation type:{}", charBuffer.getClass().getName());
    log.info("===========================");
    log.info("ByteBuffer capacity capacity: {}", byteBuffer.capacity());
    log.info("CharBuffer capacity capacity: {}", charBuffer.capacity());
}
// result
/*
ByteBuffer Implementation type: java.nio.HeapByteBuffer
CharBuffer Implementation type: java.nio.heapcarbuffer
===========================
ByteBuffer capacity: 5
CharBuffer capacity: 5
*/

It can be seen from the results that the implementation of Byte Buffer class is HeapByteBuffer, so other types of buffers should also have corresponding HeapXXXBuffer class implementation.

Continue to dig into the source code of wrap method

// 1)java.nio.ByteBuffer # wrap
public static ByteBuffer wrap(byte[] array) {
    return wrap(array, 0, array.length);
}
// 2)
public static ByteBuffer wrap(byte[] array,
                              int offset, int length)
{
    try {
        // Finally, a HeapByteBuffer implementation class is created
        return new HeapByteBuffer(array, offset, length);
    } catch (IllegalArgumentException x) {
        throw new IndexOutOfBoundsException();
    }
}

// 3)java.nio.HeapByteBuffer # HeapByteBuffer
HeapByteBuffer(byte[] buf, int off, int len) { // package-private
    // Call the parent class construction method to initialize the parameters
    super(-1, off, off + len, buf.length, buf, 0);
    /*
        hb = buf;
        offset = 0;
        */
}
// 4)java.nio.ByteBuffer # ByteBuffer
ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
           byte[] hb, int offset)
{
    super(mark, pos, lim, cap);
    // The passed in byte array is encapsulated inside
    this.hb = hb;
    this.offset = offset;
}

// 5)java.nio.Buffer # Buffer
Buffer(int mark, int pos, int lim, int cap) {       // package-private
    if (cap < 0)
        throw new IllegalArgumentException("Negative capacity: " + cap);
    this.capacity = cap;
    limit(lim);
    position(pos);
    if (mark >= 0) {
        if (mark > pos)
            throw new IllegalArgumentException("mark > position: ("
                                               + mark + " > " + pos + ")");
        this.mark = mark;
    }
}

It can be seen from the source code that the capacity of the buffer obtained by wrap is the length of the incoming array, that is, the value of capacity.

2) Limit: limit

A location in the restricted buffer is unreadable or writable. To put it bluntly, when you write or read data, you will stop subsequent read and write operations when you reach the subscript indicated by limit.

Limit cannot be greater than capacity or negative. If position is greater than limit, position will be set as the new limit.

The defined mark cannot be greater than the limit, otherwise the mark will be discarded.

The position and mark attributes that appear will be introduced later. See the following figure to understand the function of limit:

Take a case:

@Test
public void test05() {
    byte[] b = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    ByteBuffer byteBuffer = ByteBuffer.wrap(b);
    log.info("capacity capacity,{}", byteBuffer.capacity());
    log.info("limit limit,{}", byteBuffer.limit());
    log.info("Operational range:{}", byteBuffer.limit() - byteBuffer.position());
    log.info("Get actionable range data(Subscript 8),{}", byteBuffer.get(8));
    log.info("Limit subscript 7 will not be operable later");
    byteBuffer.limit(7);
    log.info("Get actionable range data(Subscript 5),{}", byteBuffer.get(5));
    log.info("Get inoperable range data(Subscript 8),{}", byteBuffer.get(8));
}

result:

Obviously, java.lang.IndexOutOfBoundsException (subscript out of bounds exception) will be reported when operating the position after the limit limit. Therefore, for the buffer, we can only operate the data between position and limit.

3) Position: position

This property indicates the next location to read. Similarly, position cannot be negative and cannot be greater than limit. If mark is defined and greater than position, mark will be discarded.

Small case:

@Test
public void test06() {
    char[] c = new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g'};
    CharBuffer charBuffer = CharBuffer.wrap(c);
    log.info("Currently start reading subscript:{}", charBuffer.position());
    log.info("Currently start reading subscript data:{}", charBuffer.get());
    log.info("Read data with subscript 5:{}", charBuffer.get(5));
    log.info("Next subscript to read:{}", charBuffer.position());
}

result

13:08:43.279 [main] INFO cn.baiqi.core.nio.TestByteBuffer - Currently starting reading subscript: 0
13:08:43.285 [main] INFO cn.baiqi.core.nio.TestByteBuffer - Currently start reading subscript data: a
13:08:43.285 [main] INFO cn.baiqi.core.nio.TestByteBuffer - Read data with subscript 5: f
13:08:43.285 [main] INFO cn.baiqi.core.nio.TestByteBuffer - Next subscript to read: 1

As can be seen from the case, the value of position is accumulated only when it is read one by one.

4) mark: mark

Mark a certain position of position. When the reset method is executed, the position of the original mark mark will change no matter where the position points to.

Therefore, position and limit should be greater than or equal to mark, otherwise mark will be invalid and cannot be negative.

Small case:

@Test
public void test02() {
    char[] c = new char[]{'a', 'b', 'c', 'd', 'e'};
    CharBuffer charBuffer = CharBuffer.wrap(c);
    log.info("capacity capacity = {}", charBuffer.capacity());
    log.info("Start limit limit = {}", charBuffer.limit());
    log.info("Start read position position = {}", charBuffer.position());
    log.info("set up position Location: 2");
    charBuffer.position(2);
    // Mark current position
    charBuffer.mark();
    log.info("I marked it mark Location of mark = {},position Location:{}", charBuffer.mark(), charBuffer.position());
    charBuffer.position(4);
    log.info("Take a few steps forward position = {}", charBuffer.position());
    // Return to marked position
    log.info("implement reset method");
    charBuffer.reset();
    log.info("Current read position position = {}", charBuffer.position());
    log.info("position Back to the beginning mark Location of");
}

result:

13:18:56.178 [main] INFO cn.baiqi.core.nio.TestByteBuffer - capacity capacity = 5
13:18:56.185 [main] INFO cn.baiqi.core.nio.TestByteBuffer - Start limit limit = 5
13:18:56.185 [main] INFO cn.baiqi.core.nio.TestByteBuffer - Start read position position = 0
13:18:56.185 [main] INFO cn.baiqi.core.nio.TestByteBuffer - set up position Location: 2
13:18:56.185 [main] INFO cn.baiqi.core.nio.TestByteBuffer - I marked it mark Location of mark = cde,position Location: 2
13:18:56.185 [main] INFO cn.baiqi.core.nio.TestByteBuffer - Take a few steps forward position = 4
13:18:56.186 [main] INFO cn.baiqi.core.nio.TestByteBuffer - implement reset method
13:18:56.186 [main] INFO cn.baiqi.core.nio.TestByteBuffer - Current read position position = 2

This mark mark mark just allows the program to repeatedly read a piece of data, just execute the reset method.

In buffer, basically all API methods are implemented based on these four attributes. The following will introduce some common methods and follow up the specific embodiment of these four attributes in the source code. However, if these four attributes have not formed an impression in your mind before, it is recommended to do it again.

3, Common methods

See the methods in the Buffer source code for details. Here are only a few introductions.

3.1 remaining()

Function: the size of the remaining space is to return the number of elements between position and limit.

Source code:

public final int remaining() {
    return limit - position;
}

Case:

@Test
public void remainingTests() {
    ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
    log.info("Current capacity:{}", byteBuffer.capacity());
    log.info("Current location:{}", byteBuffer.position());
    log.info("Operable space:{}", byteBuffer.remaining());
    log.info("Read two data");
    log.info("{},{}", byteBuffer.get(), byteBuffer.get());
    log.info("Remaining operable space:{}", byteBuffer.remaining());
}

result:

13:53:53.389 [main] INFO cn.baiqi.core.nio.BufferTest - Current capacity: 8
13:53:53.394 [main] INFO cn.baiqi.core.nio.BufferTest - Current location: 0
13:53:53.394 [main] INFO cn.baiqi.core.nio.BufferTest - Operable space: 8
13:53:53.394 [main] INFO cn.baiqi.core.nio.BufferTest - Read two data
13:53:53.395 [main] INFO cn.baiqi.core.nio.BufferTest - 1,2
13:53:53.395 [main] INFO cn.baiqi.core.nio.BufferTest - Remaining operable space: 6

3.2 hasRemaining()

Function: judge whether there are remaining elements in the current position and the restricted position.

Source code:

public final boolean hasRemaining() {
    return position < limit;
}

Case:

@Test
public void hasRemainingTests() {
    ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
    log.info("Operable:{}", byteBuffer.hasRemaining());
    log.info("set up position = limit");
    byteBuffer.position(byteBuffer.limit());
    log.info("Operable:{}", byteBuffer.hasRemaining());
}

result:

14:11:35.993 [main] INFO cn.baiqi.core.nio.BufferTest - Operable: true
14:11:35.999 [main] INFO cn.baiqi.core.nio.BufferTest - set up position = limit
14:11:36.000 [main] INFO cn.baiqi.core.nio.BufferTest - Operable: false

3.3 flip()

Function: reverse the buffer, which can be understood as changing from write to read.

Source code:

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

Case:

@Test
public void flipTests() {

    ByteBuffer byteBuffer = ByteBuffer.allocate(10);
    log.info("Write data to buffer");
    byteBuffer.put((byte) 1);
    byteBuffer.put((byte) 2);
    byteBuffer.put((byte) 3);
    byteBuffer.put((byte) 4);
    log.info("flip Before calling: position = {},limit = {}", byteBuffer.position(), byteBuffer.limit());
    log.info("Reverse buffer");
    byteBuffer.flip();
    log.info("flip After calling: position = {},limit = {}", byteBuffer.position(), byteBuffer.limit());
    log.info("Start reading data");
    log.info("Read:{}", byteBuffer.get());
}

result:

14:35:56.765 [main] INFO cn.baiqi.core.nio.BufferTest - Write data to buffer
14:35:56.769 [main] INFO cn.baiqi.core.nio.BufferTest - flip Before calling: position = 4,limit = 10
14:35:56.770 [main] INFO cn.baiqi.core.nio.BufferTest - Reverse buffer
14:35:56.770 [main] INFO cn.baiqi.core.nio.BufferTest - flip After calling: position = 0,limit = 4
14:35:56.770 [main] INFO cn.baiqi.core.nio.BufferTest - Start reading data
14:35:56.771 [main] INFO cn.baiqi.core.nio.BufferTest - Read: 1

3.4 rewind()

Function: rewinding the buffer is to discard the mark bit (set to - 1) and set position to 0. My understanding is to discard tags. As for repeatedly reading data, I prefer the flip method.

Source code:

public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}

Case:

@Test
public void rewindTests() {
    ByteBuffer byteBuffer = ByteBuffer.allocate(10);
    log.info("Write data to buffer");
    byteBuffer.put((byte) 1);
    byteBuffer.put((byte) 2);
    byteBuffer.put((byte) 3);
    byteBuffer.put((byte) 4);
    log.info("mark Mark current position");
    byteBuffer.mark();
    log.info("rewind Before calling: position = {},mark = {}", byteBuffer.position(), byteBuffer.mark());
    log.info("rewind Method pointing");
    byteBuffer.rewind();
    log.info("rewind After calling: position = {},mark = {}", byteBuffer.position(), byteBuffer.mark());
    log.info("Start reading data");
    log.info("Read:{}", byteBuffer.get());
}

result:

14:48:02.364 [main] INFO cn.baiqi.core.nio.BufferTest - Write data to buffer
14:48:02.368 [main] INFO cn.baiqi.core.nio.BufferTest - mark Mark current position
14:48:02.368 [main] INFO cn.baiqi.core.nio.BufferTest - rewind Before calling: position = 4,mark = java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
14:48:02.370 [main] INFO cn.baiqi.core.nio.BufferTest - rewind Method pointing
14:48:02.370 [main] INFO cn.baiqi.core.nio.BufferTest - rewind After calling: position = 0,mark = java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
14:48:02.370 [main] INFO cn.baiqi.core.nio.BufferTest - Start reading data
14:48:02.371 [main] INFO cn.baiqi.core.nio.BufferTest - Read: 1

Note: Although the data can be read, the subscripts with and without data are lost, which is unsafe for reading data. Therefore, personal understanding is not the function of repeated reading, and the flip method is more like the degree of repetition.

3.5 reset()

Function: point position to mark bit

Source code:

public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m;
    return this;
}

See the mark section in Section 2.1 for the case, which is very clear.

3.6 clear()

Function: clear the buffer, but only reset the values of position, limit and mark attributes. Because there are no specific attributes indicating that there is data in the buffer and there is no data, it can be indirectly understood as clearing the buffer.

Source code:

public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}

Case:

@Test
public void clearsTests() {
    ByteBuffer byteBuffer = ByteBuffer.allocate(10);
    log.info("Write data to buffer");
    byteBuffer.put((byte) 1);
    byteBuffer.put((byte) 2);
    byteBuffer.put((byte) 3);
    byteBuffer.put((byte) 4);
    log.info("Current buffer status:{}", byteBuffer);
    byteBuffer.clear();
    log.info("call clear Method, current buffer state:{}", byteBuffer);
}

result:

14:53:53.778 [main] INFO cn.baiqi.core.nio.BufferTest - Write data to buffer
14:53:53.783 [main] INFO cn.baiqi.core.nio.BufferTest - Current buffer status: java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
14:53:53.785 [main] INFO cn.baiqi.core.nio.BufferTest - call clear Method, current buffer state: java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]

4, ByteBuffer class uses

As its name implies, it provides a byte type buffer to manipulate data, and the other six are the same.

4.1 create ByteBuffer method

  • wrap method: creates a buffer of the specified content based on the passed in array
  • allocate method: create an empty buffer of the specified capacity based on the incoming capacity
  • allocateDirect method: the same as allocate, but the created buffer is a direct buffer.

Case:

public void test01() {
    ByteBuffer wrap = ByteBuffer.wrap(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9});
    ByteBuffer allocate = ByteBuffer.allocate(10);
    ByteBuffer allocateDirect = ByteBuffer.allocateDirect(10);
}

4.2 direct buffer and indirect buffer

The data flow direction of indirect buffer is: hard disk = > intermediate buffer of JVM = > buffer

Direct buffer data flow: hard disk = > buffer

The disadvantage of indirect buffer is that it greatly reduces the software's data throughput and improves the memory occupancy, but indirect buffer just solves this disadvantage.

4.3 implementation of ByteBuffer class

As we said earlier, ByteBuffer is also an abstract class, so there must be an implementation class below it. See the inheritance diagram of this class:

  • HeapByteBuffer: read / write byte indirect buffer.
  • HeapByteBufferR: read only byte indirect buffer.
  • DirectByteBuffer: read / write byte direct buffer.
  • Directbytebuffer R: read only byte direct buffer.

You should know how to operate ByteBuffer here! There are many APIs, but I have written some basic API methods through cases above. For more API methods, I suggest to directly look at the source code of ByteBuffer class. There are not many methods to introduce here (too many to introduce).

5, Finally

The above is all the content of this article. Among them, the four attributes in the Buffer class are the top priority. In fact, you can see from the Buffer class system source code that basically every method is implemented according to these four attributes, which can be seen everywhere.

For the subclasses of Buffer class, focus on two ByteBuffer and CharBuffer, which are used more in development. It is not too late to get familiar with the later several when they arrive.

Then the buffer content in NIO will come to an end. The following is the content related to Channel.

If we regard Buffer as water flow, then Channel is the Channel for transporting water, so the content related to Channel is also a key point in NIO, so it is also a key point.

Well, that's the end of today's content. Pay attention to me. I'll see you next time

Access or reference:

Gao Hongyan, NIO and Socket programming guide

  • Due to the blogger's lack of talent and learning, it is inevitable that there will be mistakes. If you find mistakes or prejudices, please leave a message to me and I will correct them.

  • If you think the article is good, your forwarding, sharing, likes and comments are my greatest encouragement.

  • Thank you for your reading. Welcome and thank you for your attention.

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

CSDN: J3 - white start

Nuggets: J3 white

Know: J3 white

This is a technology in general, but keen to share; Experience is still shallow, but the skin is thick enough; A programmer who is young and has a good face, but doesn't rely on talent.

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Keywords: Java Back-end

Added by polarbear66 on Mon, 08 Nov 2021 03:24:32 +0200