RAFs, Random Access Files permit asynchronous (random) access to a file contents. To access a file randomly we open the file, seek a particular position, and then we read or write to that file.

Java NIO.2 introduces a new interface – SeekableByteChannel for working with Random Access Files. Also improves the well-known FileChannel class by implementing this interface.

Before we start to talk about about FileChannel and SeekableByteChannel it is advisable to first talk a little bit about ByteBuffers and Channels.


ByteBuffers

A byte-buffer is an in-memory array of bytes. It usually contains data that was recently read, or that will be written from/to a destination.

A buffer has three important properties:

  • The buffer’s capacity represents the “maximum amount of information” that can be stored in the buffer.
  • The buffer’s position represents how much data has been read or written. The position is an index in the buffer’s array, and cannot have a negative value or a value bigger than the buffer’s capacity.
  • The buffer’s limit is the difference between buffer’s capacity and the buffer’s position.

Channels

Channels are in a way similar with the classical I/O streams, the difference is that while streams are one-way directed (read or write), channels can support both operations in the same time. Also Channels allow you to write and read asynchronously.


Using SeekableByteChannel to write / read to / from files

The SeekableByteChannel interface has 6 methods:

  • position(): Returns the channels current position.
  • position(long): Sets the channel position to the specified value. The value needs to be a positive number.
  • truncate(long): Truncates the entity connected to the SeekableByteChannel to the specified value.
  • read(ByteBuffer): Reads into the buffer (from the channe).
  • write(ByteBuffer): Writes bytes from the buffer to the channel.
  • size(): Returns the current size of the entity to which the channel is connected.

To open a SeekableByteChannel we will need to use the the two methods from the java.nio.file.Files class:

public static SeekableByteChannel newByteChannel(Path path, OpenOption... options);

Or:

public static SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, 
FileAttribute<?>... attrs);

As you can see to open a seekable channel you need to supply as input the Path you want to open (basically the file) and also open options, which are enum constants.

The possible enum options are as follow:

READ The file is opened with READ access.
WRITE The file is opened with WRITE access.
CREATE Creates the file if the file does not already exist.
CREATE_NEW Creates the file if the file does not already exist. If the file exists throw an exception.
APPPEND Appends to the file. It is used in conjunction with CREATE and WRITE.
DELETE_ON_CLOSE Deletes the file after the channel is closed. Use this when creating and editing / reading from temporary files.
TRUNCATE_EXISTING Truncates the file to size 0. It is used in conjunction with WRITE and it’s useful when you want to clean the contents of a file.
SPARSE Usually used in conjunction with CREATE or CREATE_NEW. On some file systems large files that have big “data gaps” are stored in a more efficient way.
SYNC File content + metadata is synchronized with the underlying file system.
DSYNC File content is synchronized with the underlying file system.

Reading a file using a SeekableByteChannel

Please follow the comments:

package mainpack;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;

import static java.nio.file.StandardOpenOption.READ;

