Six methods of writing files in JAVA

Posted by Crimpage on Tue, 21 Dec 2021 01:41:34 +0100

There are only two ways to operate files in Java: character stream and byte stream, and there are many implementation classes of byte stream and character stream. Therefore, we can choose a variety of classes to implement when writing files. In this article, we will review these methods and test their performance in order to select the best write method for us.

Before we begin, let's understand the definitions and differences of several basic concepts: stream, byte stream and character stream.

0. What is flow?

"Flow" in Java is an abstract concept and a metaphor. Just like water flow, water flow flows from one end to the other, while "water flow" in Java is data, and data will "flow" from one end to the other.

According to the direction of the stream, we can divide the stream into input stream and output stream. When the program needs to read data from the data source, an input stream will be opened. On the contrary, when writing data to a data source destination, an output stream will also be opened. The data source can be file, memory or network.

1. What is a byte stream?

The basic unit of byte stream is byte. A byte is usually 8 bits. It is used to process binary (data). Byte streams have two base classes: InputStream and OutputStream.

The inheritance diagram of common byte stream is shown in the following figure:

                   

Where InputStream is used for read operations and OutputStream is used for write operations.

2. What is character stream?

The basic unit of character stream is Unicode and its size is two bytes. It is usually used to process text data. Two base classes of character stream: Reader (input character stream) and writer (output character stream).

The inheritance diagram of common character stream is shown in the following figure:

                   

3. Classification of flow

Streams can be classified according to different dimensions. For example, they can be classified according to the direction of the stream, the transmission unit, and the function of the stream, such as the following.

① Classification by flow direction

  • Output stream: OutputStream and Writer are base classes.
  • Input stream: InputStream and Reader are base classes.

② Classification according to transmission data units

  • Byte stream: OutputStream and InputStream are base classes.
  • Character stream: Writer and Reader are base classes.

③ Classification according to function

  • Byte stream: data can be read and written from or to a specific place (node).
  • Processing flow: it refers to the connection and encapsulation of an existing flow, and data reading and writing are realized through the function call of the encapsulated flow.

PS: we usually classify streams in the unit of data transmission.

4. Six methods of writing files

The method of writing files mainly comes from the subclasses of character stream Writer and output byte stream OutputStream, as shown in the following figure:

                  

Above mark ✅ No. is the class used to write Files. In addition, the Files class is provided in JDK 1.7 to realize various operations on Files. Let's look at it separately.

Method 1: FileWriter

FileWriter is a member of the "character stream" system and the basic class for File writing. It contains five constructors, which can pass a specific File location or File object. The second parameter indicates whether to append the File. The default value is false, which means to rewrite the File content rather than append the File content (we will talk about how to append the File later).

                      

The FileWriter class is implemented as follows:

/** 
  * Method 1: use FileWriter to write files 
  * @param filepath File directory 
  * @param content  Content to be written 
  * @throws IOException 
  */ 
public static void fileWriterMethod(String filepath, String content) throws IOException { 
    try (FileWriter fileWriter = new FileWriter(filepath)) { 
        fileWriter.append(content); 
    } 
} 

Just pass in the specific file path and the content to be written. The calling code is as follows:

public static void main(String[] args) { 
    fileWriterMethod("/Users/mac/Downloads/io_test/write1.txt", "Hello,Java Chinese community."); 
} 

Then we open the written file and the results are as follows:

                  

  • About resource release: in versions above JDK 7, we only need to use try with resource to release resources, such as try (FileWriter fileWriter = new FileWriter(filepath)) {...} You can realize the automatic release of FileWriter resources.

Method 2: BufferedWriter

BufferedWriter is also a member of the character stream system. Different from FileWriter, BufferedWriter has its own buffer, so it has higher performance in writing files (the two will be tested below).

Tips: buffer

Buffer, also known as cache, is a part of memory space. In other words, a certain storage space is reserved in the memory space, which is used to buffer the input or output data. This part of the reserved space is called buffer.

Advantages of buffer

Take the file stream writing as an example. If we do not use the buffer, the CPU will interact with the low-speed storage device, that is, the disk, every time we write, and the speed of writing the file will be limited by the low-speed storage device (disk). However, if the buffer is used, each write operation will first save the data in the cache memory. When the data in the buffer reaches a certain threshold, the file will be written to the disk at one time. Because the write speed of memory is much faster than that of disk, the write speed of files is greatly improved when there is a buffer.

