Java Basics: File I/O

The Basics of Reading and Writing Files in Java

In this installment of the Java Basics series, we’re going to take a look at how to read and write files in Java.  We’ll only be looking at the Java IO package in this post.  Watch for a follow up post that covers the Java’s NIO File package (added in Java 7).

We’ll start by reading files, then move on to writing them.  After that we’ll take a look at the java.io.File object and then finish up with an example file copying utility.

File I/O provides plenty of opportunities for exceptions to be thrown.  So if you’re not familiar with how exception handling is done in Java, I’ve got a Java Basics post for that.

The examples in this post are written in Eclipse and all the files being used are placed under the main project.  This allows us to access the files by their name.  It also skirts any potential permissions or security issues.  The way access to files is handled is going to vary depending on the OS being used and how users and permissions are set up.

I/O Streams

We’re going to be using I/O Streams to read and write files.  This is a good basic building block, because once you learn to read and write from streams for file I/O, you’ll be able to easily work with other types of streams.  An I/O stream simply represents either a source of input or a destination for output.  So for our purposes today, that’ll be files.

Reading Files

The most basic way to read from a file is to use the java.io.FileInputStream.  We’re going to be reading a text file, so that we can see meaningful data read from a file.  However, the java.io.FileInputStream, is typically recommended for binary data.  In our example, we instantiate the FileInputStream by passing the file name.  In Eclipse, our example files can reside in the project directory and just providing a file name will work.  To read in each byte one at a time, we loop on the read() method until we get a -1 back which indicates that we’ve reached the end of the file.  We have to cast each byte into a character, then we can read our output.  We catch a FileNotFoundException and use a finally block to make sure we close our input stream.

InputStream is = null;
try {
   is = new FileInputStream(FILE_NAME);
   int input;
   String output = "";
         
   while ((input = is.read()) != -1) {
      output += (char) input;
   }
   System.out.println(output);
} catch (FileNotFoundException e) {
   System.err.println(String.format("Exception creating FileInputStream for file %s: %s", FILE_NAME, e.getMessage()));
} finally {
   if (is != null) {
      is.close();
   }
}

As mentioned above, the FileInputStream is mainly for binary data.  For character streams, the java.io.FileReader is an option.  It’s used nearly identically to the FileInputStream.  Again, be sure to close the stream in the finally block or use the try-with-resources construct if using Java 7 or greater.  The examples in this post will use a mix of the two.

InputStreamReader reader = null;
try (BufferedReader reader = new BufferedReader(new FileReader(FILE_NAME))){
   String line;
   
   while ((line = reader.readLine()) != null) {
      System.out.println(line);
   }
} catch (FileNotFoundException e) {
   System.err.println(String.format("Exception creating BufferedReader for file %s: %s", FILE_NAME, e.getMessage()));
}

To read text more efficiently, the character streams can be buffered using a java.io.BufferedReader.  This is very similar to our FileReader example, except we’re wrapping our FileReader with the BufferedReader.

try (BufferedReader reader = new BufferedReader(new FileReader(FILE_NAME))){
   String line;

   while ((line = reader.readLine()) != null) {
      System.out.println(line);
   }
} catch (FileNotFoundException e) {
   System.err.println(String.format("Exception creating BufferedReader for file %s: %s", FILE_NAME, e.getMessage()));
}

In addition to the input streams we’ve discussed here, there are also streams for dealing with primitive types and objects.

Writing Files

As with reading files, there are streams meant for binary data, streams meant for character data and streams for buffering.  There are also the streams for primitives and objects that we won’t be covering here.

I’ve got output text defined as a constant, so let’s take a look at that first to get our bearings.

private static final String OUTPUT_TEXT = 
         "This is my example text.\nI'ts not terribly long or exciting";

Now, let’s take a look at the simple java.io.FileOutputStream.  We create it in the same way we did the FileInputStream.  Then we convert our string to a byte array and write it to the file.  The flush() method ensures that any bytes written that may be buffered or internally cached are written to the file.

try (FileOutputStream out = new FileOutputStream(STREAM_OUTPUT_FILE);){
   byte[] data = OUTPUT_TEXT.getBytes();
   out.write(data);
   out.flush();
} catch (FileNotFoundException e) {
   System.err.println(String.format("Exception creating FileOutputStream for file %s: %s", STREAM_OUTPUT_FILE, e.getMessage()));
} catch (IOException e) {
   System.err.println(String.format("Exception writing to file %s: %s", STREAM_OUTPUT_FILE, e.getMessage()));
}

As with reading a file, there are character streams for writing text files.  We’ll use a java.io.FileWriter to write a file now.  This snippet of code is very similar to the last bit of code, except that we no longer have to convert our OUTPUT_TEXT into a byte array.

