File Channels

A java.nio.channels.FileChannel can potentially be both a GatheringByteChannel and a ScatteringByteChannel. That is, it implements both interfaces. Most of the time, however, each actual FileChannel object is either readable or writable, not both. FileChannels created by invoking the getChannel( ) method of a FileOutputStream are writable. FileChannels created by invoking the getChannel( ) method of a FileInputStream are readable. Invoking a write( ) method on a channel connected to a FileInputStream tHRows a NonWritableChannelException. Invoking a read( ) method on a channel connected to a FileOutputStream throws a NonReadableChannelException. Only a FileChannel created from a RandomAccessFile can be both read and written.

Besides reading and writing, file channels permit several other file-specific operations:

15.2.1. Transferring Data

Example 14-1 in the previous chapter showed you how to copy one file to another using two FileChannels and a ByteBuffer to hold the data as it is copied from the old file into the new one. You can simplify the operation by taking advantage of FileChannel's two transfer methods:

public abstract long transferFrom( ReadableByteChannel src, long position, long count) throws IOException public abstract long transferTo( long position, long count, WritableByteChannel target) throws IOException

The first method copies count bytes from the source channel into the file starting at position. The second method copies count bytes from the file onto the target channel starting at position.

Example 15-2 demonstrates using transferTo( ) to copy one file to another.

Example 15-2. Transferring data between channels

import java.io.*; import java.nio.channels.*; public class NIOTransfer { public static void main(String[] args) throws IOException { FileInputStream inFile = new FileInputStream(args[0]); FileOutputStream outFile = new FileOutputStream(args[1]); FileChannel inChannel = inFile.getChannel( ); FileChannel outChannel = outFile.getChannel( ); inChannel.transferTo(0, inChannel.size( ), outChannel); inChannel.close( ); outChannel.close( ); } }

Neither the source nor the target channel has to be a file. These methods can also transfer data from a file to a network channel or from a network channel into a file. However, in this case, because both channels are files, the same program could be implemented using transferFrom( ) instead.

To do this, simply change inChannel.transferTo(0, inChannel.size( ), outChannel) to outChannel.transferFrom(inChannel, 0, inChannel.size( )).

Neither transferTo( ) nor transferFrom( ) is guaranteed to transfer as many bytes as requested. However, they're a little more reliable than the multibyte read( ) methods of InputStream. These methods will fail to transfer count bytes only if either the input channel doesn't have that many bytes or the output channel is nonblocking. Neither of those is the case here.

It's possible that this is just a shortcut for moving data from one channel to another through a buffer, as seen in Example 14-1. However, some platforms can transfer the bytes much more directly and quickly when using this method. Intermediate buffering might not be used. Thus, when moving data to and from files, you should use transferTo( ) or TRansferFrom( ) whenever possible. They should never be noticeably slower than manually managing the buffers, and sometimes they may be significantly faster.

15.2.2. Random Access

Although files are often read by streams, files are not streams. Unlike a network socket, a file does not have to be read sequentially. The disk controller can easily reposition itself to read or write at any given position in a file, and file channels can take advantage of this capability.

Each file channel knows its current position within the file. This is measured in bytes and is returned by the position( ) function:

public abstract long position( ) throws ClosedChannelException, IOException

As data is read out of the file or written into the file, the position is updated automatically. However, you can also change the position manually using this overloaded position( ) method:

public abstract FileChannel position(long newPosition) throws IllegalArgumentException , ClosedChannelException, IOException

For example, this code fragment skips over the next 4K in the file:

channel.position(channel.position( )+4096);

You can position the file pointer past the end of the file. Trying to read from this position returns -1 to signal the end of the file. Of course, unlike with an InputStream, you can reposition the file pointer and reread from earlier in the file, despite having reached the end of it. Trying to write past the end of file automatically expands the file. The only things you can't do are read or write before the beginning of the file. Setting the position to a negative number throws an IllegalArgumentException.

If you set the file pointer past the end of the file and then start writing, the content of the file between the old end and the new position is system dependent. On some systems this region may be filled with random data that was left on the disk. In many circumstances this can be a security hole, since it may expose data that was meant to be deleted. Therefore, you really shouldn't do this unless you know you're going to go back later and fill in those bytes before closing the file.

You can check the size of the file with the size( ) method:

public abstract long size( ) throws IOException

There's no corresponding setter. You can expand a file only by writing more data at the desired end of the file. You can, however, shorten a file to a specified length using the TRuncate( ) method:

public abstract FileChannel truncate(long size) throws IOException

This method reduces the file to the specified size. Any bytes after that point are lost.

The truncated bytes still exist on the disk, though, until some process overwrites them. Security-conscious applications may wish to overwrite them before truncating the data.

 

15.2.3. Threading and Locking

Unlike streams, file channels are safe for access from multiple concurrent threads. Multiple threads can read from or write to the same FileChannel simultaneously. Reads that use absolute positions in the file could possibly be genuinely simultaneous. Writes and relative reads queue up behind each other. Operations block as necessary to keep the file in a well-defined state. Writes are not allowed to overlap.

Nonetheless, the ordering of writes from different threads can still be unpredictable. This may not matter for some use cases, such as a log file, where all that matters is that each write is atomic and the relative order of writes is unimportant. However, much of the time, more control is needed.

