Java SE 7 - New File IO
Java SE 7 has many enhancements and new APIs. In a previous blog, I discussed the new Fork/Join framework. In this blog, I'll talk briefly about the new Java.NIO 2.0 APIs, and specifically about how much easier it is to work with files. To be clear, although NIO used to mean New IO, Oracle now says that it really stands for Non-blocking IO.
Java NIO 2.0 (sometimes referred to as NIO.2) provides comprehensive support for the filesystem. Besides enhancements to the way you create, open, read, and write to files, you can now create custom filesystems and file providers in Java as well. One example is the ZIP file provider, which comes with the API and treats an archive file as a filesystem in its own right. In this specific case, the ZIP filesystem makes it much easier to work with ZIP archive files for both creation and expansion. We'll examine Java SE 7 File System Providers in a future blog. For now, let's dive into the new File IO APIs.
Java 1.6 File IO: Ancient History
Prior to Java SE 7, File IO involved a fair amount of code, some of it nonintuitive and difficult to remember even for Java veterans. For instance, I would often find myself searching my older code and cutting/pasting into new projects. Here's an example of some of the tedious code required in Java SE 6 to open a file:
// Try loading from the current directory InputStream is = null; try { File f = new File("config.xml"); is = new FileInputStream( f ); } catch ( Exception e ) { /*…*/ } // Or, if that fails, you can open the file by searching in the // Java application's class path: try { if ( is == null ) { // load from classpath is = getClass().getResourceAsStream("config.xml"); } } catch ( Exception e ) { System.out.println("config.xml file does not exist"); return; }
And once you open the file, you should probably buffer its input data for efficiency. That means you need to work with its bytes one (or a bunch) at a time:
BufferedInputStream bis = new BufferedInputStream(is); int data = -1; while ( (data = bis.read()) != -1 ) { // ... }
Or perhaps you know the format of the data, such as a file that contains a list of values of the primitive type double
. To work with such data-formatted file content, you could use the DataInputStream
class:
DataInputStream dis = new DataInputStream(is); int length = dis.readInt(); // first int is count of doubles double[] d = new double[length]; for ( int i = 0; i < length; i++ ) { d[i] = dis.readDouble(); }
Alternatively, you can read a file using the FileReader
class, which is altogether very different:
File f = new File("config.xml"); FileReader fr = new FileReader(f); BufferedReader reader = new BufferedReader(fr); String line = reader.readLine(); while ( line != null ) { // ... line = reader.readLine(); }
Lots of choices, lots of code. Java SE 7 does much to simplify and unify all of this. Let's look at some examples to contrast now.
Java 1.7 Files Class
First, get to know the Files
class new in Java SE 1.7. With it, you can easily create new files, create temporary files, create new (and temporary) directories, copy and move files various different ways, create symbolic links, open existing files, delete files, work with file attributes, read from files, and write to files. Just about everything you've ever wanted to do with a file can now be done through this one class. What I like about this approach is that it's easy to remember, and saves me from fumbling through the API docs or through past projects to find File IO code.
The Files
class is your one entry point for most file operations, and all you need to do is type"'Files
" in your IDE to see the list of API methods at your disposal. Awesome!
Want to open an existing file by path? Here it is:
Path path = FileSystems.getDefault().getPath(".", name);
Want to read all of the bytes in that file? No more looping, here it is:
byte[] filearray = Files.readAllBytes(path);
Would you rather deal with the file line-by-line? No problem:
List<String> lines = Files.readAllLines(path, Charset.defaultCharset() );
Is it a large file that you'd rather use buffered IO with? Here you go:
BufferedReader reader = Files.newBufferedReader(path, Charset.defaultCharset() ); String line = null; while ( (line = reader.readLine()) != null ) { /* … */ }
Surely, it must take more than that to create and write to file, right? Wrong:
String content = … Files.write( path, content.getBytes(), StandardOpenOption.CREATE); // create new, overwrite if exists
You can buffer the output and write all of the bytes with one extra line of code:
BufferedWriter writer = Files.newBufferedWriter( path, Charset.defaultCharset(), StandardOpenOption.CREATE); writer.write(content, 0, content.length());
In fact, the StandardOpenOption
class in Java 1.7 contains all of the file and directory attributes you need to deal with file existence create, delete, overwrite, append, and other options. Again, having this all in one helper class helps a great deal.
But what about all that existing File IO code you have? Do you need to throw it all away and start over to use the new Files
class? No way! The old and new File IO APIs can be used together:
Path path = FileSystems.getDefault().getPath(".", name); InputStream in = Files.newInputStream(path); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); List<String> lines = new ArrayList<>(); String line = null; while ( (line = reader.readLine()) != null ) lines.add(line); // ...
The following code (which is complete and should compile if you have Java SE 1.7 installed) contains a list of helper methods to illustrate the new File IO operations:
package java7fileio; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.List; /** * * @author Eric Bruno */ public class Java7FileIO { String myXML = "<Configuration>\n" + " <Server>\n" + " <Name>MyServer</Name>\n" + " <Address>192.168.0.1</Address>\n" + " </Server>\n" + " <RetryCount>10</RetryCount>\n" + " <Logs>\n"+ " <FilePath>/logs/out.txt</FilePath>\n"+ " <Level>INFO</Level>\n"+ " </Logs>\n"+ "</Configuration>"; public static void main(String[] args) { Java7FileIO j7fio = new Java7FileIO(); List<String> lines = null; // Write XML String bytes to a file j7fio.writeFileBytes("config.xml", j7fio.myXML); // Read all bytes from small file at once System.out.println("\n-- TEST 1 ---------------------------"); String config = new String(j7fio.readSmallFileBytes("config.xml")); System.out.println(config); // Read all lines from a small file at once System.out.println("\n-- TEST 2 ---------------------------"); lines = j7fio.readSmallFileLines("config.xml"); for ( String line: lines ) System.out.println(line); // Read all lines for a larger file System.out.println("\n-- TEST 3 ---------------------------"); lines = j7fio.readLargeFileLines("log.txt"); for ( String line: lines ) System.out.println(line); // Read all lines for a larger file System.out.println("\n-- TEST 4 ---------------------------"); lines = j7fio.readLargeFileLinesMixed("log.txt"); for ( String line: lines ) System.out.println(line); } public Java7FileIO() { } public byte[] readSmallFileBytes(String name) { byte[] filearray = null; try { Path path = FileSystems.getDefault().getPath(".", name); return Files.readAllBytes(path); } catch ( IOException ioe ) { ioe.printStackTrace(); } return filearray; } public List<String> readSmallFileLines(String name) { try { return Files.readAllLines( FileSystems.getDefault().getPath(".", name), Charset.defaultCharset() ); } catch ( IOException ioe ) { ioe.printStackTrace(); } return null; } public List<String> readLargeFileLines(String name) { try { BufferedReader reader = Files.newBufferedReader( FileSystems.getDefault().getPath(".", name), Charset.defaultCharset() ); List<String> lines = new ArrayList<>(); String line = null; while ( (line = reader.readLine()) != null ) lines.add(line); return lines; } catch (IOException ioe) { ioe.printStackTrace(); } return null; } public List<String> readLargeFileLinesMixed(String name) { try { Path path = FileSystems.getDefault().getPath(".", name); InputStream in = Files.newInputStream(path); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); List<String> lines = new ArrayList<>(); String line = null; while ( (line = reader.readLine()) != null ) lines.add(line); return lines; } catch ( IOException ioe ) { ioe.printStackTrace(); } return null; } void writeFileBytes(String filename, String content) { try { Files.write( FileSystems.getDefault().getPath(".", filename), content.getBytes(), StandardOpenOption.CREATE); } catch ( IOException ioe ) { ioe.printStackTrace(); } } void writeFileBytesBuffered(String filename, String content) { try { BufferedWriter writer = Files.newBufferedWriter( FileSystems.getDefault().getPath(".", filename), Charset.forName("US-ASCII"), StandardOpenOption.CREATE); writer.write(content, 0, content.length()); } catch ( IOException ioe ) { ioe.printStackTrace(); } } }
Happy coding!
EJB