Série de notas Netty: Buffer do componente principal NIO

I. Visão geral

A essência do Buffer é, na verdade, um pedaço de memória, análogo ao ByteBuffer comum, que pode ser simplesmente entendido como um array de bytes. Java NIO encapsula essa memória em um objeto Buffer e fornece uma série de propriedades e métodos para facilitar a interação de dados entre o Buffer e o Canal.

Dois, uso

Vejamos a operação FileChannel:

try (RandomAccessFile accessFile =
                     new RandomAccessFile("/demo.txt", "rw");) {
            // 获取 FileChannel
            FileChannel channel = accessFile.getChannel();
            // Buffer 分配空间
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            // 从 channel 中读取数据到 buffer 中
            int readBytes = channel.read(buffer);
            System.out.println("读到 " + readBytes + " 字节");
            // 判断是否到文件结尾
            while (readBytes != -1) {
                buffer.flip();
                // 若 buffer 中还有数据
                while (buffer.hasRemaining()) {
                    System.out.println((char) buffer.get());
                }
                buffer.compact();
                readBytes = channel.read(buffer);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

No exemplo acima, existem as seguintes etapas para usar o Buffer:

1. Alocar espaço

  • Alocar (capacidade
    interna ) Buffer é uma classe abstrata.Cada classe de implementação de buffer fornece método estático alocar (capacidade interna) para ajudar a instanciar objetos Buffer rapidamente. Por exemplo, o ByteBuffer comum: public static ByteBuffer allocate (int capacity) {if (capacity <0) throw new IllegalArgumentException (); return new HeapByteBuffer (capacity, capacity);} Também pode ser visto pelo seu nome que o espaço alocado por este método é Com base na memória heap.
  • allocateDirect (int capacity)
    aloca espaço com base na memória fora do heap e instancia um objeto Buffer. public static ByteBuffer allocateDirect (int capacity) {return new DirectByteBuffer (capacity);}
  • wrap (byte [] array, int offset, int length)
    Cada classe de implementação do Buffer fornece um método wrap para envolver um array em uma instância do Buffer.

2. Leia os dados do canal para o buffer

  • Use o método channel.read (buffer)
  • Todas as
    implementações de Buffer que usam o método buffer.put () fornecem o método put () para colocar dados no Buffer.

3. Chame o método flip ()

4. Obtenha dados do Buffer

channel.write(buffer)
buffer.get()

5. Chame o método clear () ou compact ()

Qual é a função dos métodos flip (), clear (), compact () acima?

Três, análise do código-fonte

Além da matriz usada para armazenar dados, o Buffer tem várias propriedades importantes:

public abstract class Buffer {

    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;

    // Used only by direct buffers
    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
    long address;

    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;
        }
    }

    // ... 省略具体方法的代码
}

Série de notas Netty: Buffer do componente principal NIO

Com base nos quatro atributos acima, o Buffer completa as operações de leitura e gravação controlando suas posições. Analisamos a partir do uso do Buffer:

1. Alocar espaço

Supondo que o método ByteBuffer.allocate (10) seja chamado para alocar 10 bytes de espaço para o buffer, observe o código de alocação (capacidade interna):

public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
}

Ele chama o método de construção de HeapByteBuffer e define a capacidade e o limite como 10 e a posição como 0.

HeapByteBuffer(int cap, int lim) {            // package-private

        super(-1, 0, lim, cap, new byte[cap], 0);
        /*
        hb = new byte[cap];
        offset = 0;
        */
    }

O diagrama esquemático do buffer neste momento é o seguinte:

Série de notas Netty: Buffer do componente principal NIO

No estado inicial do buffer, a posição é 0 e o limite e a capacidade apontam para 9 .

2. Grave os dados no Buffer

Gravamos três bytes de dados no buffer e examinamos o método HeapByteBuffer # put (byte b), uma das implementações de ByteBuffer:

public ByteBuffer put(byte x) {

        hb[ix(nextPutIndex())] = x;
        return this;

}

