Objeto Java Stream Stream (consejos prácticos)

Tabla de contenido

一、InputStream y OutputStream

1.1 Generalmente se utilizan InputStream y OutputStream

1.2 Uso especial

1.2.1 ¿Cómo indicar que el archivo ha sido leído? (Flujo de entrada de datos)

1.2.2 Lectura de caracteres/lectura de datos de texto (escáner)

1.2.3 Lectura y escritura aleatoria de archivos (RandomAccessFile)


一、InputStream y OutputStream


1.1 Generalmente se utilizan InputStream y OutputStream

InputStream tiene los siguientes métodos:

  1. int read(): lee un byte de datos y devuelve -1 significa que se ha leído por completo.
  2. int read(byte[] b): Lee hasta b.longitud de bytes de datos en b, y devuelve el número real leído; -1 significa que la lectura ha finalizado (esto es como si llevaras una palangana a la cantina. Si le preguntas a tu tía que prepare la comida, entonces la tía definitivamente la llenará de acuerdo con la cantidad de su comida. Si puede llenarla por usted, hará todo lo posible para llenarla por usted). Esta también es una forma de uso más común. método en la práctica.
  3. int read(byte[] b, int off, int len): lee hasta len - off bytes de datos en b, comienza desde off y devuelve el número real leído; -1 significa que la lectura está completa.
  4. void close(): cierra el flujo de bytes (generalmente, el InputStream se escribirá en try(), por lo que no es necesario liberarlo manualmente ~).

inputStream es solo una clase abstracta. Para usarlo, aún necesita una clase de implementación específica. Por ejemplo, después de que el cliente y el servidor aceptan, se obtiene la clase de implementación específica del objeto de flujo ... Pero lo que usamos más comúnmente es file lectura, que es FileInputStream. .

OutputStream tiene los siguientes métodos:

  1. escritura vacía (int b): escribe los bytes especificados en este flujo de salida.
  2. escritura vacía (byte [] b): escribe todos los datos de la matriz de caracteres b en el sistema operativo.
  3. int write (byte [] b, int off, int len): escribe los datos comenzando desde off en la matriz de caracteres b en os, escribe un total de len
  4. void close(): cierra el flujo de bytes
  5. void Flush (): sabemos que la velocidad de E / S es muy lenta, por lo que para reducir la cantidad de operaciones del dispositivo, la mayoría de OutputStreams escribirán datos temporalmente en un área designada de la memoria hasta que Los datos se escriben. Los datos en realidad se escriben en el dispositivo cuando el área está llena o en otras condiciones específicas. Esta área generalmente se denomina buffer. Pero un resultado es que es probable que parte de los datos que escribimos queden en el búfer. La operación de descarga debe llamarse al final o en una ubicación adecuada para descargar los datos al dispositivo.

OutputStream también es solo una clase abstracta y necesita una clase de implementación específica para usarla. Todavía solo nos importa escribir en el archivo, así que use FileOutputStream

Ps: FileOutputStream tiene un constructor llamado new FileOutputStream (ruta de cadena, anexo booleano). El primer parámetro es la ruta del archivo y el segundo parámetro es si se escribe agregado al final. Si desea agregar datos al final del archivo , Simplemente complete verdadero ~

1.2 Uso especial

1.2.1 ¿Cómo indicar que el archivo ha sido leído? (Flujo de entrada de datos)

Utilice el método read () para devolver un valor int. Si este valor es -1, significa que el archivo se ha leído por completo ~

Sin embargo, en proyectos reales, a menudo se usa una especie de método de seguimiento para indicar que la lectura del archivo se ha completado ~ Si estamos de acuerdo con el formato de los datos, es un int (que indica la longitud de la carga útil) + carga útil, seguido de datos en el mismo formato, luego, en este momento, debemos leer este int a través del método readInt en DataInputStream (este objeto de flujo se usa especialmente para leer números y flujos de bytes, y debe usarse con DataOutputStream ) . La especialidad es que después de leer hasta el final del archivo, continúe leyendo. Se lanzará la excepción EOFException (en el pasado, cuando leímos el final del archivo, devolvió -1 o nulo), así que aquí podemos capture esta excepción a través de catch, lo que indica que la lectura se completó ~

Ps: Vale la pena señalar que DataInputStream / DataOutputStream puede facilitar la lectura y escritura de números (readInt, writeInt). El InputStream / OutputStream nativo no proporciona métodos de lectura y escritura digitales, y debemos convertirlos nosotros mismos.

    public LinkedList<Message> loadAllMessageFromQueue(MSGQueue queue) throws IOException {
        //1.检查文件是否存在
        if(!checkQueueFileExists(queue.getName())) {
            throw new IOException("[MessageFileManager] 获取文件中所有有效消息时,发现队列文件不存在!queueName=" + queue.getName());
        }
        //2.获取队列中所有有效的消息
        synchronized (queue) {
            LinkedList<Message> messages = new LinkedList<>();
            try (InputStream inputStream = new FileInputStream(getQueueDataFilePath(queue.getName()))) {
                try (DataInputStream dataInputStream = new DataInputStream(inputStream)) {
                    int index = 0;
                    while(true) {
                        int messageSize = dataInputStream.readInt();
                        byte[] payload = new byte[messageSize];
                        int n = dataInputStream.read(payload);
                        if(n != messageSize) {
                            throw new IOException("[MessageFileManager] 读取消息格式出错!expectedSize=" + messageSize +
                                    ", actualSize=" + n);
                        }
                        //记录 offset
                        Message message = (Message) BinaryTool.fromBytes(payload);
                        if(message.getIsValid() == 0x0) {
                            index += (4 + messageSize);
                            continue;
                        }
                        message.setOffsetBeg(index + 4);
                        message.setOffsetEnd(index + 4 + messageSize);
                        messages.add(message);
                        index += (4 + messageSize);
                    }
                }
            } catch (EOFException e) {
                System.out.println("[MessageFileManager] 队列文件中有消息获取完成!queueName=" + queue.getName());
            }
            return messages;
        }
    }