Applications sometimes need exclusive access to particular files. For instance, if two different word processors try to edit the same document at the same time, conflicts are almost inevitable. File channels allow Java applications to attempt to lock a given file or piece of a file for exclusive access. The attempt may not always succeed, of course. Another process may have locked the same file already, but at least you can ask. The method that asks is lock( ):

public final FileLock lock( ) throws IOException

This method blocks until it can get the lock on the file. If it can't get the lock on the file, it throws one of a variety of IOExceptions, depending on exactly why it can't get the lock. Besides the usual reasons (IOException, ClosedChannelException, etc.), it may also throw a FileLockInterruptionException if another thread interrupts this one while it's waiting for the lock.

On Windows, locks are mandatory and are enforced by the operating system. Once you've locked a file, no other program can access it (though another thread in the same VM can). Most of the time on Unix, though, locks are only advisory. That is, another process is supposed to check to see if the file is locked, and wait if it is. However, the operating system does not enforce this.

For some use cases, it's not necessary to lock the entire file. You can instead request a lock over only a piece of it, starting at a specified position and continuing for a certain number of bytes:

public abstract FileLock lock(long position, long size, boolean shared) throws IOException

The range bounded by position and size need not be contained within the file. You can lock regions that go beyond the end of the file or are even completely outside it. If so, the lock is retained if the file grows. If the file grows beyond the range you've locked, new content past the lock's boundary is not locked.

Some operating systems, including Windows and Unix, support shared locks. A shared lock allows multiple applications, all of which have the shared lock, to access the file. However, no one application can exclusively lock the file. If the third argument is true, lock( ) tries to get a shared lock. However, if the operating system doesn't support this functionality, it just waits for an exclusive lock instead. Shared locks are allowed only on readable files (i.e., you can't have a shared lock on a write-only channel). Unshared locks are allowed only on writable files (i.e., you can't have an unshared lock on a read-only channel).

Both lock( ) methods can wait for an indefinite amount of time. If you want to lock if possible but don't want to block the entire thread, use the tryLock( ) methods instead:

public final FileLock tryLock( ) throws IOException public abstract FileLock tryLock(long position, long size, boolean shared) throws IOException

These methods act the same as lock( ), except that they return immediately. If a file has already been locked, these two methods return null. If the file can't be locked for some other reason, these methods throw an IOException.

15.2.4. FileLock

All four lock( ) and tryLock( ) methods return a FileLock object that represents the lock on the file. The primary purpose of this object is to release the lock when you're done with the file:

public abstract void release( ) throws IOException

A FileLock can also tell you whether it has locked at least part of a given range:

public final boolean overlaps(long position, long size)

This class also has a few getter methods that may occasionally be useful:

public final FileChannel channel( ) public final long position( ) public final long size( ) public final boolean isShared( ) public abstract boolean isValid( )

They don't follow the usual Java naming conventionsalmost nothing in the new I/O API doesbut they are all basic getter methods. The channel( ) method returns the channel that locked the file. The position( ) method returns the byte index of the start of the locked range. The size( ) method returns the length of the locked range. The isShared( ) method returns true if the lock is shared and false otherwise; the isValid( ) method returns false if the lock has been released or the channel closed and TRue otherwise.

Example 15-3 demonstrates locking a file before copying data into it. The output channel needs an unshared lock because it's write-only, so we can use the simple no-args lock( ) method to lock it. The input channel needs a shared lock because it's read-only, so we have to use the three-args lock( ) method to lock it from the first byte of the file through the last byte of the file.

Example 15-3. Copying locked files

import java.io.*; import java.nio.channels.*; public class LockingCopier { public static void main(String[] args) throws IOException { FileInputStream inFile = new FileInputStream(args[0]); FileOutputStream outFile = new FileOutputStream(args[1]); FileChannel inChannel = inFile.getChannel( ); FileChannel outChannel = outFile.getChannel( ); FileLock outLock = outChannel.lock( ); FileLock inLock = inChannel.lock(0, inChannel.size( ), true); inChannel.transferTo(0, inChannel.size( ), outChannel); outLock.release( ); inLock.release( ); inChannel.close( ); outChannel.close( ); } }

Technically, the lock release isn't necessary here. Closing the output channel also releases the locks on it. However, it's good form and it will definitely be necessary if you're not going to close the file immediately after finishing the operation for which you locked it in the first place.

15.2.5. Flushing

Like file streams, file channels can be buffered. The actual implementation of the buffer is usually different, though. In particular, a file channel's buffering most likely reflects the caching of the native filesystem, while an OutputStream's buffer usually reflects operations inside the VM. However, the effect in both cases is the same.

To make sure that data is actually written to the disk, it may be necessary to flush it. The method that flushes a FileChannel is called force( ) :

public abstract void force(boolean metaData) throws IOException

The single argument specifies whether or not any file metadata changes (e.g., last modified time, name changes, and so on) should also be committed, in addition to the contents of the file.

For example, in Example 15-3, we could flush the output channel before releasing its lock and closing it like so:

outChannel.force( ); outLock.release( ); outChannel.close( );

Here are a couple of caveats about forcing:

Категории