6 métodos para escribir archivos en JAVA

Básicamente, solo existen dos métodos para manipular archivos en Java: flujos de caracteres y flujos de bytes, y existen muchas clases de implementación para flujos de bytes y flujos de caracteres, por lo que podemos elegir varias clases al escribir archivos. En este artículo, haremos un balance de estos métodos y, de paso, probaremos su rendimiento para elegir el mejor método de escritura para nosotros.

Antes de comenzar oficialmente, comprendamos algunos conceptos básicos: la definición y diferencia de flujo, flujo de bytes y flujo de caracteres.

0. ¿Qué es una corriente?

"Fluir" en Java es un concepto abstracto y una metáfora, al igual que el flujo de agua, el flujo de agua fluye de un extremo al otro, mientras que el "flujo de agua" en Java son datos, y los datos "fluirán" de un extremo al otro.

Según la direccionalidad del flujo, podemos dividir el flujo en flujo de entrada y flujo de salida. Cuando el programa necesita leer datos de la fuente de datos, abrirá un flujo de entrada. Por el contrario, escribirá datos en un destino de fuente de datos. A veces se abre un flujo de salida y la fuente de datos puede ser un archivo, una memoria o una red.

1. ¿Qué es el flujo de bytes?

La unidad básica del flujo de bytes es el byte (Byte), y un byte suele ser de 8 bits, que se utiliza para procesar (datos) binarios. El flujo de bytes tiene dos clases base: InputStream (flujo de bytes de entrada) y OutputStream (flujo de bytes de salida).

El diagrama de relación de herencia de flujos de bytes de uso común se muestra en la siguiente figura:

                   

Entre ellos, InputStream se usa para operaciones de lectura y OutputStream se usa para operaciones de escritura.

2. ¿Qué es una secuencia de personajes?

La unidad básica del flujo de caracteres es Unicode, el tamaño es de dos bytes (Byte), generalmente se usa para procesar datos de texto. Dos clases base de flujos de caracteres: Lector (flujo de caracteres de entrada) y Escritor (flujo de caracteres de salida).

El diagrama de relación de herencia de flujos de caracteres de uso común se muestra en la siguiente figura:

                   

3. Clasificación de corrientes

Las corrientes se pueden clasificar según diferentes dimensiones, como la dirección de la corriente, la unidad de transmisión o la función de la corriente, como las siguientes.

① Clasificado por dirección de flujo

  • Flujo de salida: OutputStream y Writer son clases base.
  • Flujo de entrada: InputStream y Reader son clases base.

② Clasificación por unidad de datos de transmisión

  • Flujo de bytes: OutputStream y InputStream son clases base.
  • Flujo de caracteres: Writer y Reader son clases base.

③ Clasificación según función

  • Flujo de bytes: los datos se pueden leer y escribir desde o hacia un lugar específico (nodo).
  • Flujo de procesamiento: es la conexión y encapsulación de un flujo existente, y los datos se leen y escriben a través de la llamada de función del flujo encapsulado.

PD : normalmente clasificamos las transmisiones por la unidad de datos transmitidos.

4. 6 formas de escribir archivos

El método de escritura de archivos se deriva principalmente de las subclases de Writer de flujo de caracteres y OutputStream del flujo de bytes de salida, como se muestra en la siguiente figura:

                  

La clase marcada ✅ arriba es la clase utilizada para implementar la escritura de archivos. Además, JDK 1.7 también proporciona la clase Archivos para implementar varias operaciones en archivos. A continuación, veámoslas por separado.

Método 1: escritor de archivos

FileWriter es miembro del sistema "flujo de caracteres" y también es la clase básica para la escritura de archivos. Contiene 5 constructores, que pueden pasar una ubicación de archivo específica o un objeto de archivo. El segundo parámetro indica si se debe agregar un archivo. El valor predeterminado Falso significa reescribir el contenido del archivo en lugar de agregar el contenido del archivo (sobre cómo agregar archivos, hablaremos de ello más adelante).

                      

 La clase FileWriter se implementa de la siguiente manera:

/** 
  * 方法 1:使用 FileWriter 写文件 
  * @param filepath 文件目录 
  * @param content  待写入内容 
  * @throws IOException 
  */ 
public static void fileWriterMethod(String filepath, String content) throws IOException { 
    try (FileWriter fileWriter = new FileWriter(filepath)) { 
        fileWriter.append(content); 
    } 
} 

Solo necesita pasar la ruta del archivo específico y el contenido a escribir, el código de llamada es el siguiente:

public static void main(String[] args) { 
    fileWriterMethod("/Users/mac/Downloads/io_test/write1.txt", "哈喽,Java中文社群."); 
} 

