Return to Course Content Home

Java I/O

 

Important note on images in this module

Please note that the screenshots in this section were made with an earlier version of the JDK and Elipse. Since everything covered in this module still works the same in the latest JDK/Eclipse versions, they have not been updated as they fully apply.

First a disclaimer

Java I/O, let's see, brings to mind a certain movie...

The Good, the Bad and the Ugly

Seriously, Java I/O has had more than its share of updates. The initial API set seemed like a research project for a computer scientist. It was very modular, and allowed you to build Input/Output "chains" by passing one class as an argument to another class's constructor. Very flexible...very non-intuitive.

Required Reading

The Java Tutorial on Basic I/O. Read the I/O Streams section and the File I/O section. Note that the File I/O section includes the lastest NIO.2 (New I/O version 2) components, which actually should make things easier.

You will not be responsibile for knowing *everything* there is about I/O, so if you want to skim the File I/O section, feel free.

Helpful Hint:

One important, but easily missed detail when using sockets is that when you close the stream, it automatically closes the associated socket.

File I/O (Original classes until Java 7 added new features)

Now that you've read the tutorial on Basic I/O, let's run through some examples of how it is usually used.

The first step in File I/O is getting a reference to the file itself. Java has a File object which lets you reference files on your system.

File objects represent file names on the underlying operating system. A File object doesn't need to point to an actual file on the system, it can be a name of a file you will create soon, but haven't yet. Just because you can make a File object doesn't mean the file or directory actually exists.

One way to select a file is to use a Swing component to select the file. If you want to make a File object yourself, you can have it reference a File or a Directory.

