Chapters
FileChannel is a SeekableByteChannel that is connected to a file. File channel can reposition its file pointer to any position in a file just like RandomAccessFile; can force file updates to go directly to the storage disk, can map region of files into memory, which is often efficient for large files; can lock a region of a file; and can access by multiple threads.
Even though file channel can be accessed by multiple threads, FileChannel operations are blocking operations. Although, according to the documentation:
"Other operations, in particular those that take an explicit position, may proceed concurrently; whether they in fact do so is dependent upon the underlying implementation and is therefore unspecified."
My explanation here is simplified. More information can be found in the documentation.
There are two ways of opening a FileChannel. We can use the
This example demonstrates opening a FileChannel using open() and reading file content using FileChannel.
In order to read and write from FileChannel, we need to use ByteBuffer. FileChannel reads and writes data from ByteBuffer. Then, in the example above, I write the read data to
Another way of opening a FileChannel is to open a file-stream-based class like FileOutputStream and use its
In the example above, file channels are automatically closed by try-with-resources. If you want to manually close a file channel, use the
Method form:
Sometimes, Operating systems may cache file changes instead of sending file changes directly to the file in the storage device. This method Forces any updates to this channel's file to be written to the storage device that contains it.
If this channel's file resides on a local storage device then when this method returns it is guaranteed that all changes made to the file since this channel was created, or since this method was last invoked, will have been written to that device.
This is useful for ensuring that critical information is not lost in the event of a system crash. If the file does not reside on a local device then no such guarantee is made. To enable the feature discussed above, just enable it by setting
Method Form:
Maps a region of this channel's file directly into memory. This method accepts three type of modes:
More information can be found in the documentation. More information about modes can be found in this documentation.
This example demonstrates map().
Now, in the example above, try changing the map mode to MapMode.READ_WRITE and run the example again. This time the content of
If you want the mapped data to be read-only, use MapMode.READ.
This method has two forms.
This example demonstrates both forms.
The result is going to be:
Method Form:
Truncates this channel's file to the given size. If the given size is less than the file's current size then the file is truncated, discarding any bytes beyond the new end of the file.
If the given size is greater than or equal to the file's current size then the file is not modified. In either case, if this channel's file position is greater than the given size then it is set to that size.
This example demonstrates truncate().
Then, test.txt has a file size of 26 bytes. In the example above, the file size will be reduced to 16 bytes.
Method Form:
Transfers bytes into this channel's file from the given readable byte channel. More information can be read in the documentation. This method can be used for transferring FileChannel data to another FileChannel. We can also use this method to transfer data from channels that implements ReadableByteChannel to this FileChannel.
This example demonstrates transferFrom().
Method Form:
Transfers bytes from this channel's file to the given writable byte channel. More information can be found in the documentation. This method is the inverted version of
This method demonstrates transferTo().
We can use two methods to lock file access of file channels.
These forms lock a region of a file. Their first forms lock the entire region of file. the
Shared lock enables thread to acquire overlapping shared locks. FileLock locks may overlap one other if the regions they're locking overlap. For example, lock1 locks position 5-10 then lock2 locks position 8-13. lock1 and lock2 are overlapping. If FileLock has shared lock, FileChannel that has lock2 can acquire lock1.
Note that shared lock can't acquire overlapping exclusive lock. Exclusive lock doesn't allow acquiring of either overlapping lock types. Also, remember that File locks are held on behalf of the entire Java virtual machine. They are not suitable for controlling access to a file by multiple threads within the same virtual machine. More information can be found in the documentation.
If you want locks that are suitable for a single virtual machine, you may consider using locks in java.util.concurrent.locks. The difference between
This snippet demonstrates lock(long position, long size, boolean shared).
FileChannel
FileChannel is a SeekableByteChannel that is connected to a file. File channel can reposition its file pointer to any position in a file just like RandomAccessFile; can force file updates to go directly to the storage disk, can map region of files into memory, which is often efficient for large files; can lock a region of a file; and can access by multiple threads.
Even though file channel can be accessed by multiple threads, FileChannel operations are blocking operations. Although, according to the documentation:
"Other operations, in particular those that take an explicit position, may proceed concurrently; whether they in fact do so is dependent upon the underlying implementation and is therefore unspecified."
My explanation here is simplified. More information can be found in the documentation.
Opening a FileChannel
There are two ways of opening a FileChannel. We can use the
open()
methods in the FileChannel class or use the getChannel()
from file-stream-based classes like RandomAccessFile, FileInputStream and others.
This example demonstrates opening a FileChannel using open() and reading file content using FileChannel.
import java.nio.channels.FileChannel; import java.nio.file.*; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.io.IOException; import java.io.ByteArrayOutputStream; public class SampleClass{ public static void main(String[] args) throws IOException{ Path source = Paths.get("C:\\test\\fileTest.txt"); if(!Files.exists(source)){ System.err.println("source doesn't exist!"); return; } String content = null; try(FileChannel fc = FileChannel.open(source, StandardOpenOption.READ); ByteArrayOutputStream out = new ByteArrayOutputStream()){ int buffer = (1024L > fc.size()) ? (int)fc.size() : 1024; ByteBuffer bb = ByteBuffer.allocate(buffer); while(fc.read(bb) > 0){ out.write(bb.array()); bb.clear(); } content = new String(out.toByteArray(), StandardCharsets.UTF_8); } if(content != null) System.out.println("Content: " + content); else System.err.println("content is null!"); } }In the example above, I use this form of
open()
:public static FileChannel open(Path path, OpenOption... options)throws IOException
StandardOpenOption.READ
grants read access to file channel. Read OpenOption and StandardOpenOption for more options that can be put in this method.
In order to read and write from FileChannel, we need to use ByteBuffer. FileChannel reads and writes data from ByteBuffer. Then, in the example above, I write the read data to
ByteArrayOutputStream
.
allocate()
method sets the buffer size of ByteBuffer. array()
method in ByteBuffer
wraps bytes in a byte[]
array. StandardCharsets.UTF_8
encodes bytes into UTF_8 characters. size()
method returns the current size of the file that a FileChannel is connected to.
Another way of opening a FileChannel is to open a file-stream-based class like FileOutputStream and use its
getChannel()
method. This example demonstrates getChannel()
and writing to a FileChannel.
import java.nio.channels.FileChannel; import java.nio.file.*; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.io.IOException; import java.io.FileOutputStream; public class SampleClass{ public static void main(String[] args) throws IOException{ Path source = Paths.get("C:\\test\\test.txt"); try(FileOutputStream out = new FileOutputStream(source.toFile())){ out.write( new String("ABCDEFGHIJK"). getBytes(StandardCharsets.UTF_8)); try(FileChannel fc = out.getChannel()){ ByteBuffer bb = ByteBuffer.wrap(new String("LMNOPQRSTUVWXYZ"). getBytes(StandardCharsets.UTF_8)); while(bb.hasRemaining()){ fc.write(bb); } } } System.out.println("Writing Complete!"); } } Result(C:\test\test.txt content) ABCDEFGHIJKLMNOPQRSTUVWXYZFileChannel that is opened using
getChannel()
follows the file pointer and mode of the source file-stream. That's why the written data in FileChannel didn't overwrite the written data in FileOutputStream because FileChannel followed the file pointer of FileOutputStream.
wrap(byte[] array)
method sets the new buffer's capacity and limit to the length of the array in the argument. More information can be found in the documentation. Remember that we can't open a file channel if the source stream is closed.
In the example above, file channels are automatically closed by try-with-resources. If you want to manually close a file channel, use the
close()
method.
force() Method
Method form:
public abstract void force(boolean metaData) throws IOException
Sometimes, Operating systems may cache file changes instead of sending file changes directly to the file in the storage device. This method Forces any updates to this channel's file to be written to the storage device that contains it.
If this channel's file resides on a local storage device then when this method returns it is guaranteed that all changes made to the file since this channel was created, or since this method was last invoked, will have been written to that device.
This is useful for ensuring that critical information is not lost in the event of a system crash. If the file does not reside on a local device then no such guarantee is made. To enable the feature discussed above, just enable it by setting
force()
argument to true. More information can be found in the documentation
try(FileChannel fc = out.getChannel()){ //enable force update fc.force(true); ByteBuffer bb = ByteBuffer.wrap(new String("LMNOPQRSTUVWXYZ"). getBytes(StandardCharsets.UTF_8)); while(bb.hasRemaining()){ fc.write(bb); } }
map() Method
Method Form:
public abstract MappedByteBuffer map(FileChannel.MapMode mode, long position, long size) throws IOException
Maps a region of this channel's file directly into memory. This method accepts three type of modes:
READ_ONLY
READ_WRITE
PRIVATE
More information can be found in the documentation. More information about modes can be found in this documentation.
This example demonstrates map().
import java.nio.channels.FileChannel; import java.nio.file.*; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.charset.StandardCharsets; import java.io.IOException; public class SampleClass{ public static void main(String[] args) throws IOException{ Path source = Paths.get("C:\\test\\test.txt"); try(FileChannel fc = FileChannel.open( source, StandardOpenOption.WRITE, StandardOpenOption.READ)){ MappedByteBuffer mBuff = fc.map(FileChannel.MapMode.PRIVATE, 5, 5); ByteBuffer bb = ByteBuffer.wrap(new String("ZZZZZ"). getBytes(StandardCharsets.UTF_8)); //Note: if we put a ByteBuffer that has //size greater than the capacity of //mBuff, this method will throw //BufferOverflowException mBuff.put(bb); } System.out.println("Writing Complete!"); } }Assume that
C:\test\test.txt
exists in your machine and has this content:ABCDEFGHIJKLMNOPQRSTUVWXYZ
MapMode.PRIVATE
ensures that any changes to mapped data of a file in memory won't affect the file. Thus, the example above won't do any changes to test.txt
Now, in the example above, try changing the map mode to MapMode.READ_WRITE and run the example again. This time the content of
test.txt
is going to be like this:ABCDEZZZZZKLMNOPQRSTUVWXYZ
If you want the mapped data to be read-only, use MapMode.READ.
map()
method returns MappedByteBuffer which is a subclass of ByteBuffer.
position() Method
This method has two forms.
position()
returns current position of FileChannel's file pointer. position(long newPosition)
sets FileChannel's new position.
This example demonstrates both forms.
import java.nio.channels.FileChannel; import java.nio.file.*; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.io.IOException; public class SampleClass{ public static void main(String[] args) throws IOException{ Path source = Paths.get("C:\\test\\test.txt"); if(!Files.exists(source)){ System.err.println("source doesn't exist!"); return; } try(FileChannel fc = FileChannel.open(source, StandardOpenOption.READ)){ ByteBuffer bb = ByteBuffer.allocate(1); System.out.println ("pos: " + fc.position()); fc.read(bb); System.out.println("content: " + new String(bb.array(), StandardCharsets.UTF_8)); bb.clear(); System.out.println("set pos..."); fc.position(7); System.out.println ("pos: " + fc.position()); fc.read(bb); System.out.println("content: " + new String(bb.array(), StandardCharsets.UTF_8)); bb.clear(); } } }Assume that
C:\test\test.txt
exists in your machine and has this content:ABCDEFGHIJKLMNOPQRSTUVWXYZ
The result is going to be:
pos: 0 content: A set pos... pos: 7 content: H
truncate() Method
Method Form:
public abstract FileChannel truncate(long size)throws IOException
Truncates this channel's file to the given size. If the given size is less than the file's current size then the file is truncated, discarding any bytes beyond the new end of the file.
If the given size is greater than or equal to the file's current size then the file is not modified. In either case, if this channel's file position is greater than the given size then it is set to that size.
This example demonstrates truncate().
import java.nio.channels.FileChannel; import java.nio.file.*; import java.io.IOException; public class SampleClass{ public static void main(String[] args) throws IOException{ Path source = Paths.get("C:\\test\\test.txt"); if(!Files.exists(source)){ System.err.println("source doesn't exist!"); return; } try(FileChannel fc = FileChannel.open(source, StandardOpenOption.WRITE)){ //value in the argument is in byte //not bit fc.truncate(16L); } System.out.println("file truncated!"); } }Assume that
C:\test\test.txt
exists in your machine and has this content:ABCDEFGHIJKLMNOPQRSTUVWXYZ
Then, test.txt has a file size of 26 bytes. In the example above, the file size will be reduced to 16 bytes.
transferFrom() Method
Method Form:
public abstract long transferFrom(ReadableByteChannel src, long position, long count) throws IOException
Transfers bytes into this channel's file from the given readable byte channel. More information can be read in the documentation. This method can be used for transferring FileChannel data to another FileChannel. We can also use this method to transfer data from channels that implements ReadableByteChannel to this FileChannel.
This example demonstrates transferFrom().
import java.nio.channels.FileChannel; import java.nio.file.*; import java.nio.charset.StandardCharsets; import java.io.IOException; public class SampleClass{ public static void main(String[] args) throws IOException{ Path source1 = Paths.get("C:\\test\\test1.txt"); Path source2 = Paths.get("C:\\test\\test2.txt"); try(FileChannel fc1 = FileChannel.open(source1,StandardOpenOption.READ); FileChannel fc2 = FileChannel.open(source2, StandardOpenOption.WRITE)){ fc2.transferFrom(fc1, 0, fc1.size()); } System.out.println("Operation Complete!"); } }Note that freshly written data into the source(fc1) before calling this method won't be transferred. This method is potentially much more efficient than a simple loop that reads from the source channel and writes to this channel. Many operating systems can transfer bytes directly from the source channel into the filesystem cache without actually copying them.
transferTo() Method
Method Form:
public abstract long transferTo(long position, long count, WritableByteChannel target)throws IOException
Transfers bytes from this channel's file to the given writable byte channel. More information can be found in the documentation. This method is the inverted version of
transferFrom()
.
This method demonstrates transferTo().
import java.nio.channels.FileChannel; import java.nio.file.*; import java.nio.charset.StandardCharsets; import java.io.IOException; public class SampleClass{ public static void main(String[] args) throws IOException{ Path source1 = Paths.get("C:\\test\\test1.txt"); Path source2 = Paths.get("C:\\test\\test2.txt"); try(FileChannel fc1 = FileChannel.open(source1,StandardOpenOption.READ); FileChannel fc2 = FileChannel.open(source2, StandardOpenOption.WRITE)){ fc1.transferTo(0, fc1.size(), fc2); } System.out.println("Operation Complete!"); } }Note that freshly written data into the source(fc1) before calling this method won't be transferred. This method is potentially much more efficient than a simple loop that reads from the source channel and writes to this channel. Many operating systems can transfer bytes directly from the source channel into the filesystem cache without actually copying them.
Locking FileChannel
We can use two methods to lock file access of file channels.
lock()
and tryLock()
are methods that returns FileLock. lock()
and tryLock()
have second forms:tryLock(long position, long size, boolean shared)
lock(long position, long size, boolean shared)
These forms lock a region of a file. Their first forms lock the entire region of file. the
shared
is a boolean flag than enables/disables shared lock. FileLock has two types of lock: Exclusive and Shared lock.
Shared lock enables thread to acquire overlapping shared locks. FileLock locks may overlap one other if the regions they're locking overlap. For example, lock1 locks position 5-10 then lock2 locks position 8-13. lock1 and lock2 are overlapping. If FileLock has shared lock, FileChannel that has lock2 can acquire lock1.
Note that shared lock can't acquire overlapping exclusive lock. Exclusive lock doesn't allow acquiring of either overlapping lock types. Also, remember that File locks are held on behalf of the entire Java virtual machine. They are not suitable for controlling access to a file by multiple threads within the same virtual machine. More information can be found in the documentation.
If you want locks that are suitable for a single virtual machine, you may consider using locks in java.util.concurrent.locks. The difference between
lock()
and tryLock()
are explained there. This article that I created explains locks in java.util.concurrent.locks.
This snippet demonstrates lock(long position, long size, boolean shared).
try(FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)){ FileLock lock = fc.lock(5,10,false); System.out.println(Thread.currentThread().getName() + " acquired the lock."); //other tasks... lock.release(); }
No comments:
Post a Comment