Luego abrimos el archivo escrito y los resultados son los siguientes:

                  

  • Con respecto a la liberación de recursos: en versiones superiores a JDK 7, solo necesitamos usar try-with-resource para liberar recursos, como usar try (FileWriter fileWriter = new FileWriter (filepath)) {...} La liberación automática de recursos de FileWriter puede ser realizado.

Método 2: escritor almacenado en búfer

BufferedWriter también es miembro del sistema de flujo de caracteres. A diferencia de FileWriter, BufferedWriter tiene su propio búfer, por lo que tiene un mayor rendimiento al escribir archivos (los dos se probarán a continuación).

Pequeño punto de conocimiento: búfer

Un búfer, también conocido como caché, es parte del espacio de memoria. Es decir, se reserva un determinado espacio de almacenamiento en el espacio de memoria, y estos espacios de almacenamiento se utilizan para almacenar en el búfer datos de entrada o salida, y esta parte del espacio reservado se denomina búfer.

Ventajas del buffer

Tomando como ejemplo la escritura de un flujo de archivos, si no usamos el búfer, entonces la CPU interactuará con el dispositivo de almacenamiento de baja velocidad, es decir, el disco para cada operación de escritura, y la velocidad de escritura de todo el archivo será estar limitado por el dispositivo de almacenamiento de baja velocidad (disco). Pero si se utiliza el búfer, cada operación de escritura primero guardará los datos en la memoria del búfer de alta velocidad, y cuando los datos en el búfer alcancen un cierto umbral, el archivo se escribirá en el disco a la vez. Debido a que la velocidad de escritura de la memoria es mucho más rápida que la velocidad de escritura del disco, cuando hay un búfer, la velocidad de escritura del archivo mejora enormemente.

Después de comprender las ventajas del área de caché, volvamos al tema de este artículo. A continuación, usamos BufferedWriter para escribir archivos. El código de implementación es el siguiente:

/** 
 * 方法 2:使用 BufferedWriter 写文件 
 * @param filepath 文件目录 
 * @param content  待写入内容 
 * @throws IOException 
 */ 
public static void bufferedWriterMethod(String filepath, String content) throws IOException { 
    try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filepath))) { 
        bufferedWriter.write(content); 
    } 
} 

El código de llamada es similar al método 1, por lo que no lo repetiré aquí.

Método 3: PrintWriter

PrintWriter también es miembro del sistema de flujo de caracteres. Aunque se llama "flujo de impresión de caracteres", también se puede utilizar para escribir archivos. El código de implementación es el siguiente:

/** 
 * 方法 3:使用 PrintWriter 写文件 
 * @param filepath 文件目录 
 * @param content  待写入内容 
 * @throws IOException 
 */ 
public static void printWriterMethod(String filepath, String content) throws IOException { 
    try (PrintWriter printWriter = new PrintWriter(new FileWriter(filepath))) { 
        printWriter.print(content); 
    } 
} 

Se puede ver en el código anterior que tanto PrintWriter como BufferedWriter deben completar la llamada según la clase FileWriter.

Método 4: FileOutputStream

Los tres ejemplos anteriores tratan sobre algunas operaciones de escritura de secuencias de caracteres en archivos, y luego usaremos secuencias de bytes para completar la escritura de archivos. Usaremos el método getBytes () que viene con String para convertir la cadena en un archivo binario primero y luego escribiremos el archivo. Su código de implementación es el siguiente:

/** 
 * 方法 4:使用 FileOutputStream 写文件 
 * @param filepath 文件目录 
 * @param content  待写入内容 
 * @throws IOException 
 */ 
public static void fileOutputStreamMethod(String filepath, String content) throws IOException { 
    try (FileOutputStream fileOutputStream = new FileOutputStream(filepath)) { 
        byte[] bytes = content.getBytes(); 
        fileOutputStream.write(bytes); 
    } 
} 

Método 5: flujo de salida almacenado en búfer

BufferedOutputStream es miembro del sistema de flujo de bytes, la diferencia con FileOutputStream es que tiene una función de búfer, por lo que su rendimiento es mejor, su código de implementación es el siguiente:

/** 
 * 方法 5:使用 BufferedOutputStream 写文件 
 * @param filepath 文件目录 
 * @param content  待写入内容 
 * @throws IOException 
 */ 
public static void bufferedOutputStreamMethod(String filepath, String content) throws IOException { 
    try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( 
            new FileOutputStream(filepath))) { 
        bufferedOutputStream.write(content.getBytes()); 
    } 
} 

Método 6: archivos

