nio

Java Nio FileChannel Example

The FileChannel is a Java Nio Channel implementation for working with a file. It facilitates reading, writing, mapping and manipulating a file.

The examples in this tutorial will be demonstrated via test cases with no explicit Charset specified when encoding and decoding text from ByteBuffers.
 
 
 
 
 
 

1. Introduction

The FileChannel has a rich hierarchy of interfaces extending to it a diverse set of behaviors and provides a handle to access a rich set of meta data properties of the file including time stamps for creation, modification, size etc.

FileChannels are safe for multi-threaded use and will allow certain operations (those that use explicit positions) to execute concurrently while those that involve the intrinsic positionmarker will execute serially.

The view of the underlying file is guaranteed to be consistent with other views of the same file within the same Java process (Threads) but the same guarantee is not extended to other concurrently executing programs.

A FileChannel is created by executing one of the open(..)methods of the FileChannel class or by calling getChannel(...) on any either FileInputStream, FileOutputStream or RandomAccessFile. The positionmarker of the FileChannel and ultimately the state of the FileChannel is coupled to the state of the class on which getChannel(...)was called.

Opening a FileChannel via one of the open(...)methods allows for the provision of a mode in which the Channel is opened. This is encapsulated very nicely for us via the StandardOpenOption enumeration.

If we obtained the FileChannel via the getChannel(...)method of FileInputStream then the FileChannel is opened for reading and will throw a NonWritableChannelException if you attempt to write to it, the same bodes for opening a FileChannel with a StandardOpenOption#READ and attempting to write to it. If we obtained a FileChannel via the getChannel(...)method of FileOutputStream then the FileChannel is opened for writing and reading. We need to be explicit about our StandardOpenOption’s when getting a FileChannel via one of the open(...)methods.

2. Technologies used

The example code in this article was built and run using:

  • Java 1.8.101 (1.8.x will do fine)
  • Maven 3.3.9 (3.3.x will do fine)
  • Spring source tool suite 4.6.3 (Any Java IDE would work)
  • Ubuntu 16.04 (Windows, Mac or Linux will do fine)

3. Reading

Reading from a FileChannel

    @Test
    public void viaFileInputStream() throws IOException {
        final StringBuilder data = new StringBuilder();
        final FileInputStream fis = new FileInputStream(new File(SRC));

        try (FileChannel channel = fis.getChannel()) {
            readFromChannel(channel, data);
        } finally {
            if (!Objects.isNull(fis)) {
                fis.close();
            }
        }

        assertEquals("Invalid content", CONTENTS.toString(), data.toString().trim());
    }

    @Test
    public void readOk() throws IOException {
        final StringBuilder data = new StringBuilder();
        try (FileChannel channel = FileChannel.open(Paths.get(SRC), StandardOpenOption.READ)) {
            readFromChannel(channel, data);
        }

        assertEquals("Invalid content", CONTENTS.toString(), data.toString().trim());
    }

    @Test(expected = NoSuchFileException.class)
    public void readNoSuchFile() throws IOException {
        final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);

        try (FileChannel channel = FileChannel.open(Paths.get("/tmp/nosuchfile.txt"), StandardOpenOption.READ)) {
            channel.read(buffer);
        }
    }

    @Test(expected = NonWritableChannelException.class)
    public void tryWrite() throws IOException {
        try (FileChannel channel = FileChannel.open(Paths.get(SRC), StandardOpenOption.READ)) {
            channel.write(ByteBuffer.wrap("Trying to write this text".getBytes()));
        }
    }

    @Test(expected = NonWritableChannelException.class)
    public void tryWriteViaFileInputStream() throws IOException {
        final FileInputStream fis = new FileInputStream(new File(SRC));

        try (FileChannel channel = fis.getChannel()) {
            channel.write(ByteBuffer.wrap("Trying to write this text".getBytes()));
        } finally {
            if (!Objects.isNull(fis)) {
                fis.close();
            }
        }
    }

    private void readFromChannel(final FileChannel channel, final StringBuilder data) throws IOException {
        final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);

        int bytes = channel.read(buffer);
        while (bytes >= 0) {
            if (bytes > 0) {
                transferTo(buffer, data);
            }

            buffer.clear();
            bytes = channel.read(buffer);
        }
    }

4. Writing