1.2.2 Lectura de caracteres/lectura de datos de texto (escáner)

Es muy engorroso y difícil usar InputStream directamente para leer tipos de caracteres, por lo que usamos una clase con la que estamos familiarizados antes para completar el trabajo, que es la clase Scanner.

El escáner se usa generalmente con PrintWrite para leer y escribir datos en formato de texto, lo que elimina en gran medida la necesidad de que InputStream/OutputStream decodifique y convierta datos de bytes y datos de texto usando UTF-8.

Por ejemplo uno:

// 需要先在项目目录下准备好一个 hello.txt 的文件,里面填充 "你好中国" 的内容

public class Main {
    public static void main(String[] args) throws IOException {
        try (InputStream is = new FileInputStream("hello.txt")) {
           try (Scanner scanner = new Scanner(is, "UTF-8")) {
               while (scanner.hasNext()) {
                   String s = scanner.next();
                   System.out.print(s);
               }
           }
       }
   }
}

Ejemplo dos: 

    public void writeStat(String queueName, Stat stat) {
        try (OutputStream outputStream = new FileOutputStream(getQueueStatFilePath(queueName))) {
            PrintWriter printWriter = new PrintWriter(outputStream);
            printWriter.write(stat.totalCount + "\t" + stat.validCount);
            printWriter.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public Stat readStat(String queueName) {
        Stat stat = new Stat();
        try (InputStream inputStream = new FileInputStream(getQueueStatFilePath(queueName))) {
            Scanner scanner = new Scanner(inputStream);
            stat.totalCount = scanner.nextInt();
            stat.validCount = scanner.nextInt();
            return stat;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

1.2.3 Lectura y escritura aleatoria de archivos (RandomAccessFile)

Anteriormente, DataInputStream/DataOutputStream se usaban para recibir FileInputStream/FileOutputStream para lectura y escritura secuencial de archivos (ya sea leyendo desde el principio hasta el final, o agregando escrituras al final...). La clase RandomAccessFile se puede usar para realizar operaciones en cualquier ubicación especificada ¡Operaciones de lectura/escritura!

Esto implica el concepto de cursor. De hecho, cuando escribes un archivo, dondequiera que escribas, habrá un cursor parpadeando en esa ubicación ~

En RandomAccessFile, puede utilizar el método seek() para especificar la posición del cursor (la unidad es byte). Por ejemplo, si desea eliminar lógicamente una determinada sección de memoria en un archivo (sin eliminación real, simplemente léala primero). y márquelo como no válido, y luego vuelva a escribir el archivo, la papelera de reciclaje tiene casi la misma lógica).

    public void deleteMessage(MSGQueue queue, Message message) throws IOException {
        //1.检查队列相关文件是否存在
        if(!checkQueueFileExists(queue.getName())) {
            throw new IOException("[FileDataCenter] 删除消息时,发现队列相关文件不存在!queueName=" + queue.getName());
        }
        synchronized (message) {
            //2.将要删除的消息文件读出来
            try (RandomAccessFile randomAccessFile = new RandomAccessFile(getQueueDataFilePath(queue.getName()), "rw")) {
                randomAccessFile.seek(message.getOffsetBeg() - 4);
                int payloadSize = randomAccessFile.readInt();
                byte[] payload = new byte[payloadSize];
                int n = randomAccessFile.read(payload);
                if(n != payloadSize) {
                    throw new IOException("[FileDataCenter] 读取文件格式出错!path=" + getQueueDataFilePath(queue.getName()));
                }
                //3.将待删除的消息标记为无效(isValid = 0x0)
                Message toDeleteMessage = (Message) BinaryTool.fromBytes(payload);
                toDeleteMessage.setIsValid((byte) 0x0);
                //4.将消息写入文件
                randomAccessFile.seek(message.getOffsetBeg());
                randomAccessFile.write(BinaryTool.toBytes(toDeleteMessage));
            }
            //5.更新统计文件
            Stat stat = readStat(queue.getName());
            stat.validCount -= 1;
            writeStat(queue.getName(), stat);
        }
    }

PD:

RandomAccessFile tiene dos constructores (en realidad uno). RandomAccessFile (nombre de cadena, modo de cadena) es equivalente a RandomAccessFile (nuevo archivo (nombre), modo de cadena)

mode Este parámetro indica el modo de acceso~
➢ "r": abre el archivo especificado en modo de solo lectura. Si intenta realizar un método de escritura en RandomAccessFile, se generará una IOException.
➢ "rw": abre el archivo especificado en modo lectura y escritura. Si el archivo aún no existe, intenta crearlo.
➢ "rws": abre el archivo especificado en modo lectura y escritura. En relación con el modo "rw", también requiere que cada actualización del contenido o metadatos del archivo se escriba de forma sincrónica en el dispositivo de almacenamiento subyacente.
➢ "rwd": abre el archivo especificado en modo lectura y escritura. En relación con el modo "rw", también requiere que cada actualización del contenido del archivo se escriba en el dispositivo de almacenamiento subyacente de forma sincrónica.

Supongo que te gusta

Origin blog.csdn.net/CYK_byte/article/details/132724326
Recomendado
Clasificación