How do I use ByteBuffer to process binary files?

Using ByteBuffer to process binary files is a core part of Java NIO (New I/O). It provides a more efficient way to handle raw bytes compared to traditional stream-based I/O by allowing direct interaction with memory and OS-level optimizations.

Here is a guide on how to effectively use ByteBuffer for binary file processing.

1. The Core Lifecycle of a Buffer

When processing files, you’ll constantly switch between “writing” to the buffer (filling it from a file) and “reading” from it (processing the bytes).

  1. Allocate: Create a buffer.
  2. Write/Fill: Put data into the buffer (using channel.read(buffer) or buffer.put()).
  3. Flip: Call flip() to switch from writing mode to reading mode.
  4. Read/Process: Get data out (using buffer.get()).
  5. Clear/Compact: Call clear() to prepare for the next fill.

2. Reading a Binary File

To read a file, you use a FileChannel to fill your ByteBuffer. For binary data, you can extract specific types like int, long, or double directly.

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

public class BinaryReader {
    public void readBinaryData(Path path) throws IOException {
        try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
            // Allocate a buffer (8KB is a common size)
            ByteBuffer buffer = ByteBuffer.allocate(8192);

            while (channel.read(buffer) != -1) {
                // 1. Prepare for reading
                buffer.flip();

                // 2. Process data (e.g., reading 4-byte integers)
                while (buffer.remaining() >= 4) {
                    int value = buffer.getInt(); 
                    System.out.println("Read value: " + value);
                }

                // 3. Prepare for next read from channel
                buffer.compact(); // Keeps unprocessed bytes at the start
            }
        }
    }
}

3. Writing a Binary File

When writing, you fill the buffer with values and then “drain” it into the FileChannel.