If you make a File object manually, right away we get into some problems. Files are part of the underlying Operating System, and Java tries to be platform independent. Fortunately, there are only really two variants of Operating Systems we need to deal with Windows, and *nix (Unix, Linux, BSD Linux (Mac OS X).

Windows paths are configured as follows. The pathname may contain a drive specifier consisting of the drive letter followed by ":" and possibly followed by "\\" if the pathname is absolute. Then any further directories are separated by more "\" characters.

Unix absolute paths always start with a "/", and there are no such things as drive letters. The entire file system descends from the "/" path. Any further directories are serrated by more "/" characters.

If you know your application is running on a given platform, you can just use the exact path. In some cases, this is acceptable. Typically, you will either use a JFileChooser to let a user select a file, you you want to open a file relative to your application's installation.

The File object has four different constructors. The first and one of the more common ones is that it accepts a String as an argument. Java tries to have some common sense and will accept Strings that describe 'native" file paths.

File file = new File("foo.txt"); // gets the file in the current directory
File file = new File("/home/bob/foot.xt"); // unix directory structure
File file = new File("c:\java\foo.txt"); // windows directory structure
File file = new File("."); // get the current directory
File file = new File("../foo.txt"); // go up one directory and get the file

You can get the user's home directory with the System.getProperty("user.dir") method call. To obtain the file or path separator used on the operating system that the JVM is running on you can use the static methods:

File.separator()
File.pathSeparator()

The second constructor uses a URI as an argument. This represents a Uniform Resource Identifier (URI) reference. A URI is an object that represents any type of network resource. Why do we want to to this? Two reasons.

  1. If we want to load a file from anything other than a standard application, we should use getClass().getResource() to get a URL description of the file. The URL object has a toURI() method. This lets us use the getClass().getResource() method to use all of our files, without having to worry about if they are applets, web start, or jar files.
  2. We can use a File object to access http files.

There are several useful methods in the File object. One of the most basic is the "exists()/isFile()/isDirectory()" method which checks to see if the File object you've created actually maps to a real object on the operating system. If you want to see if the file /home/bob/foo.txt exists, you would use:

File file = new File("/home/bob/foo.txt");
if (file.exists()) {
  System.out.println("file exists");
if (file.isFile()) {
System.out.println("It is a file");
} else if (file.isDirectory()) {
System.out.println("It is a directory") } }

Several of the other useful methods are:

canRead()/canWrite()
Is a file readable and/or writeable

list()
If the File object is a directory, it returns an array of Strings (String[]) that list all files and directories contained in that directory.

mkdir()
Creates a new directory matching this File object

delete()
Delete the file, returns true if successful

toURL()
Converts the file path to a URL object

If you wanted to write a program to list the contents of your home directory, the following program would do such.

import java.io.*;
public class DirListing {
  public static void main(String[] args) {
    File dir = new File(System.getProperty("user.dir"));
    if (dir.isDirectory() {

      System.out.println("Directory of " + dir);
      String[] listing = dir.list();
      for (int i=0; i<listing.length; i++) {
        System.out.println("\t" + listing[i]);
      }
     }
   }

}

The above would not recurse through the directories contained in your home directory, but does lists the contents at the top level.

 

File I/O (New features available from Java 7 on)

As you read in the tutorial, Java 7 introduces the Path class. If you've ever had to use the fold File objects, you realize there are some inherent weaknesses that crop up (mostly due to Java's initial "platform independence", in other words, Files are a part of a platform, so we really won't worry about handling details....).

The Path class is designed to take what worked in a File object and extend it to something that is more usefull. Like File objects, a Path is an "idea". Just because you have a Path, it doesn't mean that the Path actually exists, you need to verify that it is there before you go and use it.

You create a Path by typically using the Paths.get() method (the FileSystem class also has some methods for creating Path objects).

Path aPath = Paths.get("/etc/hosts");

Now this looks a lot like a File object, but the Path object contains a bit more information than the File object ever had. One of the more interesting things is that with Files, a path (not a Path!) was always in terms of forward slashes (foo/bar/baz), no matter what the platform. With a path object, the toString() method returns the path in the native OS format (foo/bar/baz for *nix, foo\bar\baz for Windows)

Another interesting trait in the Java 7 release is the try-with-resources statement. Prior to Java 7, any methods that access the file system throw an IOException. To handle these exceptions, you sometimes had to write a somewhat awkward try-catch statement. In Java 7, there is a try-with-resources statement. This new "try" format automatically generates the code to close any resources that are no longer required when the block exits.

Lets look at the example that is shown in the Oracle tutorial in a bit more detail, as some interesting points are omitted in the on-line version.

Charset charset = Charset.forName("US-ASCII");  
String s = ...;  
try (BufferedWriter writer = Files.newBufferedWriter(file, charset)) {
      writer.write(s, 0, s.length());  
} catch (IOException x) {
      System.err.format("IOException: %s%n", x);  
}

First, note that immediately after the try, there is some code in the parenthesis. This is the resource that will be used in the try that will automatically be cleaned up (i.e. closed) at the end of the try, no matter what happened in the try.

Next, the BufferedWriter is created with the new Files class. If you aren't familiar with the Java 7 Files class (which is new), you will be impressed with what has finally been added to the NIO.2 features. There are a lot of really useful methods in this class. The one shown creqtes a BufferedWriter that is used to copy out the contents of the String s.

Note, the IOException is still caught, and some debug output sent to STDERR, but the code that you used to have to write to close the BufferedWriter is now automatically generated.

Java Input/Output Streams

Java takes a very flexible approach to the entire notion of streams. An I/O stream is an ordered sequence of undetermined length. A stream serves as an input or output source of data, be it binary or text. This flexibility at times seems extreme, and in "Just Java and Beyond" Peter van der Linden starts his chapter on File I/O by saying "It is not completely fair to remark, as some have, that support for I/O in java is "bone headed". Note the "completely fair" remark.

An I/O stream is an abstraction for many kinds of sources and destinations

Once you get the hang of chaining the classes together, Java I/O makes sense. It just seems over-elaborate sometimes. The designers decided that rather than make a plethora of individual classes, each with the appropriate traits, they would start with a few base classes, and use those classes as "core" for other wrapper classes. Each wrapper would add the appropriate functionality to the stream you wanted to create. You can define I/O streams that move standard bytes back and forth, or you can define streams that automatically compress, encrypt and even filter data. All of these different types of streams support the same basic methods.

Java I/O can be categorized into two distinct groups, binary and text I/O. With binary I/O, bytes are read from or written to a stream. Text I/O is designed to move character data.

Usually, when you use a stream, you are processing data that is expressed in bytes. If you are processing byte-oriented I/O you typically use DataStreams. If you want to use a stream for text data, you should use Readers and Writers. Readers and Writers are based on character data, which can be anywhere from one to four bytes long. ASCII uses one-byte, and Unicode used to only use two-bytes, but now has some four-byte character extensions.

You have already used an Output steam and may not have realized it. System.out.println() and System.err.println() represent the stdout and stderr output streams.

java.io.IOException

Because input and output operations are often unreliable, most of the methods involved with I/O through a checked java.io.IOException. This forces you, the programmer, to deal with often encountered errors when performing I/O.

Take a look at the following entry from BufferedReader.

java.io.BufferedReader readLine() method
BufferedReader.readLine()

Note that the method readLine() throws an IOException, so to use the readLine() method, you will have to surround it with a try-catch block.

 

Input and Output Streams

First, you may have heard of Streams in JDK 8, but these are not the droids you were looking for! Before Lambdas/Streams in Java 8, there were I/O streams.

The java.io package provides a great variety of input/output streams! So how do you determine which one to use? The best advice is to break down the classes in the java.io package by their class names. Take a look at the following types that are prefixes for many of the class names

Prefix
Used for...
Buffered... Used for most of your I/O needs, because you get buffered performance, you typically wrap these classes around other classes you find below.
File... Used when accessing files
Object... Used to read/write Java Objects directly to a stream
Data... byte data. The bytes may represent int, doubles, chars, etc...
Piped... Used to connect streams
CharArray... The stream is actually a variable length array of chars
String... The stream is actually a String

Without buffered I/O, each read and write request is handled by the operating system. Java's buffered streams read data from a buffer in memory and only involve an operating system read/write when memory area is empty.

So, if I wanted to read in text from a file...

  1. It's text, so I want to look for a Reader class
  2. It's from a file, so we'll use a FileReader class
  3. I'd like to buffer it, so I'm looking for a BufferedWriter

Reader is abstract, so we go to step 2. FileReader has a constructor, using either a File, a FileDescriptor or a String as an argument. Step 3 tells us we'd like to use a BufferedReader, but the BufferedReader doesn't have the constructors that the FileReader does, instead, it takes a Reader object as an argument.

This means to create a BufferedReader, we would do the following:

BufferedReader br = new BufferedReader(new FileReader(new File("filename.txt"));

Character File Ouput, Practice

Download and open the Netbeans project charoutput. In this project you can see where the output streams are opened and configured.


try {
out = new FileWriter("text.txt");
bout = new BufferedWriter(out);
pout = new PrintWriter(bout);
writeToFile(pout);

First a FileWriter is opened (you could have opened a File, and then used that to create the FileWriter). Then the FileWriter is used to make a BufferedWriter, and then the BufferedWriter is used to create a PrintWriter. Now here is a good question...Once we had the buffered writer, why did we need a PrintWriter? PrintWriter prints formatted representations of objects to a text-output stream. The big thing it gives us is the C-like printf statement that is used in this example to control output format.

Java programmers wanted a printf() statement for years, and it was the inclusion of this method that prompted Java to handle variable length arguments in methods, since this is essential for this method.

When you run the Eclipse app, you won't see any output, but if you look in the base directory of the porject, you will see a text.txt file.

Location of output file

You'll see a text.txt file. Double click on the file to open it and you'll see the output of the program.

Contents of output file

One other item in this example is the line that prints out the locale. To change the system default encoding, use:

System.setProperty("file.encoding", "<encoding scheme>");

Or you can specify the locale when creating the output stream. To set our output to a different locale:

new FileOutputStream("text.txt", "8859_1")

File Input, Practice

Download and open the Eclipse project charinput. In this example, you can see how to read in a file and then write out the lines you've read in to System.out.println().

Here, we create a FileReader, then a BufferedReader to read in the file. The readFromFile() method uses the readLine() method from BufferedReader. This reads in a line at a time.

Take a look at the code below.

Main method from the charinput project.
charinput project file

Here we see the getClass().getResource() method being used in a slightly different way. Since we are in the main() method, we can't call getClass() because we aren't inside of a class. Instead, we just use the Main.class to provide access to a class loader and then call getResource() from it.

Once we have the FileReader object, we use it to create a BufferedReader object. The BufferedReader object provides us with the readLine() method that allows us to read in a line at a time from a text file. The readFromFile() method is called to read in the file and print it out to the screen.

Console Input

To read input from the console, a stream must be associated with the standard input, System.in

try {
  System.out.println("Enter text: ");
  System.out.flush();
  keyboardStream = new BufferedReader(new InputStreamReader(System.in));
  line = kebyoard.readLine();

} catch (IOexception ioe) {

  System.err.println("Error reading from keyboard");
}

Converting Byte I/O to Character I/O

Java provides two classes for bridging character streams and byte streams. This lets you convert bytes to an from characters.

InputStreamReader extends the Reader class (which is character oriented). The constructor takes an InputStream (which is byte oriented) as an argument.

bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("test.dat")));

You normally would just use a FileReader, but this shows how to use the InputStreamReader to convert from bytes to characters.

OutputStreamWriter extends the Writer class. Like InputStreamReader, the constructor takes an OutputStream as an argument.

bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("test.dat")));

