Project Technical Lead
Java files handling: How to write Java files quickly and efficiently
If you read our previous article How to read files in Java quickly and efficiently you already know that there are many different ways to read files in Java. Depending on the type of file and its size, you need to choose the right approach to the solution, because it may take too long to load the files.
Similarly, there are many ways you can write data to a file in Java. Certain implementations may be better suited for a given solution than others, depending on the type and amount of data you write to the file. When writing to a file, we need to know in advance whether we are going to write our data to a text file or a binary file. In the case of a text file, we will focus on the use of classes such as: Files, Writer, OutputStreamWriter, FileWriter, PrintWriter, BufferedWriter. For binary files, we use classes such as: OutputStream, FileOutputStream, BufferedOutputStream, ByteOutputStream, DataOutputStream. There are also external third-party libraries available for working with files, which we won’t deal with today.
Each of the file handling classes mentioned above has its use for specific cases. In general, however, we use binary data to write OutputStream class and text Writer class.
Most of the pre-defined classes for reading and writing files in Java are located in the packages java.io and java.nio.file. With the introduction of the new Java NIO.2 (New I/O) file API, the situation became even more complicated, and how to work with files quickly and efficiently is often a question on programming forums even among experienced developers.
Writing data in Java can also get complicated, so in our article we will focus on 2 basic data writing scenarios that will cover 90 percent of use cases, which are:
- The simplest way to write once to both binary and text files.
- Continuous writing to file in parts
Java 7 brought the long-awaited NIO.2 File APIwhich, besides a lot of functionality, also brought a helper class java.nio.file.Files, which can be used to write a byte array, a string (String) or a list of strings to a text/binary file with a single command.
Writing a byte array to a binary file
You can write a byte array to a file as follows:
import java.nio.file.Files;
import java.nio.file.Path;
String fileName = "fileName.dat";
byte[] data = {72, 101, 108, 108, 111};
Files.write(Path.of(fileName), data);
The Path class (available from Java 11 onwards) represents an abstraction of a file and contains the path to the file in the file system.
Writing a variable of the String type to a text file
Writing a variable of type String to a file is very easy since Java 11:
import java.nio.file.Files;
import java.nio.file.Path;
String fileName = "fileName.txt";
String text = "Hello";
Files.writeString(Path.of(fileName), text);
Writing a list of String variables to a text file
Often we don’t write one line to a text file, but several lines at a time. With the following command you can write a list or more precisely Iterable<? extends CharSequence>) to a text file.
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
String fileName = "fileName.txt";
List<String> lines = new ArrayList<>(Arrays.asList("msg", "life"));
Files.write(Path.of(fileName), lines);
Writing to text file using String stream
There is no method that writes directly from the String-stream to the file, but there is a little trick that can be used to achieve this:
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Stream;
String fileName = "fileName.txt";
Stream<String> lines = new ArrayList<>(Arrays.asList("msg", "life")).stream();
Files.write(Path.of(fileName), (Iterable<String>) lines::iterator);
We use the Files.write() method as in the previous example, but it only accepts the Iterable<String> argument. The stream itself is not iterable in Java 8 because it can only be called once (repeated calls would result in an IllegalStateException). Thus, it cannot be used as a parameter. There is, however, a functional Iterable interface whose only method returns an Iterator. Therefore, we can pass a reference to the lines.iterator() method, which will also return an iterator as an Iterable.
In this section, we have been introduced to the helper methods of the java.nio.file.Files class. These methods are suitable for all use cases where the data we want to write to a file is completely stored in memory and the data range is only a few kilobytes.
In other cases, and especially when the file is written repeatedly in parts, it is better to use one of the predefined OutputStream classes. In the next section we will explain how to do this.
Writing quickly to binary files using BufferedOutputStream
The main class for writing binary data to a file is FileOutputStreamwhich allows you to write a byte to a file, or an array of bytes. Writing individual bytes is a very expensive operation, so we recommend using a buffer and writing the file in blocks (called buffering), which is significantly faster. On most systems, the optimal buffer size is 8 KB, so Java uses this value as the default. For buffered notation we use the class BufferedOutputStream as follows.
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
String fileName = "fileName.dat";
byte[] bytes;
try (FileOutputStream out = new FileOutputStream(fileName);
BufferedOutputStream bout = new BufferedOutputStream(out)) {
while((bytes = getOutputData()) != -1) {
bout.write(bytes);
}
}
In this example, the getOuputData() method (its implementation is not important) returns the parts of the file that are written sequentially.
Writing quickly to text files using BufferedWriter
To write text to a file, the text must first be converted to binary data. The character to byte conversion is the responsibility of the class OutputStreamWriter. Class FileWriter combines FileOutputStream and OutputStreamWriter. For faster writing of files we use the class BufferedWriterwhich wraps the class FileWriter and allows to use an 8 KB buffer along with an additional buffer for encoded characters.
import java.io.BufferedWriter;
import java.io.FileWriter;
String fileName = "fileName.txt";
try (FileWriter writer = new FileWriter(fileName);
BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
String line;
while ((line = getLine()) != -1) {
bufferedWriter.write(line);
}
}
In this example, the getLine() method (its implementation is not important) returns the lines of the file that is sequentially written.
In the previous examples, we did not close the created streams. After leaving the try block, the close() method is automatically called to close both streams, so it is not explicitly stated or needed.
Conclusion
In this article, we have shown the most common scenarios of writing data to a file. Smaller files that we have prepared in memory can be written using the methods of the Files class, and larger files are written in chunks using a buffer.
If you’re a Java developer looking for a job, check out our employee benefits and respond to our job offers.