Unless otherwise restricted, a Java application can read and write to the host filesystem with the same level of access as the user who runs the Java interpreter. Java applets and other kinds of untrusted applications can, of course, be restricted by the security policy and cut off from these services. We’ll discuss applet access at the end of this section. First, let’s take a look at the tools for basic file access.
Working with files in Java is still somewhat problematic. The host filesystem lies outside of Java’s virtual environment, in the real world, and can therefore still suffer from architecture and implementation differences. Java tries to mask some of these differences by providing information to help an application tailor itself to the local environment; we’ll mention these areas as they occur.
The java.io.File
class encapsulates access to information about a file or directory
entry in the filesystem. It can be used to get attribute information
about a file, list the entries in a directory, and perform basic
filesystem operations like removing a file or making a directory.
While the File
object handles these tasks, it
doesn’t provide direct access for reading and writing file
data; there are specialized streams for that purpose.
You can
create an instance of File
from a
String
pathname:
File fooFile = new File( "/tmp/foo.txt" ); File barDir = new File( "/tmp/bar" );
You can also create a file with a relative path:
File f = new File( "foo" );
In this case, Java works relative to the
current
directory of the Java interpreter. You can determine the current
directory by checking the
user.dir
property in
the System Properties
list:
System.getProperty("user.dir"));
An overloaded version of the File
constructor lets
you specify the directory path and filename as separate
String
objects:
File fooFile = new File( "/tmp", "foo.txt" );
With yet another variation, you can specify the directory with a
File
object and the filename with a
String
:
File tmpDir = new File( "/tmp" ); File fooFile = new File ( tmpDir, "foo.txt" );
None of the File
constructors throw any
exceptions. This means the object is created whether or not the file
or directory actually exists; it isn’t an error to create a
File
object for a nonexistent file. You can use
the object’s exists( )
instance method to
find out whether the file or directory exists. The
File
object simply exists as a handle for getting
information about what is (potentially at least) a file or directory.
One of the reasons that working with files in Java is problematic is that pathnames are expected to follow the conventions of the local filesystem. Java’s designers intend to provide an abstraction that deals with most system-dependent filename features, such as the file separator, path separator, device specifier, and root directory. Unfortunately, not all these features are implemented in the current version.
On some systems, Java can compensate for differences such as the direction of the file separator slashes in a pathname. For example, in the current implementation on Windows platforms, Java accepts paths with either forward slashes or backslashes. However, under Solaris, Java accepts only paths with forward slashes.
Your best bet is to make sure you follow
the filename conventions of the host filesystem. If your application
is just opening and saving files at the user’s request, you
should be able to handle that functionality with the Swing
JFileDialog
class. This class encapsulates a
graphical file-selection dialog box. The methods of the
JFileDialog
take care of system-dependent filename
features for you.
If your application needs to deal with
files on its own behalf, however, things get a little more
complicated. The File
class contains a few
static
variables to make this task possible.
File.separator
defines a String
that specifies the file separator on the local host (e.g.,
/
on Unix and Macintosh systems and
\
on Windows systems);
File.separatorChar
provides the same information
as a char
. File.pathSeparator
defines a String
that separates items in a path
(e.g., :
on Unix systems and ;
on Macintosh and Windows systems);
File.pathSeparatorChar
provides the information as
a char
.
You can use this system-dependent information in several ways.
Probably the simplest way to localize pathnames is to pick a
convention you use internally, for instance the forward slash, and do
a String
replace to substitute for the localized
separator character:
// we'll use forward slash as our standard String path = "mail/1999/june/merle"; path = path.replace('/', File.separatorChar); File mailbox = new File( path );
Alternately, you could work with the components of a pathname and build the local pathname when you need it:
String [] path = { "mail", "1999", "june", "merle" }; StringBuffer sb = new StringBuffer(path[0]); for (int i=1; i< path.length; i++) sb.append( File.separator + path[i] ); File mailbox = new File( sb.toString( ) );
One thing to remember is
that Java interprets the backslash character (\
)
as an escape character when used in a String
. To
get a backslash in a String
, you have to use
\\
.
Another issue to grapple with is that some operating systems use
special identifiers for the
"roots” of filesystems. For
example, Windows uses C:\
. Should you need it, the
File
class provides the static method
listRoots( )
, which returns an array of
File
objects corresponding to the filesystem root
directories.
Once we have a File
object, we can use it to ask for information about the file or
directory and to perform standard operations on it. A number of
methods let us ask certain questions about the
File
. For example, isFile( )
returns true
if the File
represents a file, while isDirectory( )
returns true
if
it’s a directory. isAbsolute( )
indicates if the File
has an absolute or relative path specification.
Components of the
File
pathname are available through the following
methods: getName( )
, getPath( )
, getAbsolutePath( )
, and
getParent( )
. getName( )
returns a String
for the filename without any
directory information; getPath( )
returns the
directory information without the filename. If the
File
has an absolute path specification,
getAbsolutePath( )
returns that path. Otherwise it
returns the relative path appended to the current
working directory. getParent( )
returns the
parent directory of the
File
.
Interestingly, the string returned by
getPath( )
or getAbsolutePath( )
may not follow the same case-conventions as the
underlying filesystem. You can retrieve the filesystem’s own or
“canonical” version of the file’s path using the
method getCanonicalPath( )
. In Windows, for
example, you can create a File
object whose
getAbsolutePath( )
is
C:\Autoexec.bat
, but whose
getCanonical-Path( )
is
C:\AUTOEXEC.BAT
. This is useful for comparing
filenames that may have been supplied with different case
conventions.
You can get or set the
modification time of a file or
directory with lastModified() and setLastModified( )
methods. The value is a long
that is
the number of milliseconds since the epoch ( Jan
1, 1970, 00:00:00 GMT). We can also get the size of the file in bytes
with length( )
.
Here’s a fragment of code that prints some information about a file:
File fooFile = new File( "/tmp/boofa" ); String type = fooFile.isFile( ) ? "File " : "Directory "; String name = fooFile.getName( ); long len = fooFile.length( ); System.out.println(type + name + ", " + len + " bytes " );
If the File
object
corresponds to a directory, we can list the files in the directory
with the list( )
method or the listFiles( )
method:
String [] fileNames = fooFile.list( ); File [] files = fooFile.listFiles( );
list( )
returns an array of
String
objects that contains filenames.
listFiles( )
returns an array of
File
objects. Note that in neither case are the
files guaranteed to be in any kind of order (alphabetical, for
example).
If
the File
refers to a nonexistent directory, we can
create the directory with mkdir( )
or
mkdirs( )
. mkdir( )
creates a
single directory; mkdirs( )
also creates all of
the intervening directories in a File
specification. Use renameTo( )
to rename a file or directory and
delete( )
to delete a file or directory.
Note that using the File
object itself isn’t
generally the way to create a file; that’s normally done
implicitly with a
FileOutputStream
or FileWriter
, as
we’ll discuss in a moment. The exception is the
createNewFile( )
method, which can be used to attempt to
create a new zero-length file at the location pointed to by the
File
object. The useful thing about this method is
that the operation is guaranteed to be “atomic” with
respect to all other file creation. createNewFile( )
returns a boolean value which tells you whether the file
was created.
You can use this to implement file locking from Java. This is useful
in combination with deleteOnExit( )
, which flags the file to be
automatically removed when the Java Virtual Machine exits. Another
file creation method related to the File
class
itself is the static method createTempFile( )
, which creates a file in a specified
location using an automatically generated unique name. This, too, is
useful in combination with deleteOnExit( )
.
The toURL( )
method converts a file path to a
file:
URL object. We’ll talk about URLs in
Chapter 12. They are an abstraction that allows you
to point to any kind of object anywhere on the Net. Converting a
File
reference to a URL may be useful for
consistency with more general routines that deal with URLs.
Table 10.1 summarizes the methods provided by
the File
class.
Table 10-1. File Methods
Method |
Return Type |
Description |
---|---|---|
|
| |
|
| |
createNewFile() |
|
Creates a new file |
createTempFile (String |
static File |
Creates a new file, with the specified prefix and suffix, in the default temp-file directory |
|
|
Deletes the file (or directory) |
deleteOnExit() |
void |
When it exits, Java runtime system will delete the file |
|
| |
|
|
Returns the absolute path of the file (or directory) |
|
|
Returns the absolute, case-correct path of the file (or directory) |
|
|
Returns the name of the file (or directory) |
|
|
Returns the name of the parent directory of the file (or directory) |
|
|
Returns the path of the file (or directory) |
|
|
Is the filename (or directory name) absolute? |
|
|
Is the item a directory? |
|
|
Is the item a file? |
|
|
Returns the last modification time of the file (or directory) |
|
|
Returns the length of the file |
|
|
Returns a list of files in the directory |
listfiles() |
File[] |
Returns the contents of the directory as an array of
|
|
|
Creates the directory |
|
|
Creates all directories in the path |
|
|
Renames the file (or directory) |
setLastModified() |
boolean |
Sets the last-modified time of the file (or directory) |
boolean |
Sets the file to read-only status | |
toURL() |
java.net.URL |
Java provides two
specialized streams for reading and
writing files in the filesystem: FileInputStream
and FileOutputStream
. These streams provide the
basic InputStream
and
OutputStream
functionality applied to reading and
writing the contents of files. They can be combined with the filter
streams described earlier to work with files in the same way we do
other stream communications.
Because FileInputStream
is a subclass of
InputStream
, it inherits all standard
InputStream
functionality for reading the contents
of a file. FileInputStream
provides only a
low-level interface to reading data, however, so you’ll
typically wrap it with another stream, such as a
DataInputStream
.
You can create a FileInputStream
from a
String
pathname or a File
object:
FileInputStream foois = new FileInputStream( fooFile ); FileInputStream passwdis = new FileInputStream( "/etc/passwd" );
When
you create a FileInputStream
, the Java runtime
system attempts to open the specified file. Thus, the
FileInputStream
constructors can throw a
FileNotFoundException
if the specified file
doesn’t exist, or an IOException
if some
other I/O error occurs. Be sure to catch and handle these exceptions
in your code. When the stream is first created, its
available( )
method and the File
object’s length( )
method should return the same value. Be
sure to call the close( )
method when you are done with the file.
To read characters from a file, you can
wrap an InputStreamReader
around a
FileInputStream
. If you want to use the default
character-encoding scheme, you can use the
FileReader
class instead, which is provided as a
convenience. FileReader
works just like
FileInputStream
, except that it reads characters
instead of bytes and wraps a Reader
instead of an
InputStream
.
The following class,
ListIt
, is a small utility that sends the
contents of a file or directory to standard output:
//file: ListIt.java import java.io.*; class ListIt { public static void main ( String args[] ) throws Exception { File file = new File( args[0] ); if ( !file.exists() || !file.canRead( ) ) { System.out.println( "Can't read " + file ); return; } if ( file.isDirectory( ) ) { String [] files = file.list( ); for (int i=0; i< files.length; i++) System.out.println( files[i] ); } else try { FileReader fr = new FileReader ( file ); BufferedReader in = new BufferedReader( fr ); String line; while ((line = in.readLine( )) != null) System.out.println(line); } catch ( FileNotFoundException e ) { System.out.println( "File Disappeared" ); } } }
ListIt
constructs a File
object
from its first command-line argument and tests the
File
to see whether it exists and is readable. If
the File
is a directory, ListIt
outputs the names of the files in the directory. Otherwise,
ListIt
reads and outputs the file.
FileOutputStream
is a
subclass of
OutputStream
, so it inherits all the standard
OutputStream
functionality for writing to a file.
Just like FileInputStream
though,
FileOutputStream
provides only a low-level
interface to writing data. You’ll typically wrap another
stream, like a
DataOutputStream
or a
PrintWriter
, around the
FileOutputStream
to provide higher-level
functionality.
You can create a FileOutputStream
from a
String
pathname or a File
object. Unlike FileInputStream
, however, the
FileOutputStream
constructors don’t throw a
FileNotFoundException
. If the specified file
doesn’t exist, the FileOutputStream
creates
the file. The FileOutputStream
constructors can
throw an IOException
if some other I/O error
occurs, so you still need to handle this exception.
If the specified file does exist, the
FileOutputStream
opens it for writing. When you
subsequently call the write( )
method, the new
data overwrites the current contents of the file. If you need to
append data to an existing file, you
should use a different constructor that accepts an append flag:
FileInputStream foois = new FileOutputStream( fooFile, true); FileInputStream psis = new FileOutputStream("/etc/passwd", true);
Another way to append data to files is with a
RandomAccessFile
, as
we’ll discuss shortly.
To write characters (instead of bytes)
to a file, you can wrap an
OutputStreamWriter
around a
FileOutputStream
. If you want to use the default
character-encoding scheme, you can use instead the
FileWriter
class, which is provided as a
convenience. FileWriter
works just like
FileOutputStream
,
except that it writes characters instead of bytes and wraps a
Writer
instead of an
OutputStream
.
The following example reads a line of data from standard input and
writes it to the file /tmp/foo.txt
:
String s = new BufferedReader( new InputStreamReader( System.in ) ).readLine( ); File out = new File( "/tmp/foo.txt" ); FileWriter fw = new FileWriter ( out ); PrintWriter pw = new PrintWriter( fw ) pw.println( s ); fw.close( );
Notice how we have wrapped a PrintWriter
around
the FileWriter
to facilitate writing the data.
Also, to be a good filesystem citizen, we’ve called the
close( )
method when we’re done with the
FileWriter
.
The
java.io.RandomAccessFile
class provides the
ability to read and write data from or to any specified location in a
file.
RandomAccessFile
implements both the
DataInput
and DataOutput
interfaces, so you can use it to read and write strings and primitive
types. In other words, RandomAccessFile
defines
the same methods for reading and writing data as
DataInputStream
and
DataOutputStream
. However, because the class
provides random, rather than sequential, access to file data,
it’s not a subclass of either InputStream
or
OutputStream
.
You can create a RandomAccessFile
from a
String
pathname or a File
object. The constructor also takes a second String
argument that specifies the mode of the file. Use
"r
" for a
read-only file or "rw
"
for a read-write file. Here’s how we would start to create a
simple database to keep track of user information:
try { RandomAccessFile users = new RandomAccessFile( "Users", "rw" ); ... } catch (IOException e) { ... }
When you create
a RandomAccessFile
in read-only mode, Java tries
to open the specified file. If the file doesn’t exist,
RandomAccessFile
throws an
IOException
. If, however, you are creating a
RandomAccessFile
in read-write mode, the object
creates the file if it doesn’t exist. The constructor can still
throw an IOException
if some other I/O error
occurs, so you still need to handle this exception.
After you have created a RandomAccessFile
, call
any of the normal reading and writing methods, just as you would with
a DataInputStream
or
DataOutputStream
. If you try to write to a
read-only file, the write method throws an
IOException
.
What makes a
RandomAccessFile
special is the seek( )
method. This method takes a long
value
and uses it to set the location for reading and writing in the file.
You can use the
getFilePointer( )
method to get the current
location. If you need to append data to the end of the file,
use
length( )
to
determine that location, then seek( )
to it. You
can write or seek beyond the end of a file, but you can’t read
beyond the end of a file. The read( )
method throws an
EOFException
if you try to do
this.
Here’s an example of writing some data to our user database:
users.seek( userNum * RECORDSIZE ); users.writeUTF( userName ); users.writeInt( userID );
One caveat to notice with this example is that we need to be sure
that the String
length for
userName
, along with any data that comes after it,
fits within the specified record size.
For security reasons,
untrusted applets and applications are
not permitted to read from and write to arbitrary places in the
filesystem. The ability of untrusted code to read and write files, as
with any kind of system resource, is under the control of the system
security policy, through a
SecurityManager
object. A
SecurityManager
is installed by the application
that is running the untrusted code, such as
appletviewer or a Java-enabled web browser. All
filesystem access must first pass the scrutiny of the
SecurityManager
.
For example, Sun’s HotJava web browser allows even untrusted applets to have access to specific files designated by the user in an access-control list. Netscape Navigator, on the other hand, currently doesn’t allow untrusted applets any access to the filesystem. In both cases, trusted applets can be given arbitrary access to the filesystem, just like a standalone Java application.
It isn’t unusual to want an applet to maintain some kind of state information on the system on which it’s running. But for a Java applet that is restricted from access to the local filesystem, the only option is to store data over the network on its server. Applets have at their disposal powerful general means for communicating data over networks. The only limitation is that, by convention, an applet’s network communication is restricted to the server that launched it. This limits the options for where the data will reside.
Currently, the only way for a Java program to send data to a server is through a network socket or tools like RMI, which run over sockets. In Chapter 11, we’ll take a detailed look at building networked applications with sockets. With the tools described in that chapter, it’s possible to build powerful client/server applications. Sun also has a Java extension called WebNFS, which allows applets and applications to work with files on an NFS server in much the same way as the ordinary File API.
We often have data files and other
objects that we want our programs to use. Java provides many ways to
access these resources. In a standalone application, we can simply
open files and read the bytes. In both standalone applications and applets,
we can construct URLs to well-known locations. The problem with these
methods is that we have to know where our application lives in order
to find our data. This is not always as easy as it seems. What is
needed is a universal way to access resources associated with our
application and our application’s individual classes.
The
Class
class’s
getResource( )
method provides just this.
What does getResource( )
do for us? To construct a
URL to a file, we normally have to
figure out a home directory for our code and construct a path
relative to that. In an
applet, we could use
getCodeBase( )
or
getDocumentBase( )
to find the
base URL, and
use that base to create the URL for the resource we want. But these
methods don’t help a standalone application—and
there’s no reason that a standalone application and an applet
shouldn’t be able to share classes anyway. To solve this
problem, the getResource( )
method provides a
standard way to get objects relative to a given class file or to the
system classpath. getResource( )
returns a special
URL that uses the class’s class loader. This means that no
matter where the class came from—a web server, the local
filesystem, or even a JAR file—we can simply ask for an object,
get a URL for the object, and use the URL to access the object.
getResource( )
takes as an argument a
slash-separated pathname for the
resource and returns a URL. There are two kinds of
paths: absolute and relative.
An absolute path begins with a slash. For example:
/foo/bar/blah.txt
. In this case, the search for
the object begins at the top of the class path. If there is a
directory foo/bar in the class path,
getResource( )
searches that directory for the
blah.txt file. A relative URL does not begin
with a slash. In this case, the search begins at the location of the
class file, whether it is local, on a remote server, or in a JAR file
(either local or remote). So if we were calling getResource( )
on a class loader that loaded a class in the
foo.bar
package, we could refer to the file as
blah.txt. In this case, the class itself would
be loaded from the directory foo/bar somewhere
on the class path, and we’d expect to find the file in the same
directory.
For example, here’s an application that looks up some resources:
//file: FindResources.java package mypackage; import java.net.URL; import java.io.IOException; public class FindResources { public static void main( String [] args ) throws IOException { // absolute from the classpath URL url = FindResources.class.getResource("/mypackage/foo.txt"); // relative to the class location url = FindResources.class.getResource("foo.txt"); // another relative document url = FindResources.class.getResource("docs/bar.txt"); } }
The FindResources
class belongs to the
mypackage
package, so its class file will live in
a mypackage directory somewhere on the class
path. FindResources
locates the document
foo.txt using an absolute and then a relative
URL. At the end, FindResources
uses a relative
path to reach a document in the mypackage/docs
directory. In each case we refer to the
FindResources
’s Class
object using the static .class
notation.
Alternatively, if we had an instance of the
object, we could use its getClass( )
method to reach the Class
object.
For an applet, the search is
similar but occurs on the host from which the applet was loaded.
getResource( )
first checks any JAR files loaded
with the applet, and then searches the normal remote applet class
path, constructed relative to the applet’s codebase URL.
getResource( )
returns a URL for whatever type of
object you reference. This could be a text file or properties file
that you want to read as a stream, or it might be an image or sound
file or some other object. If you want the data as a stream, the
Class
class also provides
a
getResourceAsStream( )
method. In the case of an
image, you’d probably hand
the URL over to the getImage( )
method of the
Applet
class or one of the
components of the
Swing package
for loading.
Get Learning Java now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.