After understanding the advantages of cache, let's return to the topic of this article. Next, we use BufferedWriter to write files. The implementation code is as follows:

/** 
 * Method 2: write a file using BufferedWriter 
 * @param filepath File directory 
 * @param content  Content to be written 
 * @throws IOException 
 */ 
public static void bufferedWriterMethod(String filepath, String content) throws IOException { 
    try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filepath))) { 
        bufferedWriter.write(content); 
    } 
} 

The calling code is similar to method 1 and will not be repeated here.

Method 3: PrintWriter

PrintWriter is also a member of the character stream system. Although it is called "character print stream", it can also be used to write files. The implementation code is as follows:

/** 
 * Method 3: use PrintWriter to write files 
 * @param filepath File directory 
 * @param content  Content to be written 
 * @throws IOException 
 */ 
public static void printWriterMethod(String filepath, String content) throws IOException { 
    try (PrintWriter printWriter = new PrintWriter(new FileWriter(filepath))) { 
        printWriter.print(content); 
    } 
} 

As can be seen from the above code, both PrintWriter and BufferedWriter must complete the call based on the FileWriter class.

Method 4: FileOutputStream

The above three examples are about the operation of writing a file with a character stream, and next we will use a byte stream to write the file. We will use the getBytes() method of String to convert the String into a binary file first, and then write the file. Its implementation code is as follows:

/** 
 * Method 4: use FileOutputStream to write a file 
 * @param filepath File directory 
 * @param content  Content to be written 
 * @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); 
    } 
} 

Method 5: BufferedOutputStream

BufferedOutputStream is a member of the byte stream system. Different from FileOutputStream, it has its own buffer function, so its performance is better. Its implementation code is as follows:

/** 
 * Method 5: write a file using BufferedOutputStream 
 * @param filepath File directory 
 * @param content  Content to be written 
 * @throws IOException 
 */ 
public static void bufferedOutputStreamMethod(String filepath, String content) throws IOException { 
    try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( 
            new FileOutputStream(filepath))) { 
        bufferedOutputStream.write(content.getBytes()); 
    } 
} 

Method 6: Files

The next operation method is different from the previous code. Next, we use a new file operation class Files provided in JDK 7 to write Files.

Files class is a new class for operating files added by JDK 7. It provides a large number of methods for processing files, such as file copying, reading, writing, obtaining file attributes, quickly traversing file directories, etc. these methods greatly facilitate file operation. Its implementation code is as follows:

/** 
 * Method 6: use Files to write Files 
 * @param filepath File directory 
 * @param content  Content to be written 
 * @throws IOException 
 */ 
public static void filesTest(String filepath, String content) throws IOException { 
    Files.write(Paths.get(filepath), content.getBytes()); 
}

All of the above methods can write files. Which method has higher performance? Next, let's test it.

5. Performance test

Let's first build a relatively large string, then use the above six methods to test the file writing speed, and finally print the results. The test code is as follows:

import java.io.*; 
import java.nio.file.Files; 
import java.nio.file.Paths; 
 
public class WriteExample { 
    public static void main(String[] args) throws IOException { 
        // Build write content 
        StringBuilder stringBuilder = new StringBuilder(); 
        for (int i = 0; i < 1000000; i++) { 
            stringBuilder.append("ABCDEFGHIGKLMNOPQRSEUVWXYZ"); 
        } 
        // Write content 
        final String content = stringBuilder.toString(); 
        // Directory where files are stored 
        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"; 
 
        // Method 1: use FileWriter to write files 
        long stime1 = System.currentTimeMillis(); 
        fileWriterTest(filepath1, content); 
        long etime1 = System.currentTimeMillis(); 
        System.out.println("FileWriter Write time:" + (etime1 - stime1)); 
 
        // Method 2: use BufferedWriter to write files 
        long stime2 = System.currentTimeMillis(); 
        bufferedWriterTest(filepath2, content); 
        long etime2 = System.currentTimeMillis(); 
        System.out.println("BufferedWriter Write time:" + (etime2 - stime2)); 
 
        // Method 3: use PrintWriter to write files 
        long stime3 = System.currentTimeMillis(); 
        printWriterTest(filepath3, content); 
        long etime3 = System.currentTimeMillis(); 
        System.out.println("PrintWriterTest Write time:" + (etime3 - stime3)); 
 
        // Method 4: use FileOutputStream to write files 
        long stime4 = System.currentTimeMillis(); 
        fileOutputStreamTest(filepath4, content); 
        long etime4 = System.currentTimeMillis(); 
        System.out.println("FileOutputStream Write time:" + (etime4 - stime4)); 
 
        // Method 5: write a file using BufferedOutputStream 
        long stime5 = System.currentTimeMillis(); 
        bufferedOutputStreamTest(filepath5, content); 
        long etime5 = System.currentTimeMillis(); 
        System.out.println("BufferedOutputStream Write time:" + (etime5 - stime5)); 
 
        // Method 6: use Files to write Files 
        long stime6 = System.currentTimeMillis(); 
        filesTest(filepath6, content); 
        long etime6 = System.currentTimeMillis(); 
        System.out.println("Files Write time:" + (etime6 - stime6)); 
 
    } 
 