Writing to a FileChannel

    @Test
    public void viaFileOutuputStream() throws IOException {
        final ByteBuffer buffer = ByteBuffer.wrap(CONTENTS.getBytes());
        final FileOutputStream fos = new FileOutputStream(new File(TARGET));

        int bytes = 0;
        try (FileChannel channel = fos.getChannel()) {
            bytes = writeToChannel(channel, buffer);
        } finally {
            if (!Objects.isNull(fos)) {
                fos.close();
            }
        }

        assertTrue("Invalid amount of bytes written", CONTENTS.getBytes().length == bytes);
    }

    @Test
    public void writeOk() throws IOException {
        final ByteBuffer buffer = ByteBuffer.wrap(CONTENTS.getBytes());

        int bytes = 0;
        try (FileChannel channel = FileChannel.open(Paths.get(TARGET), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
            bytes = writeToChannel(channel, buffer);
        }

        assertTrue("Invalid amount of bytes written", CONTENTS.getBytes().length == bytes);
    }

    @Test(expected = NoSuchFileException.class)
    public void writeNoSuchFile() throws IOException {
        try (FileChannel channel = FileChannel.open(Paths.get("/tmp/nosuchfile.txt"), StandardOpenOption.WRITE)) {
        }
    }

    private int writeToChannel(final FileChannel channel, final ByteBuffer buffer) throws IOException {
        int bytes = 0;
        while (buffer.hasRemaining()) {
            bytes += channel.write(buffer);
        }

        return bytes;
    }
  • line 2: defines a method that gets a FileChannel from a FileOutputStream and writes some content to the FileChannel
  • line 19: defines a method that gets a FileChannel via the open(...)method and writes some content to the FileChannel. Two StandardOpenOption settings are specified indicating that the file should be created and that the FileChannel should be opened in writemode
  • line 31: defines a method that attempts to write to a file that does not exist
  • lines 36-43: defines a method that writes the contents of the given ByteBuffer to the given FileChannel and returns the number of bytes that was written

5. Transferring

Transferring data between two FileChannels

    @Test
    public void transfer() throws IOException {
        try (FileChannel in = FileChannel.open(Paths.get(SRC), StandardOpenOption.READ, StandardOpenOption.CREATE);
                FileChannel out = FileChannel.open(Paths.get(TARGET), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
            long bytes = 0;
            while(bytes < in.size()) {
                bytes += out.transferFrom(in, bytes, in.size());
            }
            
            out.force(true);

            assertTrue("All bytes not transfered", in.size() == bytes);
        }
    }

line 2: defines a method that opens two FileChannels, one for reading from and one for writing to. The transferFrom(...)method is called on the target FileChannel which allows us to transfer all the bytes from the source FileChannel to the target FileChannel. It’s important to do this in a loop as not all the bytes are guaranteed to be transferred in a single call.

The transferFrom(...)and transferTo(...)methods exploit various OS optimizations (if available) and should probably be preferred when working with ReadableByteChannel and WritableByteChannel instances.

6. Mapping

Mapping the contents of a File into memory

    @Test
    public void mapReadonly() throws IOException {
        try (FileChannel channel = FileChannel.open(Paths.get(SRC), StandardOpenOption.READ)) {
            final MappedByteBuffer buffer = channel.map(MapMode.READ_ONLY, 0, channel.size());

            byte[] tmp = new byte[buffer.remaining()];
            buffer.get(tmp);

            assertTrue("Buffer not direct", buffer.isDirect());
            assertEquals("Content does not match", CONTENTS.toString(), new String(tmp).trim());
        }
    }

    @Test(expected = ReadOnlyBufferException.class)
    public void mapReadonlyBufferException() throws IOException {
        try (FileChannel channel = FileChannel.open(Paths.get(SRC), StandardOpenOption.READ)) {
            final MappedByteBuffer buffer = channel.map(MapMode.READ_ONLY, 0, channel.size());

            buffer.putChar('x');
        }
    }

    @Test
    public void mapWrite() throws IOException {
        try (FileChannel channel = FileChannel.open(Paths.get(SRC), StandardOpenOption.READ, StandardOpenOption.WRITE)) {
            final MappedByteBuffer buffer = channel.map(MapMode.READ_WRITE, 0, channel.size());

            buffer.clear();
            buffer.put(new StringBuilder(CONTENTS).reverse().toString().getBytes());
            channel.force(true);
        }

        try (FileChannel channel = FileChannel.open(Paths.get(SRC), StandardOpenOption.READ)) {
            final MappedByteBuffer buffer = channel.map(MapMode.READ_ONLY, 0, channel.size());

            byte[] tmp = new byte[buffer.remaining()];
            buffer.get(tmp);

            assertEquals("Contents does not match reverse", new StringBuilder(CONTENTS).reverse().toString(), new String(tmp));
        }
    }
  • line 2: defines a method that maps the contents (entire) of a file into memory in read only mode.
  • line 15: defines a method that attempts to mutate a MappedByteBuffer that was opened in READ_ONLYmode
  • line 24: defines a method that hat maps the entire contents of a file into memory and mutates the contents of it in memory

This type of behavior is especially useful with large files where a portion of a file (specified by byte ranges) can be mapped into memory and utilized at any given point in time. The FileChannel.MapMode specifies the following modes PRIVATE READ_ONLY READ_WRITE which need to correlate with the FileChannels StandardOpenOption mode otherwise a NonWritableChannelException or a NonReadableChannelException will be raised.

The MapMode in concert with the StandardOpenOptions allow finer granularity with permissible operations when mapping a files contents into memory.

7. Locking

Taking out a lock on a file region

    @Test
    public void exclusiveLock() throws IOException {
        try (FileChannel channel = FileChannel.open(Paths.get(SRC), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
                FileLock lock = channel.lock();) {
            assertTrue("Lock is not exclusive", !lock.isShared());
        }
    }

    @Test
    public void sharedLock() throws IOException {
        try (FileChannel channel = FileChannel.open(Paths.get(SRC), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
                FileLock lock = channel.lock(0, channel.size(), true)) {
            assertTrue("Lock is not shared", lock.isShared());
        }
    }
    
    @Test(expected = OverlappingFileLockException.class)
    public void testOverlappingLock() {
        final CountDownLatch innerThreadLatch = new CountDownLatch(1);
        final CountDownLatch testThreadLatch = new CountDownLatch(1);

        try (FileChannel channel = FileChannel.open(Paths.get(SRC), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {

            new Thread() {
                public void run() {
                    try {
                        channel.lock();
                        innerThreadLatch.countDown();
                        testThreadLatch.await();
                    } catch (OverlappingFileLockException | IOException | InterruptedException e) {
                        throw new RuntimeException("Unable to get lock on file for overlapping lock test", e);
                    }
                }
            }.start();

            innerThreadLatch.await();
            channel.lock();
        } catch (InterruptedException | IOException e) {
            throw new RuntimeException(e);
        } finally {
            testThreadLatch.countDown();
        }
    }

    @Test
    public void lockDifferentRegions() throws IOException {
        try (FileChannel channel = FileChannel.open(Paths.get(SRC), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
                FileLock lock1 = channel.lock(0, 5, true);
                FileLock lock2 = channel.lock(5, channel.size(), true)) {
            assertFalse("Locks same", lock1 == lock2);
        }
    }

    @Test(expected = OverlappingFileLockException.class)
    public void overlappingLockSameThread() throws IOException {
        try (FileChannel channel = FileChannel.open(Paths.get(SRC), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
                FileLock lock1 = channel.lock(0, channel.size(), true);
                FileLock lock2 = channel.lock(0, channel.size(), true)) {
        }
    }
  • line 2 & 10: defines a method for testing the sharedproperty of a FileLock
  • line 18 & 55: defines a method for testing for an OverlappingFileLockException from different and the same Threads
  • line 46: defines a method for showing that two different FileLocks can be taken out on two different regions (byte range) of the same file

An aside about FileLocks. Strictly not to be used as a sole means of concurrency in the JVM, however in concert with normal synchronization measures can prove effective when trying to control file region access. Inside a JVM process no two FileLocks can lock the same region.

FileLocks can be exclusive or shared:

  • Exclusive: prevents other processes / threads from acquiring any FileLock (shared or exclusive) on the same region of a file
  • Shared: prevents other processes / threads from acquiring an exclusive FileLock on the same region of a file but allows other processes / threads to acquire a shared FileLock on the same region of a file

8. Test cases

The project is a maven project and can be run from the command line by executing the following: mvn clean install. Alternatively one can run the code from within Eclipse.

9. Summary

In this example we learnt how to read from a file via a FileChannel, how to write to a file via a FileChannel, how to map part of a file into memory from a FileChannel, how to transfer data from one file to another via a FileChannel and how to lock regions of a file from a FileChannel.

10. Download the Source Code

This was a Java Nio FileChannel Example

Download
You can download the full source code of this example here: Java Nio FileChannel Example

JJ

Jean-Jay Vester graduated from the Cape Peninsula University of Technology, Cape Town, in 2001 and has spent most of his career developing Java backend systems for small to large sized companies both sides of the equator. He has an abundance of experience and knowledge in many varied Java frameworks and has also acquired some systems knowledge along the way. Recently he has started developing his JavaScript skill set specifically targeting Angularjs and also bridged that skill to the backend with Nodejs.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button