Binary file Input and Output

So far, we've covered I/O from files using text, but what if you had a file in binary format that you needed to read. Often times, data files are written out in binary format so that they can be used without having to convert from text to native types (String to int...).

Java provides two classes, DataInputStream and DataOutputStream, that allow you to read and write primitive data types directly to and from streams/files.

Data Type
DataInputStream method
DataOutputStream method
byte readByte writeByte
short readShort writeShort
int readInt writeInt
long readLong writeLong
float readFloat writeFloat
double readDouble writeDouble
boolean readBoolean writeBoolean
char readChar writeChar
String readUTF writeUTF
byte[] readFully  

The readFully method blocks until all bytes are read or an EOF occurs.

All values above are written in big-endian format regardless of computer platform. Big-endian is MSB (Most Significant Byte) first format. If you are only reading and writing binary files generated by Java programs, you won't really care about this. This is also the same format that network communications use, so you can transmit these values to other programs over the net without any concern.

The problem occurs when you want to read a binary file that is in little-endian format...like those generated by C for PCs. Note that "endian-ness" only applies to the short and int data types. You can use the class ByteOrder to determine at run time the "endian-ness" of your machine.

If you need to convert between formats, you can use the following two methods to do so.

// 2-byte number  
int SHORT_little_endian_TO_big_endian(int i)  {
      return ((i>>8)&0xff)+((i << 8)&0xff00);
}


