Serie de notas de Netty: Búfer de componentes centrales NIO

I. Resumen

La esencia de Buffer es en realidad una pieza de memoria, análoga al ByteBuffer común, que puede entenderse simplemente como una matriz de Byte. Java NIO encapsula esta memoria en un objeto Buffer y proporciona una serie de atributos y métodos para facilitar la interacción de datos entre Buffer y Channel.

Dos, uso

Veamos la operación 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();
        }

En el ejemplo anterior, existen los siguientes pasos para usar Buffer:

1. Asignar espacio

  • allocate (int capacidad)
    Buffer es una clase abstracta.Cada clase de implementación de Buffer proporciona un método estático de asignación (int capacidad) para ayudar a crear instancias de objetos Buffer rápidamente. Por ejemplo, el ByteBuffer común: public static ByteBuffer allocate (int capacity) {if (capacity <0) throw new IllegalArgumentException (); return new HeapByteBuffer (capacidad, capacidad);} También se puede ver por su nombre que el espacio asignado por este método es Basado en memoria de pila.
  • allocateDirect (capacidad int)
    asigna espacio en función de la memoria fuera del montón y crea una instancia de un objeto Buffer. public static ByteBuffer allocateDirect (int capacidad) {return new DirectByteBuffer (capacidad);}
  • wrap (byte [] array, int offset, int length)
    Cada clase de implementación de Buffer proporciona un método de ajuste para envolver un arreglo en una instancia de Buffer.

2. Leer datos del canal al búfer

  • Utilice el método channel.read (buffer)
  • Todas las
    implementaciones de Buffer que utilizan el método buffer.put () proporcionan el método put () para colocar datos en el Buffer.

3. Llame al método flip ()

4. Obtener datos de Buffer

channel.write(buffer)
buffer.get()

5. Llame al método clear () o al método compact ()

¿Cuál es la función de los métodos flip (), clear (), compact () anteriores?

Tres, análisis de código fuente

Además de la matriz utilizada para almacenar datos, Buffer tiene varias propiedades 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;
        }
    }

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

Serie de notas de Netty: Búfer de componentes centrales NIO

Basándose en los cuatro atributos anteriores, Buffer completa las operaciones de lectura y escritura controlando sus posiciones. Analizamos desde el uso de Buffer:

1. Asignar espacio

Suponiendo que se llama al método ByteBuffer.allocate (10) para asignar 10 bytes de espacio para el búfer, mire el código de allocate (capacidad int):

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

Llama al método de construcción de HeapByteBuffer y establece la capacidad y el límite en 10 y la posición en 0.

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

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

El diagrama esquemático del búfer en este momento es el siguiente:

Serie de notas de Netty: Búfer de componentes centrales NIO

En el estado inicial del búfer, la posición es 0 y el límite y la capacidad apuntan a 9 .

2. Escribir datos en búfer

Escribimos tres bytes de datos en el búfer y observamos el método HeapByteBuffer # put (byte b), una de las implementaciones de ByteBuffer:

public ByteBuffer put(byte x) {

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

}

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

El método nextPutIndex () en ByteBuffer se usa para calcular el índice de los siguientes datos escritos:

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

Llegamos a la conclusión de que cada vez que se ingresa un byte de datos, la posición aumenta en 1 .

En este momento, la orientación de los tres atributos cambia de la siguiente manera:

Serie de notas de Netty: Búfer de componentes centrales NIO

Después de escribir tres bytes de datos, la posición apunta a la siguiente posición operable 3 , y las posiciones de límite y capacidad permanecen sin cambios.

Vamos a decir que en este momento se llama modo de escritura en búfer :

En el modo de escritura, el límite y la capacidad son iguales.

3. Leer datos del búfer

A continuación, leemos los tres bytes superiores de datos en el búfer, entonces, ¿cómo ubicamos estos tres bytes de datos? Recordando el uso de Buffer anterior, debe llamar al método Buffer # flip () antes de leer los datos:

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

Después de las operaciones anteriores, hemos obtenido el rango inicial y final de los datos. En este momento, el diagrama esquemático del búfer es el siguiente:

Serie de notas de Netty: Búfer de componentes centrales NIO

Vamos a decir que en este momento se llama modo de lectura de búfer :

En modo de lectura, el límite es igual al tamaño real del búfer.

Al comparar el modo de lectura y el modo de escritura del búfer, encontramos que al controlar el límite, la lectura y escritura del búfer se pueden cambiar de manera flexible.

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

A través de los métodos clear () y compact (), puede cambiar del modo de lectura al modo de escritura.

  • clear () 方法 public final Buffer clear () {position = 0; límite = capacidad; marca = -1; devuelve esto; }

El método clear () establece la posición en 0, el límite de capacidad y la marca en -1. Es decir, el búfer se restaura al estado de reconocimiento inicial En este momento, los datos aún existen en el búfer, pero es imposible determinar qué datos se han leído y qué datos no se han leído.

  • El
    método compact () cpmpact () está en la clase de implementación de Buffer. Tome la implementación de ByteBuffer HeapByteBuffer como ejemplo: public ByteBuffer compact () {// copie la matriz original al principio del Buffer System.arraycopy (hb, ix (position ()), hb, ix (0), restante ()) ; // posición se establece detrás de la posición del último elemento no leído (restante ()); // límite se establece en capacidad límite (capacidad ()); // cancelar marca discardMark (); devolver esto;} Se puede ver en el código que compacta El método () copia todos los datos no leídos al principio del búfer, luego establece la posición después del último elemento no leído y el límite se establece en la capacidad. En este momento, el búfer ha cambiado al modo de escritura, pero no sobrescribirá los datos no leídos.

5. métodos mark () y reset ()

  • El método mark () public final Buffer mark () {mark = position; return this;} El método mark () usa el atributo mark para almacenar la posición del subíndice de la marca actual.
  • reset () método public final Buffer reset () {int m = marca; if (m <0) throw new InvalidMarkException (); position = m; return this;} el método reset () restaura la propiedad de posición al valor de marca. Los dos métodos cooperan entre sí para registrar y restaurar el valor de posición para leer y escribir desde la posición marcada.

6, método rewind ()

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

El método rewind () establece la posición en 0 y borra la marca. En este momento, el límite permanece sin cambios En el modo de lectura, todos los datos se pueden volver a leer.

Cuatro, conclusión

Este es el final de la discusión sobre Buffer Hay algunos métodos que son relativamente simples y los amigos interesados ​​pueden aprender sobre ellos por sí mismos.

Supongo que te gusta

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