Reading Files
java.io.FileInputStream is a concrete subclass of java.io.InputStream. It provides an input stream connected to a particular file. FileInputStream has all the usual methods of input streams, such as read( ), available( ), skip( ), and close( ), which are used exactly as they are for any other input stream. FileInputStream( ) has three constructors, which differ only in how the file to be read is specified:
public FileInputStream(String fileName) throws IOException public FileInputStream(File file) throws FileNotFoundException public FileInputStream(FileDescriptor fdObj)
The first constructor uses a string containing the name of the file. The second constructor uses a java.io.File object. The third constructor uses a java.io.FileDescriptor object.
To read a file, just pass the name of the file into the FileInputStream( ) constructor. Then use the read( ) method as normal. For example, the following code fragment reads the file README.TXT, then prints it on System.out:
try { FileInputStream fis = new FileInputStream("README.TXT"); for (int n = fis.read(); n != -1; n = fis.read( )) { System.out.write(n); } } catch (IOException ex) { System.err.println(ex); } System.out.println( );
Java looks for files in the current working directory . Generally, this is the directory you were in when you typed javaprogram_name to start running the program. You can open a file in a different directory by passing a full or relative path to the file from the current working directory. For example, to read the file /etc/hosts no matter which directory is current, you can do this:
FileInputStream fis = new FileInputStream("/etc/hosts");
Filenames are platform-dependent, so hardcoded filenames should be avoided wherever possible. This example depends on a Unix-style pathname. It is not guaranteed to work on other platforms such as Windows or Mac OS 9, though it might. Using a filename to create a FileInputStream violates Sun's rules for "100% Pure Java." Some runtime environments such as Apple's Macintosh Runtime for Java include extra code to translate from Unix-style filenames to the native style. However, for maximum cross-platform awareness, you should use File objects instead. These can be created directly from filenames as described in Chapter 17, supplied by the user through a GUI such as a Swing JFileChooser, or returned by various methods scattered throughout the API and class libraries. Much of the time, code that uses a File object adapts more easily to unexpected filesystem conventions. One particularly important trick is to create multisegment paths by successively appending new File objects for each directory like so:
File root = new File("/"); File dir = new File(root, "etc"); File child = new File(dir, "hosts"); FileInputStream fis = new FileInputStream(child);
However, this still assumes that the root of the filesystem is named "/", which isn't likely to be a true on a non-Unix system. It's better to use the File.listRoots( ) method:
File[] roots = File.listRoots( ) File dir = new File(roots[0], "etc"); File child = new File(dir, "hosts"); FileInputStream fis = new FileInputStream(child);
However, although this code is more platform independent, it still assumes a particular file layout structure. This can vary not just from platform to platform, but from one PC to the next, even those running the same operating system. For more robustness, you'll want to get at least a directory, if not a complete file, by invoking a method that adapts to the local system. Possibilities include:
- Ask the user to choose a file with a Swing JFileChooser.
- Ask the user to choose a file with an AWT FileDialog.
- Ask a third-party library such as MRJ Adapter's SpecialFolder for a known location such as the preferences folder or the desktop folder.
- Create a temporary file with the File.createTempFile( ) method.
- Find the user's home directory with System.getProperty("user.home").
- Find the current working directory with System.getProperty("user.dir").
This list is not exhaustive; there are other approaches. Which one is appropriate depends on the use case. Details of these approaches are addressed in future chapters.
If the file you're trying to read does not exist when the FileInputStream object is constructed, the constructor throws a FileNotFoundException (a subclass of java.io.IOException). If for some other reason a file cannot be readfor example, the current process does not have read permission for the filesome other kind of IOException is thrown.
Example 4-1reads a filename from the command line, then copies the named file to System.out. The StreamCopier.copy( ) method from Example 3-3 in the previous chapter does the actual reading and writing. Notice that that method does not care whether the input is coming from a file or going to the console. It works regardless of the type of the input and output streams it's copying. It will work equally well for other streams still to be introduced, including ones that did not even exist when StreamCopier was created.
Example 4-1. The FileDumper program
import java.io.*; import com.elharo.io.*; public class FileTyper { public static void main(String[] args) throws IOException { if (args.length != 1) { System.err.println("Usage: java FileTyper filename"); return; } typeFile(args[0]); } public static void typeFile(String filename) throws IOException { FileInputStream fin = new FileInputStream(filename); try { StreamCopier.copy(fin, System.out); } finally { fin.close( ); } } } |
Untrusted code is not usually allowed to read or write files. If an applet tries to create a FileInputStream, the constructor will throw a SecurityException.
The FileInputStream class has one method that's not declared in the InputStream superclass: getFD( ).
public final FileDescriptor getFD( ) throws IOException
This method returns the java.io.FileDescriptor object associated with this stream. FileDescriptor objects are discussed inChapter 17. For now, all you can do with this object is use it to create another file stream.
It is possible to open multiple input streams to the same file at the same time, though it's rarely necessary to do so. Each stream maintains a separate pointer that points to the current position in the file. Reading from the file does not change the file in any way. Writing to the file is a different story, as you'll see in the next section.