public void writeBinaryData(Path path) throws IOException {
    try (FileChannel channel = FileChannel.open(path, 
            StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // Put various binary types
        buffer.putInt(42);
        buffer.putDouble(3.14159);
        buffer.putLong(System.currentTimeMillis());

        // Prepare for the channel to read from this buffer
        buffer.flip();

        while (buffer.hasRemaining()) {
            channel.write(buffer);
        }
    }
}

4. Key Considerations for Binary Files

  • Byte Order (Endianness): Binary formats often specify a byte order (Big-Endian or Little-Endian). You can set this easily:
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
  • Direct vs. Heap Buffers:
    • ByteBuffer.allocate(size): Creates a buffer on the Java heap.
    • ByteBuffer.allocateDirect(size): Allocates memory outside the JVM heap. Use this for large, long-lived buffers or when performance is critical, as it allows the OS to perform I/O directly without extra memory copies.
  • Memory Mapping: For extremely large files (larger than your available RAM), use channel.map(). This maps the file directly into virtual memory, allowing you to treat the entire file like a huge ByteBuffer without manual read() calls.

Summary of Methods

Method Purpose
flip() Switches from writing to reading.
clear() Resets the buffer (doesn’t erase data, just pointers) for a fresh start.
compact() Moves leftover bytes to the start; useful if you didn’t finish reading everything.
rewind() Resets position to 0 so you can read the same data again.
get...() / put...() Typed methods (e.g., getInt, putLong) to handle primitive binary types.

How do I read last n characters from a file?

In the following post you will learn how to read last n characters from a file. The JDK 7 introduces a new SeekableByteChannel interface which allows its implementation to change the position and the size of the byte channel. One of its implementation is the FileChannel class (java.nio.channels.FileChannel).

The FileChannel class make it possible to get hold the current position of where we are going to read from or write to a file. The code snippet below shows you how you can read the last 1000 characters from a log file.

package org.kodejava.io;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class FileReadLastNCharacters {
    public static void main(String[] args) {
        // Defines the path to the log file and creates a ByteBuffer.
        Path logPath = Paths.get("C:/tools/apache-tomcat-10.0.11/logs/catalina.out");
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        try {
            // Creates FileChannel and open the file channel for read access.
            FileChannel channel = FileChannel.open(logPath, StandardOpenOption.READ);

            // Read a sequence of bytes from the channel into the buffer starting
            // at given file position, which is the channel size - 1000. Because
            // we are going to read the last 1000 characters from the file.
            channel.read(buffer, channel.size() - 1000);
            System.out.println("Characters = " + new String(buffer.array()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

The steps in the code snippet above are:

  • Get the path to the log file.
  • Create a ByteBuffer, a buffer where the read bytes to be transferred.
  • Using the FileChannel.open() method we open a file to be read and return a FileChannel object.
  • The read() method of the FileChannel reads a sequence of bytes from the channel and transfer them to the given buffer starting and the position defined by channel.size() - 1000. This method returns the number of bytes read, possible zero, or -1 if the given position is greater than or equal to the file’s current size.
  • Print out the buffered string.

How do I read data from a buffer into channel?

In this example you’ll see how to read data from buffer using FileChannel.write() method call. Reading from a buffer means that you are writing data into the channel object. In the snippet below the data from our dummy buffer will be read and written into the result.txt file.

package org.kodejava.io;

import java.io.File;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class BufferRead {
    public static void main(String[] args) throws Exception {
        FileChannel channel = null;

        try {
            // Define an output file and create an instance of FileOutputStream
            File file = new File("result.txt");
            FileOutputStream fos = new FileOutputStream(file);

            // Create a dummy ByteBuffer which value to be read into a channel.
            ByteBuffer buffer = ByteBuffer.allocate(256);
            buffer.put(new byte[]{65, 66, 67, 68, 69, 70, 71, 72, 73, 74});

            // Change the buffer from writing mode to reading mode.
            buffer.flip();

            // Gets the channel from the FileOutputStream object and read the
            // data available in buffer using channel.write() method.
            channel = fos.getChannel();
            int bytesWritten = channel.write(buffer);
            System.out.println("written : " + bytesWritten);
        } finally {
            if (channel != null && channel.isOpen()) {
                channel.close();
            }
        }
    }
}

How do I write data into ByteBuffer using put method?

The snippet below show you how to write some bytes into the java.nio.ByteBuffer object through a call to the put() method.

package org.kodejava.io;

import java.nio.ByteBuffer;

public class BufferPut {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(32);
        buffer.put((byte) 65);
        buffer.put((byte) 66);
        buffer.put((byte) 67);
        buffer.put((byte) 68);
        buffer.put((byte) 69);


        // Writes a sequence of bytes
        byte[] bytes = new byte[]{70, 71, 72, 73, 74};
        buffer.put(bytes);

        // Write to the beginning of the buffer
        buffer.put(0, (byte) 75);

        buffer.flip();

        while (buffer.hasRemaining()) {
            System.out.print((char) buffer.get());
        }
    }
}

How do I use a FileChannel to read data into a Buffer?

The example below show how to use a FileChannel to read some data into a Buffer. We create a FileChannel from a FileInputStream instance. Because a channel reads data into buffer we need to create a ByteBuffer and set its capacity. Read data from a channel into buffer using the FileChannel.read() method.

To read out the data from the buffer we need to flip the buffer first using the Buffer.flip() method. The method will change the buffer from writing-mode into reading-mode. After the entire buffer is read clear the buffer using the clear() method call.

package org.kodejava.io;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileRead {
    public static void main(String[] args) {
        String path = "D:/Temp/source.txt";
        try (FileInputStream fis = new FileInputStream(path);
             FileChannel fileChannel = fis.getChannel()) {

            ByteBuffer buffer = ByteBuffer.allocate(64);

            int bytesRead = fileChannel.read(buffer);
            while (bytesRead != -1) {
                buffer.flip();

                while (buffer.hasRemaining()) {
                    System.out.print((char) buffer.get());
                }

                buffer.clear();
                bytesRead = fileChannel.read(buffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}