public class HideAndSeek {
	public static void main(String... args) {
		// We create a Path out the file we are going to read
		Path file = Paths.get(System.getProperty("user.home"), "file.txt");

		// We open the file in order to read it ()
		try (SeekableByteChannel sbc = Files.newByteChannel(file,
				EnumSet.of(READ))) {
			
			// We use a ByteBuffer to read (2^5 size = 32)
			ByteBuffer buff = ByteBuffer.allocate(1<<5);
			// Position is set to 0
			buff.clear();
			
			// We use the current encoding to read 
			String encoding = System.getProperty("file.encoding");
			
			// While the number of bytes from the channel are > 0
			while(sbc.read(buff)>0) {
				
				// Prepare the data to be written
				buff.flip();
				
				// Usins the current enconding we decode the bytes read
				System.out.print(Charset.forName(encoding).decode(buff));
				
				// Prepare the buffer for a new read
				buff.clear();
			}
			
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
}

Output:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctu.

Writing a file using SeekableByteChannel

Please follow the comments:

package mainpack;

import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.WRITE;
import static java.nio.file.StandardOpenOption.CREATE;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;

public class HideAndSeek2 {

	public static void main(String... args) {
		// The path were we are going to write
		Path file = Paths.get(System.getProperty("user.home"), "file2.txt");

		// We open the file in APPEND mode with CREATE
		try (SeekableByteChannel sbc = Files.newByteChannel(file,
				EnumSet.of(CREATE, APPEND, WRITE))) {

			// We create the buffer with the text we are going to write
			ByteBuffer buff = ByteBuffer.wrap("Writing text\n".getBytes());
			
			// Write the byte buffer contents to the file and show
			// how many bytes were written
			System.out.println("Bytes written: " + sbc.write(buff));
			buff.clear();
			
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
}

If we are going to open the file called “file2.txt” from our home folder the content will be:

Writing text

If we run multiple times the program the content of the file will look like this (the text is appended):

Writing text
Writing text
...
Writing text

Using the SeekableByteChannel to read characters from different file locations

Given the following acrostic:

JANet was quite ill one day.
FEBrile trouble came her way.
MARtyr-like, she lay in bed;
APRoned nurses softly sped.
MAYbe, said the leech judicial
JUNket would be beneficial.
JULeps, too, though freely tried,
AUGured ill, for Janet died.
SEPulchre was sadly made.
OCTaves pealed and prayers were said.
NOVices with ma'y a tear
DECorated Janet's bier.

We want to read the first 3 letters at the beginning of every word and print on the standard output.

Code example:

package mainpack;

import static java.nio.file.StandardOpenOption.READ;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;

public class HideAndSeek3 {

	// This list contains indexes for every beginning of the
	// rows
	public static List<Long> indexes = new LinkedList<Long>();
	static {
		indexes.add(0L); // start index for row 1 - JAN
		indexes.add(30L); // start index for row 2 - FEB
		indexes.add(61L); // start index for row 3 - MAR
		indexes.add(91L); // start index for row 4 - APR
		indexes.add(120L); // start index for row 5 - MAY
		indexes.add(152L); // start index for row 6 - JUN
		indexes.add(181L); // start index for row 7 - JUL
		indexes.add(216L); // start index for row 8 - AUG
		indexes.add(246L); // start index for row 9 - SEP
		indexes.add(273L); // start index for row 10 - OCT
		indexes.add(312L); // start index for row 11 - NOV
		indexes.add(338L); // start index for row 12 - DEC
	}

	public static void main(String... args) {
		// The path we are going to open - the file containing the acrostic
		Path acrostic = Paths.get(System.getProperty("user.home"),
				"acrostic.txt");

		// The ByteBuffer is an array of 3 characters
		ByteBuffer buff = ByteBuffer.allocate(3);

		// Obtain encoding
		String encoding = System.getProperty("file.encoding");

		// We open the file in READ mode
		try (SeekableByteChannel sbc = Files.newByteChannel(acrostic,
				EnumSet.of(READ))) {

			// We jump on every index using position , we read the characters
			// in the buffer and we print them on the screen.
			for (Long idx : indexes) {
				sbc.position(idx);
				sbc.read(buff);
				buff.flip();
				System.out.print(Charset.forName(encoding).decode(buff)+" ");
				buff.rewind();
			}

			// Clear buffer
			buff.clear();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
}

And the output of the code:

JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC 

The Watch Service was introduced in Java 7 as a “thread-safe” service responsible for watching objects for changes.

The most popular use of this API would be to use it as way to monitor a folder of files for changes such as: addition of new files, deletion of files, file renames, etc.


Implementing a WatchService

The WatchService is dependent on the FileSystem, the first step of creating a new Service is to obtain the underlying file system:

// Create a new Watch Service
WatchService watchService = FileSystems.getDefault().newWatchService();

Every object needs to be explicitly registered with the newly created service. In our particular case we will register a Path instance (a folder).

After we register the object, we also need to specific events the service need to watch.

Eg.:

  • StandardWatchEventKinds.ENTRY_CREATE: This event triggers when a folder entry is created, or a new entry is moved or renamed.
  • StandardWatchEventKinds..ENTRY_DELETE: This event is triggered when a folder/file is deleted, moved or renamed.
  • StandardWatchEventKinds.ENTRY_MODIFY: This event is pretty-platform dependent. Usually is triggered when the contents of a file is modified. But on some file systems it can also trigger when the attributes of that particular file are modified.
  • StandardWatchEventKinds.OVERFLOW: Indicates that an event has been lost.

The following code will register ENTRY_CREATE, ENTRY_DELETE and ENTRY_MODIFY to the home folder:

// Folder we are going to watch
Path folder = Paths.get(System.getProperty("user.home"));

// Create a new Watch Service
WatchService watchService = FileSystems.getDefault().newWatchService();

// Register events
folder.register(watchService, 
		StandardWatchEventKinds.ENTRY_CREATE,
		StandardWatchEventKinds.ENTRY_MODIFY,
		StandardWatchEventKinds.ENTRY_DELETE);
	
// Closes a watch service
watchService.close();

For every Path instance we are registering we will receive an WatchKey instance.

To wait for incoming events we will need to write an infinite loop:

for(;;) {}
while(true) {}

In this loop we will be able to poll for obtaining WatchKey instances.

while(true) {
	// Obtaining watch keys
	final WatchKey key = watchService.poll();
	// key value can be null if no event was triggered
}

We can also add a timing interval for polling (if we don’t need an instant feedback on the events):

while(true) {
	// Obtaining watch keys every 10 seconds
	final WatchKey key = watchService.poll(10, TimeUnit.SECONDS);
	// key value can be null if no event was triggered
}

A WatchKey can have the following states:

  • Ready: The WatchKey is ready to accept events.
  • Signaled: In this state the WatchKey has at least one event that occurred and it was queued.
  • Invalid: In this state the key is considered to be no longer valid.

So the next step will be to retrieve the pending events from the WatchKey. There can be multiple events that were triggered. Those events are queued. The code to retrieve the pending events is:

for (WatchEvent<?> watchEvent : key.pollEvents()) {
	final Kind<?> kind = watchEvent.kind();
	// Overflow event
	if (StandardWatchEventKinds.OVERFLOW == kind) {
		continue; // loop
	}
}

To obtain the Path from the watchEvent we will need to do something like this:

final WatchEvent<Path> wePath = ( WatchEvent<Path>) watchEvent;
final Path path = wePath.context();

The last step of the implementation is to put key back into it’s Ready step.

// Inside the loop
if(key.reset()) { break; }

Also you should also take in consideration that if the loops break (eg.: because of an error) you need to explicitly close the Watch Service:

watchService.close();

Or, since Java 7, include the opening of the WatchService in the new try() block:

try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
…
}

Putting all the code togheter

Please take in consideration that in our particular case we are only going to watch the Home folder, and not the whole sub-tree of folders. If you wish to watch the whole sub-tree for modifications you will need to register a watch service for every folder in the tree.

To obtain the list of sub-folders of a given folder, I recommend you to read my previous article called: Java 7 NIO.2 – Recursive folder walks

Also you will need to maintain the collection of watchers in the case you are creating/deleting new/existing sub-folders.

The Watch Service API is a low-level approach, so maybe it’s best for you if you write your own high-level mechanism, or use an already-existing solution.

package mainpack;

import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;

import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;

public class MainWatch {

	public static void watchDirectoryPath(Path path) {
		// Sanity check - Check if path is a folder
		try {
			Boolean isFolder = (Boolean) Files.getAttribute(path,
					"basic:isDirectory", NOFOLLOW_LINKS);
			if (!isFolder) {
				throw new IllegalArgumentException("Path: " + path + " is not a folder");
			}
		} catch (IOException ioe) {
			// Folder does not exists
			ioe.printStackTrace();
		}
		
		System.out.println("Watching path: " + path);
		
		// We obtain the file system of the Path
		FileSystem fs = path.getFileSystem ();
		
		// We create the new WatchService using the new try() block
		try(WatchService service = fs.newWatchService()) {
			
			// We register the path to the service
			// We watch for creation events
			path.register(service, ENTRY_CREATE);
			
			// Start the infinite polling loop
			WatchKey key = null;
			while(true) {
				key = service.take();
				
				// Dequeueing events
				Kind<?> kind = null;
				for(WatchEvent<?> watchEvent : key.pollEvents()) {
					// Get the type of the event
					kind = watchEvent.kind();
					if (OVERFLOW == kind) {
						continue; //loop
					} else if (ENTRY_CREATE == kind) {
						// A new Path was created 
						Path newPath = ((WatchEvent<Path>) watchEvent).context();
						// Output
						System.out.println("New path created: " + newPath);
					}
				}
				
				if(!key.reset()) {
					break; //loop
				}
			}
			
		} catch(IOException ioe) {
			ioe.printStackTrace();
		} catch(InterruptedException ie) {
			ie.printStackTrace();
		}
		
	}

	public static void main(String[] args) throws IOException,
			InterruptedException {
		// Folder we are going to watch
		Path folder = Paths.get(System.getProperty("user.home"));
		watchDirectoryPath(folder);
	}
}

I will create new files/folders inside my home directory the output will look like:

Watching path: C:\Users\IBM_ADMIN
New path created: New Text Document.txt
New path created: test1.txt
New path created: New Text Document.txt
New path created: test2.txt

FileVisitor Interface

The FileVisitor interface allows us to recursively traverse file structures – folders, sub-folders and files.

Every method of this interface can return 4 possible results (instances of the FileVisitResult enum):

This FileVisitor interface has 4 methods:

  • visitFile():
    The method is invoked for a file. The method should return a FileVisitResult.CONTINUE result or a FileVisitResult.TERMINATE result. The method receive a reference to the file (a Path object) and to the BasicFileAttributes object associated with the Path.
  • preVisitDirectory():
    This method is invoked for a directory before visiting its children. The method returns FileVisitResult.CONTINUE if we want it’s children to be visited or FileVisitResult.SKIP_SUBTREE if we want the process to stop. If we want to skip visiting the siblings of the directory we need to return FileVisitResult.SKIP_SIBLINGS .
  • postVisitDirectory():
    This method is invoked after we visit all the children of a directory (including other folders and their descendants).
  • visitFileFailed():
    This method is invoked if a file (or folder) cannot be accessed.

In practice it is also possible to use the SimpleFileVisitor class if we want to traverse only the directories.

Once we have created the “recursive-walking-mechanism” by implementing FileVisitor interface or by extending the SimpleFileVisitor class we can start the recursive process by calling the walkFileTree() method .


Example: Writing an application that search for files bigger than a pre-defined size

In this example we are going to implement a FileVisitor that walks a folder and logs to output all files that are bigger than certain amount.

The first step is to write the FileVisitor:

/**
 * This FileVisitor searches for files bigger than 'size' .
 * 
 * If a file matching our criteria is found, we log the results in the stdout .
 * 
 * @author AndreICiobanu
 * 
 */
class FileSizeVisitor implements FileVisitor<Path> {

	private Long size;

	public FileSizeVisitor(Long size) {
		this.size = size;
	}

	/**
	 * This is triggered before visiting a directory.
	 */
	@Override
	public FileVisitResult preVisitDirectory(Path path,
			BasicFileAttributes attrs) throws IOException {
		return FileVisitResult.CONTINUE;
	}

	/**
	 * This is triggered when we visit a file.
	 */
	@Override
	public FileVisitResult visitFile(Path path, BasicFileAttributes attrs)
			throws IOException {

		long fileSize = attrs.size() / 1024;

		if (fileSize >= this.size) {
			System.out.println("File bigger than " + this.size + "KB  found: "
					+ path);
		}

		return FileVisitResult.CONTINUE;
	}

	/**
	 * This is triggered if we cannot visit a Path We log the fact we cannot
	 * visit a specified path .
	 */
	@Override
	public FileVisitResult visitFileFailed(Path path, IOException exc)
			throws IOException {
		// We print the error
		System.err.println("ERROR: Cannot visit path: " + path);
		// We continue the folder walk
		return FileVisitResult.CONTINUE;
	}

	/**
	 * This is triggered after we finish visiting a specified folder.
	 */
	@Override
	public FileVisitResult postVisitDirectory(Path path, IOException exc)
			throws IOException {
		// We continue the folder walk
		return FileVisitResult.CONTINUE;
	}

}

The main method:

Path homeFolder = Paths.get("C:\\");
FileVisitor fileVisitor = new FileSizeVisitor(new Long(5000));
try {
	Files.walkFileTree(homeFolder, fileVisitor);
} catch (IOException e) {
	e.printStackTrace();
}

And some sample output from my machine:

...
File bigger than 5000  found: C:\Windows\System32\IME\imekr8\applets\mshwkorrIME.dll
File bigger than 5000  found: C:\Windows\System32\IME\IMETC10\applets\MSHWCHTRIME.dll
File bigger than 5000  found: C:\Windows\System32\korwbrkr.lex
ERROR: Cannot visit path: C:\Windows\System32\LogFiles\WMI\RtBackup
...

Writing a file search application based on a criteria

We can extend the example from above and create a more general approach.

The idea is to write an abstract implementation of the FileVisitor interface, that contains an abstract method “criteria(Path, BasicFileAttributes)“.

Later we can use anonymous classes to define a new behavior of our visitors specifying only the criteria and avoiding to write the boiler-plate-code necessary to implement a FileVisitor.

We will name our FileVisitor implementation FileSearchByCriteriaVisitor:

abstract class FileSearchByCriteriaVisitor implements FileVisitor<Path>

This class will have two instance variables called results and failedVisits:

private List<Path> results = new LinkedList<Path>();
private List<Path> failedVisits = new LinkedList<Path>();
public List<Path> getResults() {
	return this.results;
}
public List<Path> getFailedVisits() {
	return this.failedVisits;
}

The “criteria(Path, BasicFileAttributes)” mentioned before will be used like this:

// This method will be later implemented
protected abstract Boolean criteria(Path path, BasicFileAttributes attrs);
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
		throws IOException {
	// Everytime we visit a file we check if that particular file matches a
	// criteria .
	if (criteria(file, attrs)) {
		// If the file matches the criteria we add it as a result
		results.add(file);
	}
	return FileVisitResult.CONTINUE;
}

Now everytime we implement a new FileSearchByCriteriaVisitor we must supply an implementation for the abstract method defined before.

Example how to use the FileSearchByCriteriaVisitor:

// Search all the files bigger than size
final long size = 5000; // KB
// Defining a new visitor criteria
FileSearchByCriteriaVisitor sizeVisitor = new FileSearchByCriteriaVisitor() {
	@Override
	protected Boolean criteria(Path path, BasicFileAttributes attrs) {
		if (attrs.size() / 1024 >= size) {
			return true;
		}
		return false;
	}
};
// Walk don't run
Files.walkFileTree(Paths.get("C:\\"), sizeVisitor);
// Evaluate results
evaluate(sizeVisitor.getResults());

The results and the paths with errors are available in “sizeVisitor.getResults()” and “sizeVisitor.getFailedVisits()”.

Introduction

NIO.2 help us determine the attributes of a file (such as whether is hidden, is a directory, its size, etc.) by introducing the java.nio.file.attribute package.

Given the fact that various file systems (a file system usually depends on the operating system) have different properties associated with files, NIO.2 groups the attributes into views, each view is specific to a particular file system implementation.

NIO.2 comes with a set of views:

  • BasicFileAttributeView: This view contains a set of common attributes supported by all filesystem implementations. The inner name of the attribute view is ‘basic’.
  • DosFileAttributeView: This view contains a set of attributes specific to filesystems that implement DOS attributes. The inner name of the attribute view is ‘dos’.
  • PosixFileAttributeView: This view contains a set of attributes specific to file systems that support the POSIX standards. The inner name of the attribute view is ‘posix’.
  • AclFileAttributeView: This view is supported by all filesystems implementations that have the concept of ACL. The inner name of the attribute is ‘acl’.
  • FileOwnerAttributeView: This view is supported by all filesystems implementations that have the concept of “ownership” over a file. The inner name of the attribute is ‘owner’.
  • UserDefinedFileAttributeView

How do we determine what are the views supported by our filesystem ?

import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.util.Set;

public class MainClass {

	public static void main(String argv[]) {
		// Retrieves the default file system
		FileSystem fileSystem = FileSystems.getDefault();
		Set<String> fileSystemViews = fileSystem.supportedFileAttributeViews();

		// We iterate over the available file attribute views
		// of a file system
		for (String fileSystemView : fileSystemViews) {
			System.out.println(fileSystemView);
		}
	}
}

Output (I am using a Windows 7 machine):

acl
basic
owner
user
dos

Everytime we want to access the view attributes of a file/folder, it is advisable to check if the file system supports the particular view. NIO.2 allows us to test this using the FileSystem.supportedFileAttributeViews() method .

Eg.:

package mainpack;

import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.attribute.AclFileAttributeView;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.DosFileAttributeView;
import java.nio.file.attribute.FileOwnerAttributeView;
import java.nio.file.attribute.PosixFileAttributeView;

public class MainClass {

	public static void main(String argv[]) {
		// Retrieves the default file system
		FileSystem fileSystem = FileSystems.getDefault();
		Iterable<FileStore> fileStores = fileSystem.getFileStores();

		for (FileStore fileStore : fileStores) {
			// Test if it supports BasicFileAttributeView
			System.out.println(String.format(
				"Filestore %s supports (%s) : %s",
				fileStore,
				BasicFileAttributeView.class.getSimpleName(),
				fileStore.supportsFileAttributeView(BasicFileAttributeView.class)));
			
			// Test if supports DosFileAttributeView
			System.out.println(String.format(
				"Filestore %s supports (%s) : %s",
				fileStore,
				DosFileAttributeView.class.getSimpleName(),
				fileStore.supportsFileAttributeView(DosFileAttributeView.class)));
			
			// Test if supports PosixFileAttributeView
			System.out.println(String.format(
				"Filestore %s supports (%s) : %s",
				fileStore,
				PosixFileAttributeView.class.getSimpleName(),
				fileStore.supportsFileAttributeView(PosixFileAttributeView.class)));
			
			// Test if supports AclFileAttributeView
			System.out.println(String.format(
				"Filestore %s supports (%s) : %s",
				fileStore,
				AclFileAttributeView.class.getSimpleName(),
				fileStore.supportsFileAttributeView(AclFileAttributeView.class)));
			
			// Test if supports FileOwnerAttributeView
			System.out.println(String.format(
				"Filestore %s supports (%s) : %s",
				fileStore,
				FileOwnerAttributeView.class.getSimpleName(),
				fileStore.supportsFileAttributeView(FileOwnerAttributeView.class)));
			
			System.out.println();
		}
	}
}

Output:

Filestore (C:) supports (BasicFileAttributeView) : true
Filestore (C:) supports (DosFileAttributeView) : true
Filestore (C:) supports (PosixFileAttributeView) : false
Filestore (C:) supports (AclFileAttributeView) : true
Filestore (C:) supports (FileOwnerAttributeView) : true

Filestore (D:) supports (BasicFileAttributeView) : true
Filestore (D:) supports (DosFileAttributeView) : true
Filestore (D:) supports (PosixFileAttributeView) : false
Filestore (D:) supports (AclFileAttributeView) : true
Filestore (D:) supports (FileOwnerAttributeView) : true

Supported file attributes

Every view support file attributes.

BasicFileAttributeView

Attribute Returned Type Comments
“basic:creationTime” FileTime The exact time when the file was created.
“basic:fileKey” Object An object that uniquely identifies a file or null if a file key is not available.
“basic:isDirectory” Boolean Returns true if the file is a directory.
“basic:isRegularFile” Boolean Returns true if a file is not a directory.
“basic:isSymbolicLink” Boolean Returns true if the file is considered to be a symbolic link.
“basic:isOther” Boolean
“basic:lastAccessTime” FileTime The last time when the file was accesed.
“basic:lastModifiedTime” FileTime The time when the file was last modified.
“basic:size” Long The file size.

DosFileAttributeView

Attribute Returned Type Comments
“dos:archive” Boolean Return true if a file is archive or not.
“dos:hidden” Boolean Returns true if the file/folder is hidden.
“dos:readonly” Boolean Returns true if the file/folder is read-only.
“dos:system” Boolean Returns true if the file/folder is system file.

PosixFileAttributeView

Attribute Returned Type Comments
“posix:permissions” Set<PosixFilePermission> The file permissions.
“posix:group” GroupPrincipal Used to determine access rights to objects in a file system

AclFileAttributeView

Attribute Returned Type
“acl:acl” List<AclEntry>
“acl:owner” UserPrincipal

Retrieving file/folder attributes

There are two ways to extract those attributes:

  • Getting a bulk of attributes using readAttributes() method ;
  • Getting single attributes using getAttribute() method .

Retrieving bulk attributes:

package mainpack;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;

public class MainClass {

	public static void main(String argv[]) {
		// File path
		Path path = Paths.get(System.getProperty("user.home"), "Downloads",
				"file1.txt");
		
		// Getting bulk attributes
		BasicFileAttributes attributes = null;
		try {
			attributes = Files.readAttributes(path, BasicFileAttributes.class);
		} catch(IOException ioe) {
			ioe.printStackTrace();
		}
		
		// Retrieving information
		StringBuilder outBuff = new StringBuilder();
		
		outBuff.append("File: " + path);
		outBuff.append("\n");
		outBuff.append("\t").append("tsize: " + attributes.size());
		outBuff.append("\n");
		outBuff.append("\t").append("creationTime: " + attributes.creationTime());
		outBuff.append("\n");
		outBuff.append("\t").append("lastAccessTime: " + attributes.lastAccessTime());
		outBuff.append("\n");
		outBuff.append("\t").append("lastModifiedTime: " + attributes.lastModifiedTime());
		outBuff.append("\n");
		outBuff.append("\t").append("isRegularFile: " + attributes.isRegularFile());
		outBuff.append("\n");
		outBuff.append("\t").append("isDirectory: " + attributes.isDirectory());
		outBuff.append("\n");
		outBuff.append("\t").append("isSymbolicLink: " + attributes.isSymbolicLink());
		outBuff.append("\n");
		outBuff.append("\t").append("isOther: " + attributes.isOther());
		outBuff.append("\n");
		
		System.out.println(outBuff.toString());
	}
}

Output:

File: C:\Users\IBM_ADMIN\Downloads\file1.txt
	tsize: 0
	creationTime: 2013-12-03T15:55:06.208828Z
	lastAccessTime: 2013-12-03T15:55:06.208828Z
	lastModifiedTime: 2013-12-03T15:55:06.208828Z
	isRegularFile: true
	isDirectory: false
	isSymbolicLink: false
	isOther: false

We can also retrieve single attributes. Example:

...
// The second parameter which is composed by two sections
// [inner-view-name]:[supported attribute]
Long size = (Long) Files.getAttribute(path, "basic:size", NOFOLLOW_LINKS);
FileTime creationTime = (FileTime) Files.getAttribute(path, "basic:creationTime");
System.out.println("Size is: " + size);
System.out.println("Creation time: " + creationTime);
...

Updating file/folder attributes

The simplest way to update file attributes is by using the Files.setAttribute method.
In the following example we are going to modify the creation time of a given file:

...
// BEFORE 
FileTime before = (FileTime) Files.getAttribute(path, "basic:creationTime");
System.out.println("Creation time (BEFORE): " + before);
	
// AFTER
Files.setAttribute(path, "basic:creationTime", FileTime.fromMillis(System.currentTimeMillis()), NOFOLLOW_LINKS);
FileTime after = (FileTime) Files.getAttribute(path, "basic:creationTime");
System.out.println("Creation time (AFTER): " + after);
...

Output:

Creation time (BEFORE): 2013-12-03T15:55:06.208828Z
Creation time (AFTER): 2013-12-05T13:45:04.058Z

Getting attributes of FileStores

To determine the default FileStore object we can invoke FileSystems.getDefault(), but it’s also possible to obtain the list of FileStores by iterating over the list we obtain calling FileSystem.getFileStores() .

Each file store object has dedicated methods for obtaining the name, type, totalSpace etc.

import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;

public class MainClass {

	public static void main(String argv[]) {
		// Obtaining the default file System
		FileSystem fileSystem = FileSystems.getDefault();
		
		// Iterating over the list of existing file stores 
		// and retrieve their informations 
		StringBuilder outBuff = null;
		for (FileStore fileStore : fileSystem.getFileStores()) {
			try {
				// Initializing buffer
				outBuff = new StringBuilder();
				outBuff.append("File store - ").append(fileStore).append("\n");
				
				// Obtaining the total space
				outBuff.append("\t").append("Total space: ");
				outBuff.append(fileStore.getTotalSpace() / 1024);
				outBuff.append("\n");
				
				// Obtaining used space
				outBuff.append("\t").append("Used space: ");
				outBuff.append((fileStore.getTotalSpace() - fileStore
						.getUnallocatedSpace()) / 1024);
				outBuff.append("\n");
				
				// Available space
				outBuff.append("\t").append("Available space: ");
				outBuff.append(fileStore.getUsableSpace() / 1024);
				outBuff.append("\n");
				
				// File store is readonly
				outBuff.append("\t").append("Read-Only: ");
				outBuff.append(fileStore.isReadOnly());
				outBuff.append("\n");
				
				System.out.println(outBuff.toString());
				
			} catch (IOException e) {
				System.err.println(e);
			}
		}
	}
}

Output:

File store - (C:)
	Total space: 102399996
	Used space: 47860936
	Available space: 54539060
	Read-Only: false

File store - (D:)
	Total space: 210168828
	Used space: 28760504
	Available space: 181408324
	Read-Only: false

The Path class is considered to be the entry point for exploring the NIO.2 API. Basically every I/O operation in NIO.2 will exploit the facilities offered by this class.

Path is basically an upgraded version of the java.io.File class.

1. Getting the Path of the Home Directory When you want to obtain the path that points to a specific file in the Home directory, you can proceed as shown. The returned home directory is dependent on each operating system:

// Returns the folder 'Downloads' from the home directory
Path path = Paths.get(System.getProperty("user.home"), "Downloads");
System.out.println(path);
// Output is C:\Users\andreinc\Downloads

2. Obtaining information about the Path: Path Root and Path Parent 

// Obtain the Path Root 
Path path1 = Paths.get(System.getProperty("user.home"), "Downloads");
System.out.println(path1.getRoot());
// Output is: 'C:\'
// Obtain the path parent
Path path2 = Paths.get(System.getProperty("user.home"), "Downloads");
System.out.println(path2.getParent());
// Output is: 'C:\Users\andreinc'

3. Splitting a path in its name elements This is a nice feature that allows the programmer to split the path in it’s forming elements. For example if a file has the following path: ‘C:\Users\andreinc\Downloads\file1.txt’ the following function will retrieve the forming elements of the path, a List containing the following elements: [‘C:\’, ‘Users’, ‘andreinc’, ‘Downloads’, ‘file1.txt’] .

package mainpack;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class MainClass {

	public static List getPathElements(Path path) {
		List list = new LinkedList();

		// The root element has to be added separately
		list.add(path.getRoot().toString());

		for (int i = 0; i < path.getNameCount(); ++i) {
			list.add(path.getName(i).toString());
		}
		return list;
	}

	public static void main(String argv[]) {
		// The location of the file
		Path path1 = Paths.get(System.getProperty("user.home"), "Downloads",
				"file1.txt");

		List list = getPathElements(path1);
		Iterator it = list.iterator();
		while (it.hasNext())
			System.out.println(it.next());
	}
}

And the output is:

C:\
Users
andreinc
Downloads
file1.txt

4. Getting the subpath We can extract a relative path using a subpath method. This method accept two parameters, the start (index) and end (index) in the subsequence of elements.

package mainpack;

import java.nio.file.Path;
import java.nio.file.Paths;

public class MainClass {

	public static void main(String argv[]) {

		// The location of the file
		// C:\Users\andreinc\Downloads\file1.txt

		Path path1 = Paths.get(System.getProperty("user.home"), "Downloads",
				"file1.txt");

		for (int i = 0; i < path1.getNameCount(); ++i) {
			for (int j = i + 1; j < path1.getNameCount(); ++j) {
				System.out.println(String.format("subpath(%d, %d) = %s", i, j,
						path1.subpath(i, j)));
			}
		}
	}
}

And the output is:

subpath(0, 1) = Users
subpath(0, 2) = Users\andreinc
subpath(0, 3) = Users\andreinc\Downloads
subpath(1, 2) = andreinc
subpath(1, 3) = andreinc\Downloads
subpath(2, 3) = Downloads

5. Combining two paths
This allows you to define a fixed root path and append to it partial paths.

package mainpack;

import java.nio.file.Path;
import java.nio.file.Paths;

public class MainClass {

	public static void main(String argv[]) {

		// The location of the file
		// C:\Users\andreinc\Downloads\file1.txt

		Path fixedPath = Paths.get(System.getProperty("user.home"), "Downloads");
		System.out.println("Fixed path is: " + fixedPath + "\n");
		
		Path file1 = fixedPath.resolve("file1.txt");
		System.out.println("File1: " + file1);
		
		Path file2 = fixedPath.resolve("file2.txt");
		System.out.println("File2: " + file2);
		
		// You can also resolve a "sibling", a file existing at the same level
		Path file3 = file2.resolveSibling("file3.txt");
		System.out.println("File3: " + file3);
	}
}

Output:

Fixed path is: C:\Users\andreinc\Downloads

File1: C:\Users\andreinc\Downloads\file1.txt
File2: C:\Users\andreinc\Downloads\file2.txt
File3: C:\Users\andreinc\Downloads\file3.txt

6. Comparing two paths
Two paths can be compared as objects, using the “equals()” function. This method is related to the “equals()” method of class Object, thus the compared paths are not required to exist, and this method does not check if the paths are the same file.

But sometimes we want to check if two Path objects represent the same file / folder . In this case we will need to use java.nio.File.Files.isSameFile() method.

Eg.:

// The location of the file
Path fixedPath = Paths.get(System.getProperty("user.home"), "Downloads");
Path file1 = fixedPath.resolve("file1.txt");
Path file2 = fixedPath.resolve("file1.txt");
	
try {
	System.out.println("file1 isSameFile file2: " + Files.isSameFile(file1, file2));
} catch (IOException e) {
	e.printStackTrace();
}

Output:

file1 isSameFile file2: true

The Path class implements the Comparable interface, thus we can compare two paths by using the compareTo() method . The comparation will be lexicographical, so this will be useful for “sorting”.


7. Iterating over the elements of a Path

The Path class implements the Iterable interface, so we can use a foreach to iterate over the elements of a Path.

Eg.:

// The location of the file
Path path = Paths.get(System.getProperty("user.home"), "Downloads", "file1.txt");
for(Path p : path)
	System.out.println(p);

Output:

Users
andreinc
Downloads
file1.txt