In the previous problem, Reading/writing text files efficiently, we talked about buffering streaming (for a clear picture, consider reading that problem before this one). Things work the same for binary files too, and so we can jump directly into some examples.
Let's consider the following binary file and its size in bytes:
Path binaryFile = Paths.get(
"build/classes/modern/challenge/Main.class");
int fileSize = (int) Files.readAttributes(
binaryFile, BasicFileAttributes.class).size();
We can read the file's content in a byte[] via FileInputStream (this doesn't use buffering):
final byte[] buffer = new byte[fileSize];
try (InputStream is = new FileInputStream(binaryFile.toString())) {
int i;
while ((i = is.read(buffer)) != -1) {
System.out.print(" Reading ... ");
}
}
However, the preceding example isn't very efficient. Achieving high efficiency when it comes to reading the buffer.length bytes from this input stream into a byte array can be done via BufferedInputStream, as follows:
final byte[] buffer = new byte[fileSize];
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(binaryFile.toFile()))) {
int i;
while ((i = bis.read(buffer)) != -1) {
System.out.print(" Reading ... " + i);
}
}
FileInputStream can be obtained via the Files.newInputStream() method as well. The advantage of this method consists of the fact that it supports Path directly:
final byte[] buffer = new byte[fileSize];
try (BufferedInputStream bis = new BufferedInputStream(
Files.newInputStream(binaryFile))) {
int i;
while ((i = bis.read(buffer)) != -1) {
System.out.print(" Reading ... " + i);
}
}
If the file is too large to fit in a buffer of the file size, then it is preferable to read it via a smaller buffer with a fixed size (for example, 512 bytes) and the read() flavors, which are as follows:
- read(byte[] b)
- read(byte[] b, int off, int len)
- readNBytes(byte[] b, int off, int len)
- readNBytes(int len)
Alternatively, if our goal is to read the input stream as a byte array, we can rely on ByteArrayInputStream (it uses an internal buffer, so there is no need to use BufferedInputStream):
final byte[] buffer = new byte[fileSize];
try (ByteArrayInputStream bais = new ByteArrayInputStream(buffer)) {
int i;
while ((i = bais.read(buffer)) != -1) {
System.out.print(" Reading ... ");
}
}
The preceding approaches are a good fit for raw binary data, but sometimes, our binary files contain certain data (for example, ints, floats, and so on). In such cases, DataInputStream and DataOutputStream provide convenient methods for reading and writing certain data types. Let's consider that we have a file, data.bin, that contains float numbers. We can efficiently read it as follows:
Path dataFile = Paths.get("data.bin");
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(Files.newInputStream(dataFile)))) {
while (dis.available() > 0) {
float nr = dis.readFloat();
System.out.println("Read: " + nr);
}
}
Now, let's see how we can read binary files directly into memory.