// 4-byte number  
int INT_little_endian_TO_big_endian(int i)  {
      return((i&0xff)<<24)+((i&0xff00)<<8)+((i&0xff0000)>>8)+((i>>24)&0xff);
}

As with all I/O streams, you can do the simplest Binary I/O by using a FileInputStream object and just reading or writing bytes. If you wan to read or write int's or float's, you can use the DataInputStream, and if you want buffering, you can pass the DataInputStream to the BufferedInputStream()

Binary I/O practice

Download and open the Eclipse project binaryio. In this project we will write out ten int values to the file, and then read them back in and print them out to the screen. The window below shows the project and the output.

Binary I/O Project

Note one peculiarity to the reading in of the data. So often, people are used to reading in lines of data that when they are confronted by binary files they forget that they have to specify exactly how to read in the data. In this case, we know that there are int values in the file, so we can divide the file size by 4 (4 bytes per int) to get the number of int values that we should try to read in.

Formatting Output

It used to be (pre JDK 1.5), that you didn't have a lot of options when formatting output. There were a lot of people that weren't to happy about this and Sun finally put in C-like printf, sprintf facilities.

In the charoutput example above, you saw the use of the printf() method. You get the method whenever you use a PrintWriter object. In a more general case, the java.util.Formatter class specifies a huge list of formatting options that became available in JDK 1.5 (Java SE 5). Take a look at this class to see the examples of the different ways it can be used.

