“This is the 14th day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

Introduction of Buffer

Buffers in Java NIO are used to interact with NIO channels. Data is read by the channel to the buffer and written from the buffer to the channel.

A buffer is essentially a block of memory from which data can be written and then read. The memory is wrapped around the NIO Buffer object and provides a set of methods for easy access to the memory. Buffer century a container object is, more directly, an array, and in the NIO library, all data is handled in a buffer. When reading data, it reads directly into the buffer; When data is written, it is also written to the buffer; Any time you access data in NIO, you’re putting it in a buffer. In a stream-oriented I/O system, all data is written directly or read directly into a Stream object.

In NIO, all Buffer types are derived from the abstract Buffer class, the most commonly used being ByteBuffer. For the basic type of Java Chinese fabric, there is a specific Buffer type corresponding to the amount. The inheritance relationship between them is shown in the figure below:

The basic method of Buffer

1. Using 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 clear() or Compact ()

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

2. Examples of using buffers

@Test
public void buffer01(a) throws IOException {
    // FileChannel
    String pathName = "/Users/zhengsh/sourcecode.io/zhengsh-vvip/nio/src/main/resources/01.txt";
    RandomAccessFile accessFile =
        new RandomAccessFile(pathName, "rw");
    FileChannel channel = accessFile.getChannel();

    // Create buffer, size
    ByteBuffer buffer = ByteBuffer.allocate(1024);

    / / read
    int bytesRead = channel.read(buffer);
    while(bytesRead ! = -1) {
        / / read mode
        buffer.flip();

        while (buffer.hasRemaining()) {
            System.out.println((char) buffer.get());
        }
        buffer.clear();

        channel.read(buffer);
    }

    accessFile.close();
}


@Test
public void buffer02(a) {

    / / create a buffer
    IntBuffer buffer = IntBuffer.allocate(8);
    for (int i = 0; i < buffer.capacity(); i++) {
        int j = 2 * (i + 1);
        buffer.put(j);
    }

    // Reset the buffer
    buffer.flip();

    while (buffer.hasRemaining()) {
        int value = buffer.get();
        System.out.println(value + ""); }}Copy the code

Buffer capactity, Posittion, and limit

To understand how Buffer works, you need to be familiar with its three properties:

  • capacity
  • ponstition
  • limit

The meaning of position and limit depends on whether the Buffer is in read or write mode. The meaning of a capactity is always the same regardless of the buffer’s mode.

Here’s a note about capacity, Postition, and limit read modes:

(1) capactiy

As a block of memory, a Buffer has a fixed size value, also called a “capactiy”. You can only write capacity bytes in it

Long, char, and other types. Once the buffer is full, it needs to be emptied (either by reading or clearing data) in order to continue writing data.

(2) postition

1) When data is written to Bufer, position represents the current position of written data, and the initial value of position is 0. When a byte,long, etc. is written to the buffer, position is moved down to the buffer of the next pluggable element. Position can be a maximum of capacity-1 (since position starts at 0)

2) When data is read into the Buffer, position indicates the current position of the read data. For example, position = 2 indicates that 3 bytes have been read or the data has been read since the third byte. Switching to read mode by bytebuffer.flip () resets position to 0, and when Buffer reads data from position, position moves down to the next Buffer cell that can be read.

(3) limit

1) When writing data, limit indicates the maximum number of data that can be written to Buffer. In write mode, limit is equal to the capactiy of Buffer

2) When reading data, limit indicates how much data is readable (not null data) in the Buffer, and therefore can read all data previously written (Limit is set to the number of data that has been written, which in write mode is position)

The type of the Buffer

Java NIO has the following Buffer types

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

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

Buffer allocates and writes data

1. Buffer allocation

To get a Buffer object, you first allocate it. Each Buffer class has an allocate method.

Here is an example of a ByteBuffer that allocates 48 bytes of capactiy.

ByteBuffer buf = ByteBuffer.alloacte(48);
Copy the code

This is to allocate a CharBuffer that can store 1024 characters:

ByteBuffer buf = ByteBuffer.alloacte(1024);
Copy the code

2. 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 byteRead = channel.read(buf); // read into buffer
Copy the code

Example of writing a buffer using the put method:

buf.put(100);
Copy the code

There are many versions of the put method that allow you to write data to buffer in different ways, for example, to a specified location, or to write an array of bytes to buffer.

Flip () method

The flip method switches Buffer from write mode to read mode. Calling the flip() method sets position to 0 and limt to the value of the previous position. In other words, position is now used to mark the read position, and limit indicates how many bytes, char, etc., were previously written.

Read data from Buffer

Read data from a Buffer into a Channel:

(1) Read data from Buffer to Channel

(2) Read data from Buffer using get method

Example reading data from Buffer to Channnel:

// read form buffer into channel 
int bytesWritten = inChannel.write(buf);
Copy the code

Examples of reading data from a Buffer using the get() method:

byte aByte = buf.get();
Copy the code

There are many versions of the GET method that allow you to read data from Buffer in different ways. For example, reading from a specified position, or reading from a Buffer into an array of bytes.

Buffer several methods

1. Rewind () method