protected int ix(int i) {
        return i + offset;
}

O método nextPutIndex () em ByteBuffer é usado para calcular o índice dos próximos dados gravados:

final int nextPutIndex() {                          // package-private
        if (position >= limit)
            throw new BufferOverflowException();
        return position++;
}

Chegamos à conclusão de que, para cada byte de dados inserido, a posição aumenta em 1 .

A orientação dos três atributos muda da seguinte forma:

Série de notas Netty: Buffer do componente principal NIO

Depois de escrever três bytes de dados, a posição pontos para a próxima posição operável 3 , e as posições de limite e capacidade de permanecer inalterada.

Vamos afirmar neste momento é chamado de modo de gravação de buffer :

No modo de gravação, o limite e a capacidade são iguais.

3. Leia os dados do Buffer

Em seguida, lemos os três bytes superiores de dados no Buffer, então como localizamos esses três bytes de dados? Lembrando o uso do Buffer acima, você precisa chamar o método Buffer # flip () antes de ler os dados:

public final Buffer flip() {
	// 将 limit 设置为当前数据大小的下一个坐标
        limit = position;
	// position 设置为 0
        position = 0;
	// 如果有标记还原为默认值
        mark = -1;
        return this;
}

Após as operações acima, obtivemos o intervalo inicial e final dos dados. Neste momento, o diagrama esquemático do buffer é o seguinte:

Série de notas Netty: Buffer do componente principal NIO

Vamos afirmar neste momento é chamado de modo de leitura de buffer :

No modo de leitura, o limite é igual ao tamanho real do buffer.

Ao comparar o modo de leitura e o modo de gravação do buffer, descobrimos que, ao controlar o limite, a leitura e a gravação do buffer podem ser alternadas de maneira flexível.

4. métodos clear () e compact ()

Por meio dos métodos clear () e compact (), você pode alternar do modo de leitura para o modo de gravação.

  • limpar () 方法 buffer público final limpar () {posição = 0; limite = capacidade; marca = -1; devolva isso; }

O método clear () define a posição como 0, o limite da capacidade e a marca como -1. Ou seja, o Buffer é restaurado ao estado de reconhecimento inicial.Neste momento, os dados ainda existem no Buffer, mas é impossível determinar quais dados foram lidos e quais não foram.

  • O
    método compact () cpmpact () está na classe de implementação do Buffer. Considere a implementação de HeapByteBuffer de ByteBuffer como exemplo: public ByteBuffer compact () {// copia a matriz original para o início do Buffer System.arraycopy (hb, ix (position ()), hb, ix (0), restante ()) ; // a posição é definida para atrás da posição do último elemento não lido (restante ()); // o limite é definido para capacitylimit (capacity ()); // cancelar marca discardMark (); return this;} Pode ser visto no código que compacta O método () copia todos os dados não lidos para o início do Buffer, a seguir define a posição após o último elemento não lido e o limite é definido como capacidade. Neste momento, o buffer mudou para o modo de gravação, mas não substituirá os dados não lidos.

5. métodos marcar () e redefinir ()

  • O método mark () public final Buffer mark () {mark = position; return this;} O método mark () usa o atributo mark para armazenar a posição subscrita da marca atual.
  • reset () método público final Buffer reset () {int m = marca; if (m <0) lançar novo InvalidMarkException (); posição = m; retornar;} método reset () restaura a propriedade position para o valor da marca. Os dois métodos cooperam um com o outro para registrar e restaurar o valor da posição para ler e escrever a partir da posição marcada.

6, método retroceder ()

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

O método rewind () define a posição como 0 e limpa a marca. Neste momento, o limite permanece inalterado. No modo de leitura, todos os dados podem ser relidos.

Quatro, conclusão

Este é o fim da discussão sobre o Buffer. Existem alguns métodos que são relativamente simples e amigos interessados ​​podem aprender sobre eles por conta própria.

Acho que você gosta

Origin blog.csdn.net/doubututou/article/details/109180731
Recomendado
Clasificación