El concepto básico del traje de tres piezas netty-NIO

Traje de tres piezas Java NIO

Hay varios objetos principales que se deben dominar en NIO: Buffer, Selector, Channel

Buffer

1. AP básico de operación de búfer

En NIO, todos los tipos de búfer se heredan de la clase abstracta Buffer. El más utilizado es ByteBuffer. Para los tipos básicos en Java, hay básicamente un tipo de búfer específico que le corresponde. La relación de herencia entre ellos se muestra en la figura siguiente. Mostrar:

El siguiente es un ejemplo simple de cómo usar IntBuffer:

package com.gupaoedu.vip.netty.io.nio.buffer; 
​
import java.nio.IntBuffer; 
​
public class IntBufferDemo { 
​
public static void main(String[] args) { 
// 分配新的 int 缓冲区,参数为缓冲区容量 
// 新缓冲区的当前位置将为零,其界限(限制位置)将为其容量。它将具有一个底层实现数组,其数组偏移量将为零。 
    IntBuffer buffer = IntBuffer.allocate(8); 
    for (int i = 0; i < buffer.capacity(); ++i) { 
        int j = 2 * (i + 1); 
        // 将给定整数写入此缓冲区的当前位置,当前位置递增 
        buffer.put(j); 
    }
    // 重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为 0 
    buffer.flip(); 
    // 查看在当前位置和限制位置之间是否有元素 
    while (buffer.hasRemaining()) { 
        // 读取此缓冲区当前位置的整数,然后当前位置递增 
        int j = buffer.get(); 
        System.out.print(j + " "); 
    }
    } 
}
运行后可以看到:2 4 6 8 10 12 14 16

2. El principio básico de Buffer

Cuando hablamos del búfer, dijimos que el objeto búfer es esencialmente una matriz, pero en realidad es una matriz especial. El objeto búfer tiene algunos mecanismos incorporados para rastrear y registrar los cambios de estado del búfer. Si usamos get El método () obtiene datos del búfer o usa el método put () para escribir datos en el búfer, lo que hará que cambie el estado del búfer.

En el búfer, los atributos más importantes son los siguientes tres, trabajan juntos para completar el seguimiento de cambios del estado interno del búfer:

position: especifica el índice del siguiente elemento que se va a escribir o leer. Su valor se actualiza automáticamente mediante el método get () / put (). Cuando se crea un nuevo objeto Buffer, la posición se inicializa a 0.

límite: especifique cuántos datos deben extraerse (al escribir en el canal desde el búfer) o cuánto espacio se puede poner (al leer en el búfer desde el canal).

capacidad: especifica la capacidad máxima de datos que se pueden almacenar en el búfer. De hecho, especifica el tamaño de la matriz subyacente, o al menos la capacidad de la matriz subyacente que se nos permite usar.

Existe una relación de tamaño relativo entre los tres valores de atributo anteriores: 0 <= posición <= límite <= capacidad. Si creamos un nuevo objeto ByteBuffer con una capacidad de 10, en el momento de la inicialización, la posición se establece en 0, el límite y la capacidad se establecen en 10. En el proceso de usar el objeto ByteBuffer en el futuro, el valor de la capacidad no cambiará y Los otros dos cambiarán con el uso.

Usemos el código para demostrarlo nuevamente, prepare un archivo txt, almacene el disco E e ingrese lo siguiente:

Tom.

A continuación usamos un fragmento de código para verificar el proceso de cambio de los valores de posición, límite y capacidad, el código es el siguiente:

public class BufferDemo { 
    public static void main(String args[]) throws Exception { 
        //这用用的是文件 IO 处理 
        FileInputStream fin = new FileInputStream("E://test.txt"); 
        //创建文件的操作管道 
        FileChannel fc = fin.getChannel(); 
        //分配一个 10 个大小缓冲区,说白了就是分配一个 10 个大小的 byte 数组 
        ByteBuffer buffer = ByteBuffer.allocate(10); 
        output("初始化", buffer); 
        //先读一下 
        fc.read(buffer); 
        output("调用 read()", buffer); 
        //准备操作之前,先锁定操作范围 
        buffer.flip(); 
        output("调用 flip()", buffer); 
        //判断有没有可读数据 
        while (buffer.remaining() > 0) { 
        byte b = buffer.get(); 
        // System.out.print(((char)b)); 
        }
        output("调用 get()", buffer); 
        //可以理解为解锁 
        buffer.clear(); 
        output("调用 clear()", buffer); 
        //最后把管道关闭 
        fin.close(); 
    }
    //把这个缓冲里面实时状态给答应出来 
    public static void output(String step, Buffer buffer) { 
        System.out.println(step + " : "); 
        //容量,数组大小 
        System.out.print("capacity: " + buffer.capacity() + ", "); 
        //当前操作数据所在的位置,也可以叫做游标 
        System.out.print("position: " + buffer.position() + ", "); 
        //锁定值,flip,数据操作范围索引只能在 position - limit 之间 
        System.out.println("limit: " + buffer.limit()); 
        System.out.println(); 
    } 
}

