Búfer en NIO

En el código que llamó Channel antes, se usó una clase llamada ByteBuffer, que es una subclase de Buffer. Esta clase llamada Buffer se usa especialmente para resolver el problema de desajuste de velocidad entre dispositivos de alta velocidad y dispositivos de baja velocidad, y también puede reducir la cantidad de lecturas y escrituras de la base de datos.

Se divide en búfer de entrada y búfer de salida.

Muchos principiantes no entienden la diferencia entre "almacenamiento en búfer" y "almacenamiento en caché". Trato de explicarlo en un lenguaje sencillo:

1. El búfer debe actualizarse, borrarse, restablecerse, etc. periódicamente, y es posible que estas operaciones no sean necesarias para la memoria caché. Por ejemplo, al cocinar, la tabla de cortar es el amortiguador y el refrigerador es el amortiguador, porque debe cortarse, palmearse y picarse del refrigerador a la olla, y el siguiente plato debe vaciarse cada vez, y el el refrigerador no necesita estar vacío regularmente, reiniciar (a menos que haya un corte de energía y las cosas se estropeen);

2. La función principal del búfer es desacoplar las restricciones de velocidad entre dispositivos y convertirse en un "búfer" entre dispositivos, mientras que la memoria caché se utiliza para acelerar la lectura y reducir la cantidad de recálculos o readquisiciones de la base de datos. En comparación con comprar cada plato del mercado de verduras, obviamente es mucho más rápido ponerlo en el refrigerador; y en comparación con sacarlo del refrigerador cada vez que lo cocinas, por supuesto que es más rápido tomarlo de la tabla de cortar. Es decir: "velocidad de compra de comestibles (disco) < velocidad de búsqueda del refrigerador (caché) < velocidad de búsqueda de la tabla de cortar (búfer)", esa es la relación;

3. El búfer se enfoca en la velocidad y la escritura, mientras que el caché se enfoca en los tiempos y la lectura. Así como la tabla de cortar se enfoca en cortar las verduras, mientras que el refrigerador se enfoca en el almacenamiento;

4. El caché actual es generalmente muy grande, llegando incluso al nivel de TB (1 TB = 1024 GB), es imposible tener un búfer tan grande (por supuesto, también puedes hacer la tabla de cortar tan grande como un refrigerador, de todos modos, yo Nunca he visto este tipo de - _-!).

Cuando vuelva a ver el almacenamiento en búfer y el almacenamiento en caché en el futuro, puede comparar la tabla de cortar en casa con el refrigerador.

Hay ocho tipos de búfer en NIO: ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer y MappedByteBuffer.Los primeros siete corresponden a tipos de datos básicos, y MappedByteBuffer se utiliza especialmente para el mapeo de memoria.

El área de búfer es en realidad un contenedor, un contenedor compuesto de matrices/colecciones continuas. Channel proporciona un canal para leer datos de archivos y redes, pero todos los datos leídos y escritos deben pasar por Buffer.

El proceso de escritura de datos en Buffer es:

1. Escribir datos del canal al búfer: channel.read(buf)

2. Llame al método put() de Buffer: buf.put(Object)

El proceso de lectura de datos de Buffer es:

1. Leer datos del búfer al canal: channel.write(buf)

2. Llame al método get() de Buffer: buf.get()

El proceso de lectura y escritura es probablemente así:

Sigue siendo la misma oración de ayer: si desarrolla su propio sistema similar a RPC o middleware similar a MQ en una gran fábrica, entonces debe ser competente en esto; de lo contrario, puede entenderlo y no tiene que morir. En realidad, es suficiente para Buffer ver esto. En cuanto a: propiedades del búfer, pasos para usar el búfer, cómo JVM crea búferes en la memoria, etc., estos deberían ser cursos obligatorios para Mianba, pero rara vez se usan en desarrollo.

Todavía en código.

Métodos comunes de Buffer:

// 分配JVM间接缓冲区
ByteBuffer buffer = ByteBuffer.allocate(32);
System.out.println("buffer初始状态: " + buffer);
// 将position设回8
buffer.position(8);
System.out.println("buffer设置后状态: " + buffer);

System.out.println("测试reset ======================>>>");
// clear()方法,position将被设回0,limit被设置成capacity的值
buffer.clear();
System.out.println("buffer clear后状态: " + buffer);
// 设置这个缓冲区的位置
buffer.position(5);
// 将此缓冲区的标记设置5
// 如果没有buffer.mark();这句话会报错
buffer.mark();
buffer.position(10);
System.out.println("reset前状态: " + buffer);
// 将此缓冲区的位置重置为先前标记的位置(buffer.position(5))
buffer.reset();
System.out.println("reset后状态: " + buffer);