El siguiente método de operación es diferente del código anterior, y luego usaremos una nueva clase de operación de archivos Archivos proporcionada en JDK 7 para realizar la escritura de archivos.

La clase Archivos es una nueva clase de operación de archivos agregada por JDK 7. Proporciona una gran cantidad de métodos para procesar archivos, como copiar, leer, escribir, obtener atributos de archivos y recorrer rápidamente directorios de archivos, estos métodos son muy útiles. Facilita el funcionamiento del archivo, y su código de implementación es el siguiente:

/** 
 * 方法 6:使用 Files 写文件 
 * @param filepath 文件目录 
 * @param content  待写入内容 
 * @throws IOException 
 */ 
public static void filesTest(String filepath, String content) throws IOException { 
    Files.write(Paths.get(filepath), content.getBytes()); 
}

Todos los métodos anteriores pueden realizar la escritura de archivos, entonces, ¿qué método tiene mayor rendimiento? Probémoslo a continuación.

5. Pruebas de rendimiento

Primero construyamos una cadena relativamente grande, luego usemos los seis métodos anteriores para probar la velocidad de escritura del archivo y finalmente imprimamos los resultados. El código de prueba es el siguiente:

import java.io.*; 
import java.nio.file.Files; 
import java.nio.file.Paths; 
 
public class WriteExample { 
    public static void main(String[] args) throws IOException { 
        // 构建写入内容 
        StringBuilder stringBuilder = new StringBuilder(); 
        for (int i = 0; i < 1000000; i++) { 
            stringBuilder.append("ABCDEFGHIGKLMNOPQRSEUVWXYZ"); 
        } 
        // 写入内容 
        final String content = stringBuilder.toString(); 
        // 存放文件的目录 
        final String filepath1 = "/Users/mac/Downloads/io_test/write1.txt"; 
        final String filepath2 = "/Users/mac/Downloads/io_test/write2.txt"; 
        final String filepath3 = "/Users/mac/Downloads/io_test/write3.txt"; 
        final String filepath4 = "/Users/mac/Downloads/io_test/write4.txt"; 
        final String filepath5 = "/Users/mac/Downloads/io_test/write5.txt"; 
        final String filepath6 = "/Users/mac/Downloads/io_test/write6.txt"; 
 
        // 方法一:使用 FileWriter 写文件 
        long stime1 = System.currentTimeMillis(); 
        fileWriterTest(filepath1, content); 
        long etime1 = System.currentTimeMillis(); 
        System.out.println("FileWriter 写入用时:" + (etime1 - stime1)); 
 
        // 方法二:使用 BufferedWriter 写文件 
        long stime2 = System.currentTimeMillis(); 
        bufferedWriterTest(filepath2, content); 
        long etime2 = System.currentTimeMillis(); 
        System.out.println("BufferedWriter 写入用时:" + (etime2 - stime2)); 
 
        // 方法三:使用 PrintWriter 写文件 
        long stime3 = System.currentTimeMillis(); 
        printWriterTest(filepath3, content); 
        long etime3 = System.currentTimeMillis(); 
        System.out.println("PrintWriterTest 写入用时:" + (etime3 - stime3)); 
 
        // 方法四:使用 FileOutputStream  写文件 
        long stime4 = System.currentTimeMillis(); 
        fileOutputStreamTest(filepath4, content); 
        long etime4 = System.currentTimeMillis(); 
        System.out.println("FileOutputStream 写入用时:" + (etime4 - stime4)); 
 
        // 方法五:使用 BufferedOutputStream 写文件 
        long stime5 = System.currentTimeMillis(); 
        bufferedOutputStreamTest(filepath5, content); 
        long etime5 = System.currentTimeMillis(); 
        System.out.println("BufferedOutputStream 写入用时:" + (etime5 - stime5)); 
 
        // 方法六:使用 Files 写文件 
        long stime6 = System.currentTimeMillis(); 
        filesTest(filepath6, content); 
        long etime6 = System.currentTimeMillis(); 
        System.out.println("Files 写入用时:" + (etime6 - stime6)); 
 
    } 
 
    /** 
     * 方法六:使用 Files 写文件 
     * @param filepath 文件目录 
     * @param content  待写入内容 
     * @throws IOException 
     */ 
    private static void filesTest(String filepath, String content) throws IOException { 
        Files.write(Paths.get(filepath), content.getBytes()); 
    } 
 
