Architecture & DesignKey Classes for Handling Files with Java NIO.2

Key Classes for Handling Files with Java NIO.2

Java NIO2 introduced several improvements in the way that files are handled in Java. The enhancements are based on classes defined in the java.nio.files package. The Java NIO2 is built upon two foundational techniques, called buffers and channels. A buffer is used to hold data item and a channel represents an open connection to an IO device. The core aspect of using NIO2 in Java programming is to obtain a channel for IO and create a buffer to hold data items in the communication process. This article discusses some of the key classes that are commonly used in file handling in Java.

The Path Interface

The Path interface of the java.nio.file package is the key to dealing with files in the first place; it encapsulates a path to a file. The primary use of this object is to describe the file location within the directory structure of the file system. This interface inherits the characteristics of Comparable<Path>, Iterable<Path>, and the Watchable interfaces. The Watchable interface enables this interface to be watched for changes and events. The Iterable interface enables the object of this interface to be used for for-each loop statements. The Comparable interface imposes an ordering principle according to the natural ordering elements and the use of compareTo() method.

This interface is an enhancement of the well-known java.io.File class. Our usual implementation using this object is as follows.

import java.io.File;
// ...
File file=new File("myfile.txt");

Now, with NIO2 we may do this in the following manner.

import java.nio.file.Path;
import java.nio.file.Paths;
// ...
Path path=Paths.get("myfile.txt");

The Paths class is a helper class that defines two static methods and can be used to work with both Absolute and Relative paths given as a string sequence. The path sequence, however, is sensitive to the underlying file system used by the platform (such as Windows, Linux, and so forth).

  • static Path get(String first, String …more): Converts the sequence of a string to a Path
  • static Path get(URI uri): Converts an URI to a Path object

Example

The Absolute path can be chunked as follows:

Path path1=Paths.get("/home/mano/test_doc/","myfile.txt");
Path path2=Paths.get("/home/mano","test_doc/","myfile.txt");
Path path3=Paths.get("home","mano","test_doc","myfile.txt");

The relative path to the working directory may be chunked as follows.

Path path4=Paths.get("mano","test_doc/myfile.txt");

We also can also normalize a relative path, as follows. The difference between normalizing a relative path and not normalizing it is that normalization removes the redundant elements in the path.

