Java NIO – Buffer

Time:2021-10-14

structure

Buffer means “buffer”. In Java NiO, all data must pass through the buffer. The following figure shows the basic structure inside the buffer.

Java NIO - Buffer

It is actually an array with three pointers: position, limit and capacity.

capacity

capacityThe capacity for this array is immutable.

limit

limitIs the subscript of the first unreadable element in the buffer, that is, the data after limit cannot be read or written. Limit cannot be negative or greater than capacity.

The initial limit is the same as the capacity value.

position

positionSubscript indicating that the next element will be read or written. Position cannot be negative or greater than limit. Position is initially 0.

Class relation

Buffer is an abstract class. It has many sub abstract classes, corresponding to seven basic types of Java (exceptboolean)。 As shown below:

Java NIO - Buffer

withByteBufferFor example, it has two implementations, one isHeapByteBuffer, the other isDirectByteBuffer, respectively correspondingHeap memoryandDirect memory

Heap memory will allocate this object in the JVM heap, just like ordinary objects. Direct memory is also calledOut of heap memory, when using IO, we prefer to use direct memory.

Why is direct memory recommended? In fact, this is related to the garbage collection mechanism of the JVM. IO often occupies a large memory space. If it is allocated to the JVM heap, it will be considered as a large object, affecting the garbage collection efficiency of the JVM.

If the out of heap memory is full (reaching the limit of system memory), an oom exception will also be thrown.

initialization

What’s the use of buffer? The buffer is generally used in conjunction with the channel. When the channel reads data, it will read it into the buffer first, and when writing data, it will write it into the buffer first.

Here is how to use buffer.

Generally speaking, the second level class is used directly, such asByteBuffer。 They have two factory methodsallocateandallocateDirect, used to initialize and request memory. As mentioned earlier, direct memory is usually used when operating IO, so it is generally initialized as follows:

ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

Can useisDirect()Method to determine whether the current buffer object uses direct memory.

Write data

There are two main ways to write data to the buffer:

  • Write from channel to buffer
  • Write from array to buffer

Writing from the channel to the buffer uses the channelread(Buffer buffer)Method, and writing from the array to buffer mainly uses the bufferputmethod.

//Get the data in the channel and write it to the buffer

Let’s assume that the buffer requests 1024 bytes and the string occupies 16 bytes. The three pointers after writing data are like this:

  • position = 16
  • limit = 1024
  • capacity = 1024

Switch mode

Buffer is divided into read mode and write mode, which can be accessed throughflip()Method to convert the mode. In fact, looking at the source code of this method, it is found that the flip method only operates on three pointers.

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

The mark pointer is used for the reset () method. If the reset () method is called, the position will be reset to the mark position. If mark is not defined, calling the reset () method will throw an invalidmarkexception exception. Once mark is defined, it must not be negative and less than or equal to position.

The function of the mark () method is equivalent to “temporarily recording the position”, so that you can return to this position later through the reset () method.

After switching the mode, the three pointers become as follows:

  • position = 0
  • limit = 16
  • capacity = 1024

Read data

Corresponding to writing data, there are two ways to read data:

  • Read channel from buffer
  • Read array from buffer

Reading data will read the position of limit from position.

Example code:

//Read the data from the buffer and write it to the channel

Buffer is used hereremaining()method. This method tells us how many bytes we need to read. Method source code:

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

empty

Generally speaking, a channel uses a buffer, but the buffer can be reused, especially for some large IO transmission contents (such as files),clear()Andcompact()Method can reset the buffer. They have some small differences.

For the clear method, position is set back to 0 and limit is set to the value of capacity.

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

Generally speaking, there are more scenarios using the clear method.

Source code:

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

public ByteBuffer compact() {
    int pos = position();
    int lim = limit();
    assert (pos <= lim);
    int rem = (pos <= lim ? lim - pos : 0);
    try {
        UNSAFE.copyMemory(ix(pos), ix(0), (long)rem << 0);
    } finally {
        Reference.reachabilityFence(this);
    }
    position(rem);
    limit(capacity());
    discardMark();
    return this;
}

Buffer has other methods to operate the three pointers, but the frequency of use is not as high as the above methods, so this article will not introduce it in detail. Interested readers can take a look at the source code.

use

Here are the use case codes for reading and writing:

From string to channel:

ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

From channel to string:

ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

Complete sample code

Author: official account XY’s technology circle

Link:www.imooc.com/article/289086

Source: muke.com

This work adoptsCC agreement, reprint must indicate the author and the link to this article

Recommended Today

The selector returned by ngrx store createselector performs one-step debugging of fetching logic

Test source code: import { Component } from ‘@angular/core’; import { createSelector } from ‘@ngrx/store’; export interface State { counter1: number; counter2: number; } export const selectCounter1 = (state: State) => state.counter1; export const selectCounter2 = (state: State) => state.counter2; export const selectTotal = createSelector( selectCounter1, selectCounter2, (counter1, counter2) => counter1 + counter2 ); // […]