    /** 
     * Method 6: use Files to write Files 
     * @param filepath File directory 
     * @param content  Content to be written 
     * @throws IOException 
     */ 
    private static void filesTest(String filepath, String content) throws IOException { 
        Files.write(Paths.get(filepath), content.getBytes()); 
    } 
 
    /** 
     * Method 5: write a file using BufferedOutputStream 
     * @param filepath File directory 
     * @param content  Content to be written 
     * @throws IOException 
     */ 
    private static void bufferedOutputStreamTest(String filepath, String content) throws IOException { 
        try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( 
                new FileOutputStream(filepath))) { 
            bufferedOutputStream.write(content.getBytes()); 
        } 
    } 
 
    /** 
     * Method 4: use FileOutputStream to write files 
     * @param filepath File directory 
     * @param content  Content to be written 
     * @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); 
        } 
    } 
 
    /** 
     * Method 3: use PrintWriter to write files 
     * @param filepath File directory 
     * @param content  Content to be written 
     * @throws IOException 
     */ 
    private static void printWriterTest(String filepath, String content) throws IOException { 
        try (PrintWriter printWriter = new PrintWriter(new FileWriter(filepath))) { 
            printWriter.print(content); 
        } 
    } 
 
    /** 
     * Method 2: use BufferedWriter to write files 
     * @param filepath File directory 
     * @param content  Content to be written 
     * @throws IOException 
     */ 
    private static void bufferedWriterTest(String filepath, String content) throws IOException { 
        try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filepath))) { 
            bufferedWriter.write(content); 
        } 
    } 
 
    /** 
     * Method 1: use FileWriter to write files 
     * @param filepath File directory 
     * @param content  Content to be written 
     * @throws IOException 
     */ 
    private static void fileWriterTest(String filepath, String content) throws IOException { 
        try (FileWriter fileWriter = new FileWriter(filepath)) { 
            fileWriter.append(content); 
        } 
    } 
} 

Before viewing the results, we first go to the corresponding folder to see if the written file is normal, as shown in the following figure:

                   

It can be seen from the above results that 26 MB of data is normally written in each method, and their final execution results are shown in the following figure:

                   

From the above results, we can see that the operation speed of character stream is the fastest. This is because the code we tested this time operates on strings. Therefore, when using byte stream, we need to convert the string into byte stream first, so it does not have an advantage in execution efficiency.

From the above results, we can see that the string write stream BufferedWriter with buffer has the best performance, and the Files has the slowest performance.

PS: the above test results are only valid for string operation scenarios. If binary files are operated, the byte stream BufferedOutputStream with buffer should be used.

6. Expand knowledge: add content

The above code will rewrite the file. If you only want to add content on the original basis, you need to set an append parameter to true when creating the write stream. For example, if we use FileWriter to append the file, the implementation code is as follows:

public static void fileWriterMethod(String filepath, String content) throws IOException { 
    // The second append parameter passes a true = append file meaning 
    try (FileWriter fileWriter = new FileWriter(filepath, true)) { 
        fileWriter.append(content); 
    } 
} 

If BufferedWriter or PrintWriter is used, it is also necessary to set the parameter of append to true when building the new FileWriter class. The implementation code is as follows:

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

In contrast, if the Files class wants to implement the additional writing method of the file, it needs to pass an additional standardopenoption when calling the write method The parameter of append, and its implementation code is as follows:

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

7. Summary

In this article, we show six methods of writing Files, which are divided into three categories: character stream writing, byte stream writing and Files class writing. The most convenient operation is the Files class, but its performance is not very good. If performance is required, it is recommended to use streams with buffers to complete operations, such as BufferedWriter or BufferedOutputStream. BufferedWriter is recommended if the content to be written is a string. BufferedOutputStream is recommended if the content to be written is a binary file.

Topics: Java