    /** 
     * 方法五:使用 BufferedOutputStream 写文件 
     * @param filepath 文件目录 
     * @param content  待写入内容 
     * @throws IOException 
     */ 
    private static void bufferedOutputStreamTest(String filepath, String content) throws IOException { 
        try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( 
                new FileOutputStream(filepath))) { 
            bufferedOutputStream.write(content.getBytes()); 
        } 
    } 
 
    /** 
     * 方法四:使用 FileOutputStream  写文件 
     * @param filepath 文件目录 
     * @param content  待写入内容 
     * @throws IOException 
     */ 
    private static void fileOutputStreamTest(String filepath, String content) throws IOException { 
        try (FileOutputStream fileOutputStream = new FileOutputStream(filepath)) { 
            byte[] bytes = content.getBytes(); 
            fileOutputStream.write(bytes); 
        } 
    } 
 
    /** 
     * 方法三:使用 PrintWriter 写文件 
     * @param filepath 文件目录 
     * @param content  待写入内容 
     * @throws IOException 
     */ 
    private static void printWriterTest(String filepath, String content) throws IOException { 
        try (PrintWriter printWriter = new PrintWriter(new FileWriter(filepath))) { 
            printWriter.print(content); 
        } 
    } 
 
    /** 
     * 方法二:使用 BufferedWriter 写文件 
     * @param filepath 文件目录 
     * @param content  待写入内容 
     * @throws IOException 
     */ 
    private static void bufferedWriterTest(String filepath, String content) throws IOException { 
        try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filepath))) { 
            bufferedWriter.write(content); 
        } 
    } 
 
    /** 
     * 方法一:使用 FileWriter 写文件 
     * @param filepath 文件目录 
     * @param content  待写入内容 
     * @throws IOException 
     */ 
    private static void fileWriterTest(String filepath, String content) throws IOException { 
        try (FileWriter fileWriter = new FileWriter(filepath)) { 
            fileWriter.append(content); 
        } 
    } 
} 

Antes de ver los resultados, vayamos a la carpeta correspondiente para verificar si los archivos escritos son normales, como se muestra en la siguiente figura:

                   

 De los resultados anteriores se puede ver que cada método normalmente escribe 26 MB de datos y los resultados finales de su ejecución se muestran en la siguiente figura:

                   

De los resultados anteriores se puede ver que la velocidad de operación del flujo de caracteres es la más rápida, esto se debe a que el código que probamos esta vez opera en una cadena, por lo que cuando se usa un flujo de bytes, la cadena debe convertirse en un el flujo de bytes primero, por lo que no hay ventaja en la eficiencia de ejecución.

De los resultados anteriores, se puede ver que el mejor rendimiento es el flujo de escritura de cadenas BufferedWriter con un búfer, y el rendimiento más lento es Archivos.

PD : Los resultados de la prueba anteriores solo son válidos para el escenario de operación de cadena. Si la operación es un archivo binario, entonces se debe usar el flujo de bytes BufferedOutputStream con un búfer.

6. Ampliar conocimientos: adición de contenidos

El código anterior reescribirá el archivo. Si solo desea agregar contenido sobre la base original, debe establecer un parámetro de adición en verdadero al crear la secuencia de escritura. Por ejemplo, si usamos FileWriter para implementar la adición de archivos, el código de implementación es así:

public static void fileWriterMethod(String filepath, String content) throws IOException { 
    // 第二个 append 的参数传递一个 true = 追加文件的意思 
    try (FileWriter fileWriter = new FileWriter(filepath, true)) { 
        fileWriter.append(content); 
    } 
} 

Si está utilizando BufferedWriter o PrintWriter, también debe establecer un parámetro adicional de agregar en verdadero al crear una nueva clase FileWriter. El código de implementación es el siguiente:

try (BufferedWriter bufferedWriter = new BufferedWriter( 
    new FileWriter(filepath, true))) { 
    bufferedWriter.write(content); 
} 

En comparación, la clase Files es más especial para implementar escritura adicional de archivos. Necesita pasar un parámetro adicional de StandardOpenOption.APPEND al llamar al método de escritura. Su código de implementación es el siguiente:

Files.write(Paths.get(filepath), content.getBytes(), StandardOpenOption.APPEND); 

7. Resumen

En este artículo, hemos mostrado 6 métodos para escribir archivos, que se dividen en 3 categorías: escritura de flujo de caracteres, escritura de flujo de bytes y escritura de clases de Archivos. La operación más conveniente es la clase Archivos, pero su rendimiento no es muy bueno. Si se requiere rendimiento, se recomienda utilizar una secuencia con un búfer para completar la operación, como BufferedWriter o BufferedOutputStream. Si el contenido a escribir es una cadena, se recomienda utilizar BufferedWriter, y si el contenido a escribir es un archivo binario, se recomienda utilizar BufferedOutputStream.

Supongo que te gusta

Origin blog.csdn.net/zs319428/article/details/119926133
Recomendado
Clasificación