try (FileWriter out = new FileWriter(WRITER_OUTPUT_FILE)) {
   out.write(OUTPUT_TEXT);
   out.flush();
} catch (FileNotFoundException e) {
   System.err.println(String.format("Exception creating FileWriter for file %s: %s", WRITER_OUTPUT_FILE, e.getMessage()));
} catch (IOException e) {
   System.err.println(String.format("Exception writing to file %s: %s", WRITER_OUTPUT_FILE, e.getMessage()));
}

For more efficiently writing text files, there’s a java.io.BufferedWriter class.  We create our BufferedWriter by wrapping it around a FileWriter.  Then we loop through an array of strings.  After each call to the write method, we add move to the next line using the newLine method.  This is preferable to using “\n” because new lines vary depending on the operating system and the newLine method will use what’s appropriate for the OS.

BufferedWriter out = null;
String[] lines = {"This is my first line of text", 
   "This is the second line", 
   "And this is the third line"};

try {
   out = new BufferedWriter(new FileWriter(BUFFERED_OUTPUT_FILE));
   for (String line : lines) {
      out.write(line);
      out.newLine();
   }
   out.flush();
} catch (FileNotFoundException e) {
   System.err.println(String.format("Exception creating FileWriter for file %s: %s", BUFFERED_OUTPUT_FILE, e.getMessage()));
} catch (IOException e) {
   System.err.println(String.format("Exception writing to file %s: %s", BUFFERED_OUTPUT_FILE, e.getMessage()));
} finally {
   if (out != null) {
      out.close();
   }
}

Working with java.io.File

Sometimes we need information or to operate on a file in ways that doesn’t involve reading or writing to it.  We can use the java.io.File class for that.  We instantiate our File object by passing the file name (or a path to a file or directory) to the constructor.  Then we can get information from it about whether it exists, if it’s a directory or file, what kind of access does the application have to it and path information.  And plenty more that’s not covered here.  Additionally, all those streams we just talked about can take a file object instead of just a file name or path as a string.  This is especially handy when you need to know things about or act on a file or directory before working with it.

File exampleInput = new File(FILE_NAME);

System.out.println(String.format("Does it exist? %b", exampleInput.exists()));
System.out.println(String.format("Is it a directory? %b", exampleInput.isDirectory()));
System.out.println(String.format("Is it a file? %b", exampleInput.isFile()));
System.out.println(String.format("Can the app read it? %b", exampleInput.canRead()));
System.out.println(String.format("Absolute Path: %s", exampleInput.getAbsolutePath()));
System.out.println(String.format("Path: %s", exampleInput.getPath()));

FileReader reader = null;
BufferedWriter out = null;
try {
   reader = new FileReader(exampleInput);
   out = new BufferedWriter(new FileWriter(exampleInput));
} finally {
   if (reader !=null) reader.close();
   if (out != null) out.close();
}

Copying Files

A common thing we do with data is move it around.  So now that we’ve gone over the basics of reading and writing to files, we’ll look at a couple of simple file copy examples.

First, we’ll copy a binary file.  The example contains a jpg image that we’ll copy into an identical file.  First we open a try-with-resources containing an InputFileStream to the image and an OutputFileStream with a name for the copied file.  We create an empty byte array for holding bytes as they’re being copied.  In our while loop, we read into the byte array and then write it immediately back out to the copy.  As an optional exercise, try modifying this method to use the single byte read and write and see how much slower it runs.

try (
   InputStream in = new FileInputStream(IMAGE_FILE);
   OutputStream out = new FileOutputStream(IMAGE_COPY)
) {

   byte[] data = new byte[16];
   while ((in.read(data)) != -1) {
      out.write(data);
   }
   out.flush();
} catch (FileNotFoundException e) {
   System.err.println(String.format("Unable to find file: %s", e.getMessage()));
} catch (IOException e) {
   System.err.println(String.format("Exception reading or writing: %s", e.getMessage()));
}

Next, we’ll use our buffered character streams to copy a text file line by line.  We define our BufferedReader and BufferedWriter in the try-with-resources.  Then our while loop reads each line one at a time and then writes it back out to the copy and inserts a newline after each line.

try (
   BufferedReader in = new BufferedReader(new FileReader(TEXT_FILE)); 
   BufferedWriter out = new BufferedWriter(new FileWriter(TEXT_COPY))
   ) {
   String line;
   
   while ((line = in.readLine()) != null) {
      out.write(line);
      out.newLine();
   }
   out.flush();
} catch (FileNotFoundException e) {
   System.err.println(String.format("Unable to find file: %s", e.getMessage()));
} catch (IOException e) {
   System.err.println(String.format("Exception reading or writing: %s", e.getMessage()));
}

Conclusion

In this post, we covered the basics of working with files in Java.  We demonstrated a few ways of reading and writing files using different I/O stream classes and got acquainted with the File object.  We wrapped up by looking at a couple of simple file copy utilities.  I plan to follow up with a post about the NIO File package which was added to address some of the limitations to the original IO classes.

The example code is available over on GitHub.

Coffee Photo by Nathan Dumlao on Unsplash

Advertisements

8 thoughts on “Java Basics: File I/O

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s