Path path1=Paths.get("/home/./mano/Documents
   /../myfile.txt");
Path path2=Paths.get("/home/./mano/Documents
   /../myfile.txt").normalize();

System.out.println("Not Normalized: "+path1);
System.out.println("Normalized: "+path2);;

Output

Not Normalized: /home/./mano/Documents/../myfile.txt
Normalized: /home/mano/myfile.txt

We may use the URI to create the path objects as follows:

Path path3=Paths.get(URI.create("~/myfile.txt"));

The Path supplies many utility functions to get information about the path. A few of them are as follows:

Path path=Paths.get("/home/./mano/Documents
   /../myfile.txt").normalize();
System.out.println("File Name :"+path.getFileName());
System.out.println("File system :"+
   path.getFileSystem().toString());
System.out.println("Parent :"+path.getParent());
System.out.println("Root :"+path.getRoot());

for(int i=0;i<path.getNameCount();i++)
   System.out.println(path.getName(i));

File Metadata

Sometimes, we want to get the metadata information of a file, such as its size, owner, whether it’s a hidden file, whether it is a directory, and so on. The package java.nio.file.attribute provides six view interfaces to obtain the information.

  • BasicFileAttributeView: Provides the basic view of file attributes common to many file systems.
  • DosFileAttributeView: Provides the view of file attributes found in legacy DOS file systems.
  • PosixFileAttributeView: Provides the view of file attributes found in the file system that implements the POSIX standard.
  • FileOwnerAttributeView: Provides the view of file attributes found in the file system that supports the concept of a file owner.
  • AclFileAttributeView: Provides the view of file attributes that supports reading and updating the file’s Access Control List (ACL) or file owner attributes.
  • UserDefinedFileAttributeView: Provides the view of file attributes that is user defined.

If we want to find out the type of file system view supported by the underlying system, we may test with the help of the following code snippet.

import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.util.Set;
// ...
FileSystem fileSystem=FileSystems.getDefault();
Set<String> set=fileSystem.supportedFileAttributeViews();

for(String str: set)
   System.out.print(str+" ");

Output

owner dos basic posix user unix

We may get the file attribute meta data as follows.

package org.mano.example;

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 Main {

   public static void main(String[] args) {

      Path path = Paths.get("/home/mano/myfile.txt");
      BasicFileAttributes a = null;
      try {
         a = Files.readAttributes(path, BasicFileAttributes.class);
      } catch (IOException ex) {
         System.err.println(ex);
      }
      System.out.println(path.getFileName() + " created at "
         + a.creationTime());
      System.out.println(path.getFileName() + " size is "
         + a.size()+" bytes.");
      System.out.println(path.getFileName() + " last accessed at "
         + a.lastAccessTime());
      System.out.println(path.getFileName() + " last modified at "
         + a.lastModifiedTime());
      System.out.println(path.getFileName() + " is a "
         + (a.isDirectory() ? "directory" : "file"));
      System.out.println(path.getFileName() + " "
         + (a.isRegularFile() ? "is a regular File" :
         "is not a regular file"));
      System.out.println(
         path.getFileName() + " "
         + (a.isSymbolicLink() ? "is a symbolic link" :
         "is not a synbolic link"));

   }
}

Output 2

myfile.txt created at 2017-03-22T08:58:07Z
myfile.txt size is 101 bytes.
myfile.txt last accessed at 2017-03-22T08:58:07Z
myfile.txt last modified at 2017-03-22T08:58:07Z
myfile.txt is a file
myfile.txt is a regular File
myfile.txt is not a synbolic link

File Processing

Creating a file and then reading and writing content in it is the basic operation performed in any file. Java NIO2 provides numerous methods to perform these actions. The complexity of file processing varies depending upon our requirements. We can deal with basic file operations to advanced operations, such as file locking and memory-mapped IO.

Data to be written into the file can be a simple byte array to a string representing different character set such as UTF-8, ISO-8859-1, and the like. A stream that represents an input source to output destination can be used with different data types, such as a localized character set, or objects apart from primitive data types. The read, write scheme can be applied with both buffered and unbuffered schemes of file operation.

A file can be opened with variety of standard options, such as READ, WRITE, CREATE_NEW, APPEND, DELETE_ON_CLOSE, TRUNCATE_EXISTING, SPARSE, SYNC, and DSYNC. They are defined by the optional parameter during creation or opening a file called OpenOption. The OpenOption is an interface declared in the java.nio.file package.

Creating a File

The simplest way to create a file is as follows:

Path path = FileSystems.getDefault().getPath("./myfile.txt");
// ...
try{
   Files.createFile(path);
}catch(IOException ex){
   System.err.println(ex);
}

We can add optional file attributes in the following manner.

Path path = FileSystems.getDefault().getPath("./myfile.txt");
Set<PosixFilePermission> permission=PosixFilePermissions
         .fromString("rw-rw-rw-");
FileAttribute<Set<PosixFilePermission>>
   attribute=PosixFilePermissions
         .asFileAttribute(permission);
try{
   Files.createFile(path, attribute);
}catch(IOException ex){
   System.err.println(ex);
}

Writing a File

There are several ways file content can be written. The utility class Files provides a variation of the write method. Let’s try a few of them.

try{
   // ...

   String str="This is first line.";
   byte[] data=str.getBytes("UTF-8");
   Files.write(path, data)

}catch(IOException ex){
   System.err.println(ex);
}

Or, we can use an array list to write several lines of content while opening the file in APPEND mode.

try{
   // ...
   Charset charSet=Charset.forName("UTF-8");
   List<String> list=new ArrayList<>();
   list.add("nThis is second line");
   list.add("This is third line");
   list.add("This is fourth line");

   Files.write(path, list, charSet, StandardOpenOption.APPEND);

}catch(IOException ex){
   System.err.println(ex);
}

Reading a File

The Files utility class provides several variations of methods to read the contents of a file. One of the methods is to read the entire contents in a byte array.

try{
   // ...
   byte[] contents=Files.readAllBytes(path);
   String str=new String(contents, "UTF-8");

   System.out.println(str);

}catch(IOException ex){
   System.err.println(ex);
}

One of the disadvantages of using an array to read contents of a file is that there is a limitation on its size. If a file is of, say, more than 2GB, it is ridiculous to read its contents in an array.

Another convenient way to read contents of a file is by using the readAllLines() method, which returns a List<String>. We can loop through the list to get all the contents of the file.

try{
   List<String> list=Files.readAllLines
      (path,Charset.forName("UTF-8"));
   for(String s: list){
      System.out.println(s);
   }

}catch(IOException ex){
   System.err.println(ex);
}

Buffered Read, Write a File

Buffered readers and writers can be used to relieve expensive read, write operations performed by native IO operation of the underlying platform. The simplest way is to use BufferedReader of the java.io package, as follows:

try(BufferedReader reader=Files
.newBufferedReader(path, Charset.forName("UTF-8"))){
   String s=null;
   while((s=reader.readLine())!=null){
      System.out.println(s);
   }
}catch(IOException ex){
   System.err.println(ex);
}

Similarly, we can perform a buffered write operation by using BufferedWriter of the java.io package.

String str="This is the last line.";
try(BufferedWriter writer=Files
.newBufferedWriter(path, Charset.forName("UTF-8"),
StandardOpenOption.APPEND))
{
   writer.write(str);
}catch(IOException ex){
   System.err.println(ex);
}

Conclusion

Apart from these, a file can be read, write by using an unbuffered stream, as well. They can be used as-is or converted into a buffered scheme prior to plying the read, write operation. Java NIO2 file processing also includes directory operations such as creating one, deleting, traversing through directory tree, comparison, and many other common operations. Most methods are defined by the utility class called Files. A study on the methods declared in this class will give all the ideas one need in file processing with Java NIO2.

Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.
Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.

Latest Posts

Related Stories