System.out.println("测试get ======================>>>");
buffer = ByteBuffer.allocate(32);
buffer.put((byte) 'x').put((byte) 'i').put((byte) 'a').put((byte) 'n').put((byte) 'g');
System.out.println("flip前状态: " + buffer);
// 转换为读模式
buffer.flip();
System.out.println("get前状态: " + buffer);
System.out.println((char) buffer.get());
System.out.println("get后状态: " + buffer);

System.out.println("测试put ======================>>>");
ByteBuffer pb = ByteBuffer.allocate(32);
System.out.println("put前状态: " + pb +
        ", put前数据: " + new String(pb.array()));
System.out.println("put后状态: " + pb.put((byte) 'w') +
        ", put后数据: " + new String(pb.array()));
System.out.println(pb.put(3, (byte) '3'));
// put(3, (byte) '3')并不改变position的位置,但put((byte) '3')会
System.out.println("put(3, '3')后状态: " + pb + ", 数据: " + new String(pb.array()));
// 这里的buffer是 xiang[pos=1 lim=5 cap=32]
System.out.println("buffer叠加前状态: " + buffer +
        ", buffer叠加前数据: " + new String(buffer.array()));
// buffer.put(pb);会抛异常BufferOverflowException
pb.put(buffer);
// 叠加后数据是wiang,因为buffer的position=1
System.out.println("put(buffer)后bb状态: " + pb + ", buffer叠加后数据: " + new String(pb.array()));

// 重新读取buffer中所有数据
System.out.println("测试rewind ======================>>>");
buffer.clear();
buffer.position(10);
System.out.println("buffer当前状态: " + buffer);
// 返回此缓冲区的限制
buffer.limit(15);
System.out.println("limit后状态: " + buffer);
// 把position设为0,mark设为-1,不改变limit的值
buffer.rewind();
System.out.println("rewind后状态: " + buffer);

// 将所有未读的数据拷贝到Buffer起始处,然后将position设到最后一个未读元素正后面
System.out.println("测试compact ======================>>>");
buffer.clear();
buffer.put("abcd".getBytes());
System.out.println("compact前状态: " + buffer);
System.out.println(new String(buffer.array()));
// limit=position;position=0;mark=-1; 翻转,也就是让flip之后的position到limit这块区域变成之前的0到position这块
// 翻转就是将一个处于存数据状态的缓冲区变为一个处于准备取数据的状态,或者相反
buffer.flip();
System.out.println("flip后状态: " + buffer);
// get()方法:相对读,从position位置读取一个byte,并将position+1,为下次读写作准备
System.out.println((char) buffer.get());
System.out.println((char) buffer.get());
System.out.println((char) buffer.get());
System.out.println("三次调用get后: " + buffer);
System.out.println(new String(buffer.array()));
// 把从position到limit中的内容移到0到limit-position的区域内
// position和limit的取值也分别变成limit-position、capacity
// 如果先将positon设置到limit,再compact,那么相当于clear()
buffer.compact();
System.out.println("compact后状态: " + buffer);
System.out.println(new String(buffer.array()));

Java generalmente usa clases de E/S almacenadas en búfer como BufferedInputStream y BufferedReader para procesar archivos grandes, pero si el archivo es muy grande, como alcanzar el nivel de GB o incluso TB, la forma más rápida es usar el esquema de asignación de memoria de archivos introducido en NIO: MapeadoByteBuffer.

Solo necesita MappedByteBuffer para tener un rendimiento de lectura y escritura extremadamente alto. La razón principal es que admite operaciones asíncronas, ¡y eso es todo!

Puedes probarlo con un archivo grande:

// ByteBuffer读取大文件
public static void useFileChannel() {
    try{
        FileInputStream fis = new FileInputStream("你电脑上已经存在的文件路径,例如C:\\file1");
        FileChannel channel = fis.getChannel();
        long start = System.currentTimeMillis();
        ByteBuffer buff = ByteBuffer.allocate((int) channel.size());
        buff.clear();
        channel.read(buff);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        fis.close();
        channel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// MappedByteBuffer读取大文件
public static void useMappedByteBuffer() {
    try{
        FileInputStream fis = new FileInputStream("你电脑上已经存在的文件路径,例如C:\\file1");
        FileChannel channel = fis.getChannel();
        long start = System.currentTimeMillis();
        MappedByteBuffer mbb = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        fis.close();
        channel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    useFileChannel();
    useMappedByteBuffer();
}

Finalmente, coloque estos dos métodos en main() para ver el efecto.

El Buffer en NIO es suficiente para decir mucho, y será más directo usar código para sentirlo.

Supongo que te gusta

Origin blog.csdn.net/weixin_47367099/article/details/127458562
Recomendado
Clasificación