In addition to PrintWriter.printf() which uses Formatter formatting rules, there is also a String.format() method that lets you build a string from a C-like list of arguments.

From the Formatter class API
String Formatter

Secure Sockets

So far, the sockets we use are direct, unencrypted connections. This means if somebody is sniffing the network, they can see anything that you send/receive to the socket.

Typically, any secure connections use some type of cryptographic key to establish the encryption between two object. In Java SE version 6, the Java Cryptography Extension is no longer a com.sun package like it was in JDK 1.4, but is now in the javax.crypto package. The JDK 5.0+ release comes standard with a JCE provider named "SunJCE", which comes pre-installed and registered and which supplies the following cryptographic services:

Download the project SecureSocket and first take a look at the KeyGen class. This generates a key and writes it out as a file called "secret.key" The screen shot below is the file view of the Netbeans project, and you can see the "secret.key" output.

KeyGen File

To create a secure socket, you first need to design a simple wrapper class around a standard socket. This will provide encryption to the socket connection. In the project, you will see a SecretSocket class that functions as this wrapper.

This class will accept two constructor arguments, a Socket object and a Key object. The constructor keeps a reference to the socket and then initializes the key and the cipher algorithm that will be applied to the sockets. There are two other methods getInputStream() and getOutputStream(), which chain the standard socket streams with CipherInputStream/CipherOutputStream. The cipher (based on the key) will be applied to these streams.

CipherInputStream and CipherOutputStream are two stream types found in the javax.crypto package.

package securesocket;
import javax.crypto.*;
   import java.io.*;
   import java.net.Socket;
   import java.security.*;
 
public class SecretSocket {
   private Key key = null;
   private Cipher outCipher = null;
   private Cipher inCipher = null;
   private CipherInputStream cis = null;
   private CipherOutputStream cos = null;
 private Socket socket = null;
 private String algorithm = "DES";
 public SecretSocket(Socket socket, Key key)  {
   this.socket = socket;
   this.key = key;
   algorithm = key.getAlgorithm();
   initializeCipher();
 }
 private void initializeCipher() {
   try {
     outCipher = Cipher.getInstance(algorithm);
     outCipher.init(Cipher.ENCRYPT_MODE, key);
     inCipher = Cipher.getInstance(algorithm);
     inCipher.init(Cipher.DECRYPT_MODE, key);
   } catch (NoSuchAlgorithmException e) {
     e.printStackTrace();
   } catch (NoSuchPaddingException e) {
     e.printStackTrace();
   } catch (InvalidKeyException e) {
     e.printStackTrace();
   }
 }
 
 public InputStream getInputStream() throws IOException {
   InputStream is = socket.getInputStream();
   cis = new CipherInputStream(is, inCipher);
   return cis;
 }
 
 public OutputStream getOutputStream() throws IOException {
   OutputStream os = socket.getOutputStream();
   cos = new CipherOutputStream(os, outCipher);
   return cos;
 }
}
 

 

 

Summary

So far, we've only showed how to handle streams with File access. In the next section, we'll use some other streams to handle I/O through sockets.