Buffer.rewind() returns position to 0, so you can re-read all the data in Buffer. Limit remains the same and still indicates how many elements can be read from Buffer (byte, char, etc.).

2. Clear () and Compact () methods

Once the Buffer has been read, it needs to be ready to be written again. This can be done using either the clear() or compact() methods

If the cleanr () method is called, position is set to 0 and limit is set to the value of capactiy. In other words, the Buffer is cleared. The data in the Buffer is not cleared, just the number of marks where we start writing data to the Buffer.

If there is some unread data in the Buffer, call the clear() method and the data will be “forgotten”, meaning there are no longer any flags telling you which data has been read and which has not.

If you still have unread data in the Buffer and need it later, but want to write it first, use the compact() method.

The compact() method copies all unread data to the start of the Buffer. Position is then set directly after the last unread element. The limit property is still the same as the clear() method. Set to capacity. The Buffer is now ready to write data, but will not overwrite unread data.

3. Mark () and reset() methods

A specific position in a Buffer can be marked by calling the buffer.mark () method. You can then restore the position by calling buffer.reset (). For example:

buffer.mark();



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

Buffer operation

1. Buffer fragmentation

In NIO besides can be allocated or packing a buffer object, can also be more existing buffer objects to create a child buffer, cut out a piece of the existing buffer as a new buffer, but existing buffer and create an array of a child of the buffer in the underlying level is the data sharing, that is to say, The child buffer is essentially a view window of the existing buffer. A child buffer is created by calling the slice() method.

// Buffer fragments
@Test
public void b01(a) {
    ByteBuffer buffer = ByteBuffer.allocate(10);
    // Add data
    for (int i = 0; i < buffer.capacity(); i++) {
        buffer.put((byte) i);
    }

    // Create a subbuffer
    buffer.position(3);
    buffer.limit(7);
    ByteBuffer slice = buffer.slice();

    // Change the contents of the child buffer
    for (int i = 0; i < slice.capacity(); i++) {
        byte b = slice.get(i);
        b *= 10;
        slice.put(i, b);
    }

    / / reset
    buffer.position(0);
    buffer.limit(buffer.capacity());

    while (buffer.remaining() > 0) { System.out.println(buffer.get()); }}Copy the code

The following output is displayed:

2. Read-only buffer

Read-only buffers are very simple; you can read them, but you can’t write to them. Any regular buffer can be converted to a read-only buffer by calling the buffer’s asReadOnlyBufer() method, which returns an identical buffer and shares data with the original buffer, except that it is read-only. If the contents of the original buffer change, the contents of the read-only buffer change accordingly:

// Read-only buffer
@Test
public void b02(a) {
    ByteBuffer buffer = ByteBuffer.allocate(10);

    // Add data
    for (int i = 0; i < buffer.capacity(); i++) {
        buffer.put((byte) i);
    }

    // Create a read-only buffer
    ByteBuffer readOnlyBuf = buffer.asReadOnlyBuffer();
    for (int i = 0; i < buffer.capacity(); i++) {
        byte b = buffer.get(i);
        b *= 10;
        buffer.put(i, b);
    }

    readOnlyBuf.position(0);
    readOnlyBuf.limit(readOnlyBuf.capacity());

    while (readOnlyBuf.remaining() > 0) { System.out.println(readOnlyBuf.get()); }}Copy the code

3. Direct buffer

Direct buffer A buffer that allocates memory in a special way to speed up I/O. The JDK documentation describes it as: Given a direct byte buffer, the Java virtual machine will do its best to try to avoid copying the contents of the buffer into or from an intermediate buffer before (or after) performing native I/O operations directly on it. To allocate a direct buffer, you call the allocatieDirect() method instead of the alloacte() method, which is used in the same way as normal buffers.

// Direct buffer, file copy
@Test
public void b03(a) throws IOException {
    String filePath = "/xx/01.txt";
    FileInputStream inputStream = new FileInputStream(filePath);
    FileChannel fileInChannel = inputStream.getChannel();

    String outPath = "/xx/02.txt";
    FileOutputStream outputStream = new FileOutputStream(outPath);
    FileChannel fileOutChannel1 = outputStream.getChannel();

    // Use allocateDirect instead of allocate
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

    while (true) {
        buffer.clear();
        int r = fileInChannel.read(buffer);
        if (r == -1) {
            break;
        }
        buffer.flip();
        fileOutChannel1.write(buffer);
    }
    fileInChannel.close();
    fileOutChannel1.close();
}
Copy the code

4. 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 streaming or channel-based I/O. Memory-mapped I/O is done by making the data in a file appear as the contents of an in-memory array, which at first sounds like the master is just trying to read the entire file into the contents, but it’s not. Typically, only the parts of the file that are actually read or written are mapped to memory.

// Memory mapped file I/O
@Test
public void b04(a) throws IOException {
    String filePath = "/xxx/01.txt";
    RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "rw");
    FileChannel fileChannel = randomAccessFile.getChannel();
    MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0.1024);
    mappedByteBuffer.put(0, (byte) 97);
    mappedByteBuffer.put(1023, (byte) 122);
    fileChannel.close();
}
Copy the code