El resultado final es:

Hemos visto los resultados de la operación. Los resultados anteriores se ilustran a continuación. Los cuatro valores de los atributos se muestran en la figura:

Podemos leer algunos datos del canal al búfer. Tenga en cuenta que leer datos del canal es equivalente a escribir datos en el búfer. Si lee 4 de sus propios datos, el valor de la posición es 4 en este momento, es decir, el índice del siguiente byte a escribir es 4 y el límite sigue siendo 10, como se muestra en la siguiente figura:

El siguiente paso es escribir los datos leídos en el canal de salida, lo que equivale a leer datos del búfer. Antes de eso, debes llamar al método flip (), que logrará dos cosas:

\ 1. Establecer límite al valor de posición actual

\ 2. Establecer la posición en 0

Dado que la posición se establece en 0, se puede garantizar que el primer byte del búfer se lea en la siguiente salida y que el límite se establezca en la posición actual, lo que garantiza que los datos leídos se escriban exactamente en Los datos en el búfer son los que se muestran en la siguiente figura:

Después de leer los datos del búfer, el valor del límite permanece en el valor cuando llamamos al método flip (). Llamar al método clear () puede establecer todos los cambios de estado en el valor inicial, como se muestra en la siguiente figura:

3. Asignación de búfer

En los ejemplos anteriores, hemos visto que al crear un objeto de búfer, se llama al método estático allocate () para especificar la capacidad del búfer. De hecho, llamar a allocate () equivale a crear una matriz de un tamaño específico. Y envuélvalo como un objeto de búfer. O podemos envolver directamente una matriz existente como un objeto de búfer, como se muestra en el siguiente código de muestra:

/** 手动分配缓冲区 */ 
​
public class BufferWrap { 
    public void myMethod() { 
        // 分配指定大小的缓冲区 
        ByteBuffer buffer1 = ByteBuffer.allocate(10); 
        //包装一个现有的数组 
        byte array[] = new byte[10]; 
        ByteBuffer buffer2 = ByteBuffer.wrap( array ); 
    } 
}

4. Fragmentación del búfer

En NIO, además de asignar o empaquetar un objeto de búfer, también puede crear un sub-búfer basado en el objeto de búfer existente, es decir, cortar una porción del búfer existente como un nuevo búfer, pero El búfer existente y el sub búfer creado comparten datos en el nivel de matriz inferior, es decir, el sub búfer es equivalente a una ventana de visualización del búfer existente. Se puede crear un sub-búfer llamando al método slice (), veamos un ejemplo

/**
\* 缓冲区分片 
*/ 
public class BufferSlice { 
    static public void main( String args[] ) throws Exception { 
        ByteBuffer buffer = ByteBuffer.allocate( 10 ); 
        // 缓冲区中的数据 0-9 
        for (int i=0; i<buffer.capacity(); ++i) { 
            buffer.put( (byte)i ); 
        }
        // 创建子缓冲区 
        buffer.position( 3 ); 
        buffer.limit( 7 ); 
        ByteBuffer slice = buffer.slice(); 
        // 改变子缓冲区的内容 
        for (int i=0; i<slice.capacity(); ++i) { 
            byte b = slice.get( i ); 
            b *= 10; 
            slice.put( i, b ); 
        }
        buffer.position( 0 ); 
        buffer.limit( buffer.capacity() ); 
        while (buffer.remaining()>0) { 
        System.out.println( buffer.get() ); 
        } 
    } 
}

En este ejemplo, se asigna un búfer con una capacidad de 10, y se colocan los datos 0-9 en él, y se crea un sub búfer sobre la base del búfer y se cambia el sub búfer El contenido, del resultado de salida final, solo ha cambiado la parte "visible" de los datos en el búfer secundario, y muestra que el búfer secundario y el búfer original comparten datos. El resultado de salida es el siguiente:

5. Búfer de solo lectura

Los búferes de solo lectura son muy simples, puede leerlos, pero no puede escribir datos en ellos. Puede convertir cualquier búfer normal en un búfer de solo lectura llamando al método asReadOnlyBuffer () del búfer. Este método devuelve un búfer que es exactamente igual al búfer original y comparte datos con el búfer original, pero solo Leer. Si se cambia el contenido del búfer original, el contenido del búfer de solo lectura también cambia en consecuencia:

/** 只读缓冲区 */ 
public class ReadOnlyBuffer { 
    static public void main( String args[] ) throws Exception { 
    ByteBuffer buffer = ByteBuffer.allocate( 10 ); 
    // 缓冲区中的数据 0-9 
    for (int i=0; i<buffer.capacity(); ++i) { 
        buffer.put( (byte)i ); 
    }
    // 创建只读缓冲区 
    ByteBuffer readonly = buffer.asReadOnlyBuffer(); 
    // 改变原缓冲区的内容 
    for (int i=0; i<buffer.capacity(); ++i) { 
        byte b = buffer.get( i ); 
         b *= 10; 
         buffer.put( i, b ); 
    }
    readonly.position(0); 
    readonly.limit(buffer.capacity()); 
    // 只读缓冲区的内容也随之改变 
    while (readonly.remaining()>0) { 
         System.out.println( readonly.get()); 
    }
    } 
}

6. Búfer directo

El búfer directo es para acelerar la velocidad de E / S, use una forma especial de asignar memoria para el búfer, la descripción en la documentación del JDK es: dado un búfer de bytes directo, la máquina virtual Java hará todo lo posible para tratarlo directamente Realice operaciones de E / S nativas. En otras palabras, lo intentará antes (o después) de cada llamada a la operación de E / S nativa del sistema operativo subyacente.

Evite copiar el contenido del búfer a un búfer intermedio o copiar datos de un búfer intermedio. Para asignar un búfer directo, debe llamar al método allocateDirect () en lugar del método allocate (). El uso es el mismo que el del búfer normal, como se muestra en el siguiente ejemplo de copia de archivos:

/**
\* 直接缓冲区 
*/ 
public class DirectBuffer { 
    static public void main( String args[] ) throws Exception { 
        //首先我们从磁盘上读取刚才我们写出的文件内容 
        String infile = "E://test.txt"; 
        FileInputStream fin = new FileInputStream( infile ); 
        FileChannel fcin = fin.getChannel(); 
        //把刚刚读取的内容写入到一个新的文件中 
        String outfile = String.format("E://testcopy.txt"); 
        FileOutputStream fout = new FileOutputStream( outfile ); 
        FileChannel fcout = fout.getChannel(); 
        // 使用 allocateDirect,而不是 allocate 
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024); 
        while (true) { 
            buffer.clear(); 
            int r = fcin.read(buffer); 
            if (r==-1) { 
                break; 
            }
            buffer.flip(); 
            fcout.write(buffer); 
        } 
    } 
}

7. Mapeo de memoria

El mapeo de memoria es un método para leer y escribir datos de archivos, que puede ser mucho más rápido que las E / S convencionales basadas en flujo o canal. La E / S de archivos mapeados en memoria se realiza haciendo que los datos del archivo aparezcan como el contenido de la matriz de memoria, lo que al principio suena como leer todo el archivo en la memoria, pero de hecho no es el caso. En términos generales, solo la parte del archivo que realmente se lee o escribe se asignará a la memoria. Como el siguiente código de muestra:

/**
\* IO 映射缓冲区 
*/ 
public class MappedBuffer { 
    static private final int start = 0; 
    static private final int size = 1024; 
    static public void main( String args[] ) throws Exception { 
        RandomAccessFile raf = new RandomAccessFile( "E://test.txt", "rw" ); 
        FileChannel fc = raf.getChannel(); 
        //把缓冲区跟文件系统进行一个映射关联 
        //只要操作缓冲区里面的内容,文件内容也会跟着改变 
        MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE,start, size ); 
        mbb.put( 0, (byte)97 ); 
        mbb.put( 1023, (byte)122 ); 
        raf.close(); 
    } 
}

 

Supongo que te gusta

Origin blog.csdn.net/madongyu1259892936/article/details/109668998
Recomendado
Clasificación