// $Id: RandomAccessFile.java,v 1.7 2005-01-13 20:36:10 donm Exp $
/*
* Copyright 1997-2000 Unidata Program Center/University Corporation for
* Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
* support@unidata.ucar.edu.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* RandomAccessFile.java. By Russ Rew, based on
* BufferedRandomAccessFile by Alex McManus, based on Sun's source code
* for java.io.RandomAccessFile. For Alex McManus version from which
* this derives, see his <a href="http://www.aber.ac.uk/~agm/Java.html">
* Freeware Java Classes</a>.
*/
package ucar.netcdf;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.EOFException;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UTFDataFormatException;
import java.util.Random; // used in test method
import java.util.Date;
/**
* A buffered drop-in replacement for java.io.RandomAccessFile.
* Instances of this class realise substantial speed increases over
* java.io.RandomAccessFile through the use of buffering. This is a
* subclass of Object, as it was not possible to subclass
* java.io.RandomAccessFile because many of the methods are
* final. However, if it is necessary to use RandomAccessFile and
* java.io.RandomAccessFile interchangeably, both classes implement the
* DataInput and DataOutput interfaces.
*
* @author Alex McManus
* @author Russ Rew
* @version $Id: RandomAccessFile.java,v 1.7 2005-01-13 20:36:10 donm Exp $
* @see DataInput
* @see DataOutput
* @see java.io.RandomAccessFile */
public class RandomAccessFile extends Object
implements DataInput, DataOutput {
/** Read from the file. This is always implied. */
public static final int READ = 1;
/** Write to the file. */
public static final int WRITE = 2;
/** Create the file rather than overwriting it. This is ignored if mode is
* not also WRITE. */
public static final int CREATE = 4;
/** The default buffer size, in bytes. */
protected static final int defaultBufferSize = 4096;
/** The underlying java.io.RandomAccessFile. */
protected java.io.RandomAccessFile file;
/** The offset in bytes from the file start, of the next read or
* write operation. */
protected long filePosition;
/** The buffer used to load the data. */
protected byte buffer[];
/** The offset in bytes of the start of the buffer, from the start
* of the file. */
protected long bufferStart;
/** The offset in bytes of the end of the data in the buffer, from
* the start of the file. This can be calculated from
* <code>bufferStart + dataSize</code>, but it is cached to speed
* up the read( ) method. */
protected long dataEnd;
/** The size of the data stored in the buffer, in bytes. This may be
* less than the size of the buffer.*/
protected int dataSize;
/** True if we are at the end of the file. */
protected boolean endOfFile;
/** The access mode of the file. This is a logical OR of READ,
* WRITE and CREATE. */
protected int mode;
/** True if the data in the buffer has been modified. */
boolean bufferModified = false;
// subclasses only
protected RandomAccessFile(int bufferSize) {
// Initialise the buffer
bufferStart = 0;
dataEnd = 0;
dataSize = 0;
filePosition = 0;
buffer = new byte[bufferSize];
endOfFile = false;
}
/**
* Create a new buffered random-access file with a default buffer size.
* Note that the mode CREATE implies WRITE.
*
* @param filename the name of the file.
* @param mode how the file is to be opened. This may be a
* combination (logical OR) of CREATE, WRITE, and READ.
* @exception IOException if an I/O error occurrs.
* @exception SecurityException if a security manager exists, its checkRead
* method is called with the name argument to
* see if the application is allowed read
* access to the file. If the mode argument is
* WRITE, its checkWrite method also is called
* with the name argument to see if the
* application is allowed write access to the
* file. Either of these may result in a
* security exception.
*/
public RandomAccessFile( String filename, int mode )
throws IOException {
this( filename, mode, defaultBufferSize );
}
/**
* Creates a random access file stream to read from, and optionally
* to write to, a file with the specified name.
* <p>
* The mode argument must either be equal to <code>"r"</code> or
* <code>"rw"</code>, indicating that the file is to be opened for
* input only or for both input and output, respectively. If the
* mode is <code>"rw"</code> and the
* file does not exist, then an attempt is made to create it.
*
* @param filename the system-dependent filename.
* @param modeString the access mode.
* @exception IllegalArgumentException if the mode argument is not equal
* to <code>"r"</code> or to <code>"rw"</code>.
* @exception IOException if an I/O error occurs.
* @exception SecurityException if a security manager exists, its
* <code>checkRead</code> method is called with the name
* argument to see if the application is allowed read access
* to the file. If the mode argument is equal to
* <code>"rw"</code>, its <code>checkWrite</code> method also
* is called with the name argument to see if the application
* is allowed write access to the file. Either of these may
* result in a security exception.
* @see java.lang.SecurityException
* @see java.lang.SecurityManager#checkRead(java.lang.String)
*/
public RandomAccessFile( String filename, String modeString )
throws IOException {
this (filename, modeString, defaultBufferSize);
}
/**
* Creates a random access file stream to read from, and optionally
* to write to, a file with the specified name.
* <p>
* The mode argument must either be equal to <code>"r"</code> or
* <code>"rw"</code>, indicating that the file is to be opened for
* input only or for both input and output, respectively. If the
* mode is <code>"rw"</code> and the
* file does not exist, then an attempt is made to create it.
*
* @param filename the system-dependent filename.
* @param modeString the access mode.
* @exception IllegalArgumentException if the mode argument is not equal
* to <code>"r"</code> or to <code>"rw"</code>.
* @exception IOException if an I/O error occurs.
* @exception SecurityException if a security manager exists, its
* <code>checkRead</code> method is called with the name
* argument to see if the application is allowed read access
* to the file. If the mode argument is equal to
* <code>"rw"</code>, its <code>checkWrite</code> method also
* is called with the name argument to see if the application
* is allowed write access to the file. Either of these may
* result in a security exception.
* @see java.lang.SecurityException
* @see java.lang.SecurityManager#checkRead(java.lang.String)
*/
public RandomAccessFile( String filename, String modeString, int bufferSize )
throws IOException {
this( filename,
modeString.equals("r")? READ :
(modeString.equals("rw")? WRITE | READ : 0),
bufferSize );
}
/**
* Creates a random access file stream to read from, and optionally
* to write to, the file specified by the <code>File</code> argument.
* A new {@link FileDescriptor} object is created to represent
* this file connection.
* <p>
* The mode argument must either be equal to <code>"r"</code> or
* <code>"rw"</code>, indicating that the file is to be opened for
* input only or for both input and output, respectively. The
* write methods on this object will always throw an
* <code>IOException</code> if the file is opened with a mode of
* <code>"r"</code>. If the mode is <code>"rw"</code> and the
* file does not exist, then an attempt is made to create it.
*
* @param file the file object.
* @param modeString the access mode.
* @exception IllegalArgumentException if the mode argument is not equal
* to <code>"r"</code> or to <code>"rw"</code>.
* @exception IOException if an I/O error occurs.
* @exception SecurityException if a security manager exists, its
* <code>checkRead</code> method is called with the pathname
* of the <code>File</code> argument to see if the
* application is allowed read access to the file. If the
* mode argument is equal to <code>"rw"</code>, its
* <code>checkWrite</code> method also is called with the
* pathname to see if the application is allowed write access
* to the file.
* @see java.io.File#getPath()
* @see java.lang.SecurityManager#checkRead(java.lang.String)
*/
public RandomAccessFile(File file, String modeString) throws IOException {
this(file.getPath(), modeString);
}
/**
* Create a new buffered random-access file with a specified buffer
* size. Note that the mode CREATE implies WRITE, and the READ is
* always implied.
*
* @param filename the name of the file.
* @param mode how the file is to be opened. This may be a
combination (logical OR) of CREATE, WRITE, and READ.
* @param bufferSize the size of the temporary buffer, in bytes.
* @exception FileNotFoundException
* if the access is readonly and the file
* doesn't exist.
* @exception IOException if an I/O error occurrs.
* @exception SecurityException if a security manager exists, its checkRead
* method is called with the name argument to
* see if the application is allowed read
* access to the file. If the mode argument is
* WRITE, its checkWrite method also is called
* with the name argument to see if the
* application is allowed write access to the
* file. Either of these may result in a
* security exception.
*/
public RandomAccessFile( String filename, int mode, int bufferSize )
throws FileNotFoundException, IOException {
this.mode = mode;
// If we are CREATEing a file, we must also WRITE. READ is always
// set.
mode |= READ;
if( (this.mode & CREATE) > 0 )
this.mode |= WRITE;
// To match java.io.RandomAccessFile semantics, if we want to write
// a nonexistant file, create it first (even if CREATE not set)
File checkfile = new File( filename );
if((this.mode & WRITE) > 0 && !checkfile.exists( ) ) {
mode |= CREATE;
}
// If a new file is to be created, delete any existing file with the same name.
if( (this.mode & CREATE) > 0 ) {
if( checkfile.exists( ) ) {
if( !checkfile.delete( ) )
throw new IOException( "Failed to delete " + filename );
}
}
// If only reading, check that the file exists.
if( this.mode == READ && !(new File( filename )).exists( ) )
throw new FileNotFoundException( filename );
// Create the underlying file object.
String modeString = ((this.mode & WRITE) > 0) ? "rw" : "r";
file = new java.io.RandomAccessFile( filename, modeString );
// Initialise the buffer;
bufferStart = 0;
dataEnd = 0;
dataSize = 0;
filePosition = 0;
buffer = new byte[bufferSize];
endOfFile = false;
}
/**
* Close the file, and release any associated system resources.
*
* @exception IOException if an I/O error occurrs.
*/
public void close()
throws IOException {
// If we are writing and the buffer has been modified, flush the contents
// of the buffer.
if( (mode | WRITE) > 0 && bufferModified ) {
file.seek( bufferStart );
file.write( buffer, 0, (int)dataSize );
}
// Close the underlying file object.
file.close( );
}
/**
* Set the position in the file for the next read or write.
*
* @param pos the offset (in bytes) from the start of the file.
* @exception IOException if an I/O error occurrs.
*/
public void seek( long pos ) throws IOException {
// If the seek is into the buffer, just update the file pointer.
if( pos >= bufferStart && pos < dataEnd ) {
filePosition = pos;
return;
}
// If the current buffer is modified, write it to disk.
if( bufferModified )
flush( );
// need new buffer
bufferStart = pos;
filePosition = pos;
dataSize = read_( pos, buffer, 0, buffer.length);
if( dataSize < 0 ) {
dataSize = 0;
endOfFile = true;
} else {
endOfFile = false;
}
// Cache the position of the buffer end.
dataEnd = bufferStart + dataSize;
}
/**
* Returns the current position in the file, where the next read or
* write will occur.
*
* @return the offset from the start of the file in bytes.
* @exception IOException if an I/O error occurrs.
*/
public long getFilePointer() throws IOException {
return filePosition;
}
/**
* Get the length of the file. The data in the buffer (which may not
* have been written the disk yet) is taken into account.
*
* @return the length of the file in bytes.
* @exception IOException if an I/O error occurrs.
*/
public long length( ) throws IOException {
long fileLength = file.length( );
if( fileLength < dataEnd )
return dataEnd;
else
return fileLength;
}
/**
* Returns the opaque file descriptor object associated with this file.
*
* @return the file descriptor object associated with this file.
* @exception IOException if an I/O error occurs.
*/
public FileDescriptor getFD()
throws IOException {
return file.getFD();
}
/**
* Copy the contents of the buffer to the disk.
*
* @exception IOException if an I/O error occurrs.
*/
public void flush( ) throws IOException {
if (bufferModified) {
file.seek( bufferStart );
file.write( buffer, 0, dataSize );
bufferModified = false;
}
}
//
// Read primitives.
//
/**
* Read a byte of data from the file, blocking until data is
* available.
*
* @return the next byte of data, or -1 if the end of the file is
* reached.
* @exception IOException if an I/O error occurrs.
*/
public final int read()
throws IOException {
// If the file position is within the data, return the byte...
if( filePosition < dataEnd ) {
return (int)(buffer[(int)(filePosition++ - bufferStart)] & 0xff);
// ...or should we indicate EOF...
} else if( endOfFile ) {
return -1;
// ...or seek to fill the buffer, and try again.
} else {
seek( filePosition );
return read( );
}
}
/**
* Read up to <code>len</code> bytes into an array, at a specified
* offset. This will block until at least one byte has been read.
*
* @param b the byte array to receive the bytes.
* @param off the offset in the array where copying will start.
* @param len the number of bytes to copy.
* @return the actual number of bytes read, or -1 if there is not
* more data due to the end of the file being reached.
* @exception IOException if an I/O error occurrs.
*/
private int readBytes( byte b[], int off, int len ) throws IOException {
// Check for end of file.
if( endOfFile )
return -1;
// See how many bytes are available in the buffer - if none,
// seek to the file position to update the buffer and try again.
int bytesAvailable = (int)(dataEnd - filePosition);
if( bytesAvailable < 1 ) {
seek( filePosition );
return readBytes( b, off, len );
}
// Copy as much as we can.
int copyLength = (bytesAvailable >= len) ? len : bytesAvailable;
System.arraycopy( buffer, (int)(filePosition - bufferStart),
b, off, copyLength );
filePosition += copyLength;
// If there is more to copy...
if( copyLength < len ) {
int extraCopy = len - copyLength;
// If the amount remaining is more than a buffer's length, read it
// directly from the file.
if( extraCopy > buffer.length ) {
extraCopy = read_( filePosition, b, off + copyLength, len - copyLength );
// ...or read a new buffer full, and copy as much as possible...
} else {
seek( filePosition );
if( ! endOfFile ) {
extraCopy = (extraCopy > dataSize) ? dataSize : extraCopy;
System.arraycopy( buffer, 0, b, off + copyLength, extraCopy );
} else {
extraCopy = -1;
}
}
// If we did manage to copy any more, update the file position and
// return the amount copied.
if( extraCopy > 0 ) {
filePosition += extraCopy;
return copyLength + extraCopy;
}
}
// Return the amount copied.
return copyLength;
}
// read directly, without going through the buffer
protected int read_( long pos, byte[] b, int offset, int len) throws IOException {
file.seek( pos );
int n = file.read( b, offset, len );
//System.out.println(" want = "+len+" at "+pos+"; got = "+n);
return n;
}
/**
* Read up to <code>len</code> bytes into an array, at a specified
* offset. This will block until at least one byte has been read.
*
* @param b the byte array to receive the bytes.
* @param off the offset in the array where copying will start.
* @param len the number of bytes to copy.
* @return the actual number of bytes read, or -1 if there is not
* more data due to the end of the file being reached.
* @exception IOException if an I/O error occurrs.
*/
public int read( byte b[], int off, int len ) throws IOException {
return readBytes( b, off, len );
}
/**
* Read up to <code>b.length( )</code> bytes into an array. This
* will block until at least one byte has been read.
*
* @param b the byte array to receive the bytes.
* @return the actual number of bytes read, or -1 if there is not
* more data due to the end of the file being reached.
* @exception IOException if an I/O error occurrs.
*/
public int read( byte b[] ) throws IOException {
return readBytes( b, 0, b.length );
}
/**
* Reads <code>b.length</code> bytes from this file into the byte
* array. This method reads repeatedly from the file until all the
* bytes are read. This method blocks until all the bytes are read,
* the end of the stream is detected, or an exception is thrown.
*
* @param b the buffer into which the data is read.
* @exception EOFException if this file reaches the end before reading
* all the bytes.
* @exception IOException if an I/O error occurs.
*/
public final void readFully( byte b[] )
throws IOException {
readFully(b, 0, b.length);
}
/**
* Reads exactly <code>len</code> bytes from this file into the byte
* array. This method reads repeatedly from the file until all the
* bytes are read. This method blocks until all the bytes are read,
* the end of the stream is detected, or an exception is thrown.
*
* @param b the buffer into which the data is read.
* @param off the start offset of the data.
* @param len the number of bytes to read.
* @exception EOFException if this file reaches the end before reading
* all the bytes.
* @exception IOException if an I/O error occurs.
*/
public final void readFully( byte b[], int off, int len )
throws IOException {
int n = 0;
while (n < len) {
int count = this.read(b, off + n, len - n);
if (count < 0)
throw new EOFException();
n += count;
}
}
/**
* Skips exactly <code>n</code> bytes of input.
* This method blocks until all the bytes are skipped, the end of
* the stream is detected, or an exception is thrown.
*
* @param n the number of bytes to be skipped.
* @return the number of bytes skipped, which is always <code>n</code>.
* @exception EOFException if this file reaches the end before skipping
* all the bytes.
* @exception IOException if an I/O error occurs.
*/
public int skipBytes( int n )
throws IOException {
seek(getFilePointer() + n);
return n;
}
/**
* Unread the last byte read.
* This method should not be used more than once
* between reading operations, or strange things might happen.
*/
public final void unread( ) {
filePosition--;
}
//
// Write primitives.
//
/**
* Write a byte to the file. If the file has not been opened for
* writing, an IOException will be raised only when an attempt is
* made to write the buffer to the file.
* <p>
* Caveat: the effects of seek( )ing beyond the end of the file are
* undefined.
*
* @exception IOException if an I/O error occurrs.
*/
public final void write( int b )
throws IOException {
// If the file position is within the block of data...
if( filePosition < dataEnd ) {
buffer[(int)(filePosition++ - bufferStart)] = (byte)b;
bufferModified = true;
// ...or (assuming that seek will not allow the file pointer
// to move beyond the end of the file) get the correct block of
// data...
} else {
// If there is room in the buffer, expand it...
if( dataSize != buffer.length ) {
buffer[(int)(filePosition++ - bufferStart)] = (byte)b;
bufferModified = true;
dataSize++;
dataEnd++;
// ...or do another seek to get a new buffer, and start again...
} else {
seek( filePosition );
write( b );
}
}
}
/**
* Write <code>len</code> bytes from an array to the file.
*
* @param b the array containing the data.
* @param off the offset in the array to the data.
* @param len the length of the data.
* @exception IOException if an I/O error occurrs.
*/
public final void writeBytes( byte b[], int off, int len )
throws IOException {
// If the amount of data is small (less than a full buffer)...
if( len < buffer.length ) {
// If any of the data fits within the buffer...
int spaceInBuffer = 0;
int copyLength = 0;
if( filePosition >= bufferStart )
spaceInBuffer = (int)((bufferStart + buffer.length) - filePosition);
if( spaceInBuffer > 0 ) {
// Copy as much as possible to the buffer.
copyLength = (spaceInBuffer > len) ? len : spaceInBuffer;
System.arraycopy( b, off, buffer,
(int)(filePosition - bufferStart), copyLength );
bufferModified = true;
long myDataEnd = filePosition + copyLength;
dataEnd = myDataEnd > dataEnd ? myDataEnd : dataEnd;
dataSize = (int)(dataEnd - bufferStart);
filePosition += copyLength;
}
// If there is any data remaining, move to the new position and copy to
// the new buffer.
if( copyLength < len ) {
seek( filePosition );
System.arraycopy( b, off + copyLength, buffer,
(int)(filePosition - bufferStart),
len - copyLength );
bufferModified = true;
long myDataEnd = filePosition + (len - copyLength);
dataEnd = myDataEnd > dataEnd ? myDataEnd : dataEnd;
dataSize = (int)(dataEnd - bufferStart);
filePosition += (len - copyLength);
}
// ...or write a lot of data...
} else {
// Flush the current buffer, and write this data to the file.
if( bufferModified ) {
flush( );
bufferStart = dataEnd = dataSize = 0;
file.seek(filePosition); // JC added Oct 21, 2004
}
file.write( b, off, len );
filePosition += len;
}
}
/**
* Writes <code>b.length</code> bytes from the specified byte array
* starting at offset <code>off</code> to this file.
*
* @param b the data.
* @exception IOException if an I/O error occurs.
*/
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length);
}
/**
* Writes <code>len</code> bytes from the specified byte array
* starting at offset <code>off</code> to this file.
*
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
* @exception IOException if an I/O error occurs.
*/
public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len);
}
//
// DataInput methods.
//
/**
* Reads a <code>boolean</code> from this file. This method reads a
* single byte from the file. A value of <code>0</code> represents
* <code>false</code>. Any other value represents <code>true</code>.
* This method blocks until the byte is read, the end of the stream
* is detected, or an exception is thrown.
*
* @return the <code>boolean</code> value read.
* @exception EOFException if this file has reached the end.
* @exception IOException if an I/O error occurs.
*/
public final boolean readBoolean() throws IOException {
int ch = this.read();
if (ch < 0)
throw new EOFException();
return (ch != 0);
}
/**
* Reads a signed 8-bit value from this file. This method reads a
* byte from the file. If the byte read is <code>b</code>, where
* <code>0 <= b <= 255</code>,
* then the result is:
* <ul><code>
* (byte)(b)
*</code></ul>
* <p>
* This method blocks until the byte is read, the end of the stream
* is detected, or an exception is thrown.
*
* @return the next byte of this file as a signed 8-bit
* <code>byte</code>.
* @exception EOFException if this file has reached the end.
* @exception IOException if an I/O error occurs.
*/
public final byte readByte() throws IOException {
int ch = this.read();
if (ch < 0)
throw new EOFException();
return (byte)(ch);
}
/**
* Reads an unsigned 8-bit number from this file. This method reads
* a byte from this file and returns that byte.
* <p>
* This method blocks until the byte is read, the end of the stream
* is detected, or an exception is thrown.
*
* @return the next byte of this file, interpreted as an unsigned
* 8-bit number.
* @exception EOFException if this file has reached the end.
* @exception IOException if an I/O error occurs.
*/
public final int readUnsignedByte() throws IOException {
int ch = this.read();
if (ch < 0)
throw new EOFException();
return ch;
}
/**
* Reads a signed 16-bit number from this file. The method reads 2
* bytes from this file. If the two bytes read, in order, are
* <code>b1</code> and <code>b2</code>, where each of the two values is
* between <code>0</code> and <code>255</code>, inclusive, then the
* result is equal to:
* <ul><code>
* (short)((b1 << 8) | b2)
* </code></ul>
* <p>
* This method blocks until the two bytes are read, the end of the
* stream is detected, or an exception is thrown.
*
* @return the next two bytes of this file, interpreted as a signed
* 16-bit number.
* @exception EOFException if this file reaches the end before reading
* two bytes.
* @exception IOException if an I/O error occurs.
*/
public final short readShort() throws IOException {
int ch1 = this.read();
int ch2 = this.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (short)((ch1 << 8) + (ch2 << 0));
}
/**
* Reads an unsigned 16-bit number from this file. This method reads
* two bytes from the file. If the bytes read, in order, are
* <code>b1</code> and <code>b2</code>, where
* <code>0 <= b1, b2 <= 255</code>,
* then the result is equal to:
* <ul><code>
* (b1 << 8) | b2
* </code></ul>
* <p>
* This method blocks until the two bytes are read, the end of the
* stream is detected, or an exception is thrown.
*
* @return the next two bytes of this file, interpreted as an unsigned
* 16-bit integer.
* @exception EOFException if this file reaches the end before reading
* two bytes.
* @exception IOException if an I/O error occurs.
*/
public final int readUnsignedShort() throws IOException {
int ch1 = this.read();
int ch2 = this.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (ch1 << 8) + (ch2 << 0);
}
/**
* Reads a Unicode character from this file. This method reads two
* bytes from the file. If the bytes read, in order, are
* <code>b1</code> and <code>b2</code>, where
* <code>0 <= b1, b2 <= 255</code>,
* then the result is equal to:
* <ul><code>
* (char)((b1 << 8) | b2)
* </code></ul>
* <p>
* This method blocks until the two bytes are read, the end of the
* stream is detected, or an exception is thrown.
*
* @return the next two bytes of this file as a Unicode character.
* @exception EOFException if this file reaches the end before reading
* two bytes.
* @exception IOException if an I/O error occurs.
*/
public final char readChar() throws IOException {
int ch1 = this.read();
int ch2 = this.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (char)((ch1 << 8) + (ch2 << 0));
}
/**
* Reads a signed 32-bit integer from this file. This method reads 4
* bytes from the file. If the bytes read, in order, are <code>b1</code>,
* <code>b2</code>, <code>b3</code>, and <code>b4</code>, where
* <code>0 <= b1, b2, b3, b4 <= 255</code>,
* then the result is equal to:
* <ul><code>
* (b1 << 24) | (b2 << 16) + (b3 << 8) + b4
* </code></ul>
* <p>
* This method blocks until the four bytes are read, the end of the
* stream is detected, or an exception is thrown.
*
* @return the next four bytes of this file, interpreted as an
* <code>int</code>.
* @exception EOFException if this file reaches the end before reading
* four bytes.
* @exception IOException if an I/O error occurs.
*/
public final int readInt() throws IOException {
int ch1 = this.read();
int ch2 = this.read();
int ch3 = this.read();
int ch4 = this.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}
/**
* Reads a signed 64-bit integer from this file. This method reads eight
* bytes from the file. If the bytes read, in order, are
* <code>b1</code>, <code>b2</code>, <code>b3</code>,
* <code>b4</code>, <code>b5</code>, <code>b6</code>,
* <code>b7</code>, and <code>b8,</code> where:
* <ul><code>
* 0 <= b1, b2, b3, b4, b5, b6, b7, b8 <=255,
* </code></ul>
* <p>
* then the result is equal to:
* <p><blockquote><pre>
* ((long)b1 << 56) + ((long)b2 << 48)
* + ((long)b3 << 40) + ((long)b4 << 32)
* + ((long)b5 << 24) + ((long)b6 << 16)
* + ((long)b7 << 8) + b8
* </pre></blockquote>
* <p>
* This method blocks until the eight bytes are read, the end of the
* stream is detected, or an exception is thrown.
*
* @return the next eight bytes of this file, interpreted as a
* <code>long</code>.
* @exception EOFException if this file reaches the end before reading
* eight bytes.
* @exception IOException if an I/O error occurs.
*/
public final long readLong() throws IOException {
return ((long)(readInt()) << 32) + (readInt() & 0xFFFFFFFFL);
}
/**
* Reads a <code>float</code> from this file. This method reads an
* <code>int</code> value as if by the <code>readInt</code> method
* and then converts that <code>int</code> to a <code>float</code>
* using the <code>intBitsToFloat</code> method in class
* <code>Float</code>.
* <p>
* This method blocks until the four bytes are read, the end of the
* stream is detected, or an exception is thrown.
*
* @return the next four bytes of this file, interpreted as a
* <code>float</code>.
* @exception EOFException if this file reaches the end before reading
* four bytes.
* @exception IOException if an I/O error occurs.
* @see java.io.RandomAccessFile#readInt()
* @see java.lang.Float#intBitsToFloat(int)
*/
public final float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
/**
* Reads a <code>double</code> from this file. This method reads a
* <code>long</code> value as if by the <code>readLong</code> method
* and then converts that <code>long</code> to a <code>double</code>
* using the <code>longBitsToDouble</code> method in
* class <code>Double</code>.
* <p>
* This method blocks until the eight bytes are read, the end of the
* stream is detected, or an exception is thrown.
*
* @return the next eight bytes of this file, interpreted as a
* <code>double</code>.
* @exception EOFException if this file reaches the end before reading
* eight bytes.
* @exception IOException if an I/O error occurs.
* @see java.io.RandomAccessFile#readLong()
* @see java.lang.Double#longBitsToDouble(long)
*/
public final double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
/**
* Reads the next line of text from this file. This method
* successively reads bytes from the file until it reaches the end of
* a line of text.
* <p>
* A line of text is terminated by a carriage-return character
* (<code>'\r'</code>), a newline character (<code>'\n'</code>), a
* carriage-return character immediately followed by a newline
* character, or the end of the input stream. The line-terminating
* character(s), if any, are included as part of the string returned.
* <p>
* This method blocks until a newline character is read, a carriage
* return and the byte following it are read (to see if it is a
* newline), the end of the stream is detected, or an exception is thrown.
*
* @return the next line of text from this file.
* @exception IOException if an I/O error occurs.
*/
public final String readLine() throws IOException {
StringBuffer input = new StringBuffer();
int c;
while (((c = read()) != -1) && (c != '\n')) {
input.append((char)c);
}
if ((c == -1) && (input.length() == 0)) {
return null;
}
return input.toString();
}
/**
* Reads in a string from this file. The string has been encoded
* using a modified UTF-8 format.
* <p>
* The first two bytes are read as if by
* <code>readUnsignedShort</code>. This value gives the number of
* following bytes that are in the encoded string, not
* the length of the resulting string. The following bytes are then
* interpreted as bytes encoding characters in the UTF-8 format
* and are converted into characters.
* <p>
* This method blocks until all the bytes are read, the end of the
* stream is detected, or an exception is thrown.
*
* @return a Unicode string.
* @exception EOFException if this file reaches the end before
* reading all the bytes.
* @exception IOException if an I/O error occurs.
* @exception UTFDataFormatException if the bytes do not represent
* valid UTF-8 encoding of a Unicode string.
* @see java.io.RandomAccessFile#readUnsignedShort()
*/
public final String readUTF() throws IOException {
return DataInputStream.readUTF(this);
}
//
// DataOutput methods.
//
/**
* Writes a <code>boolean</code> to the file as a 1-byte value. The
* value <code>true</code> is written out as the value
* <code>(byte)1</code>; the value <code>false</code> is written out
* as the value <code>(byte)0</code>.
*
* @param v a <code>boolean</code> value to be written.
* @exception IOException if an I/O error occurs.
*/
public final void writeBoolean(boolean v) throws IOException {
write(v ? 1 : 0);
}
/**
* Writes a <code>byte</code> to the file as a 1-byte value.
*
* @param v a <code>byte</code> value to be written.
* @exception IOException if an I/O error occurs.
*/
public final void writeByte(int v) throws IOException {
write(v);
}
/**
* Writes a <code>short</code> to the file as two bytes, high byte first.
*
* @param v a <code>short</code> to be written.
* @exception IOException if an I/O error occurs.
*/
public final void writeShort(int v) throws IOException {
write((v >>> 8) & 0xFF);
write((v >>> 0) & 0xFF);
}
/**
* Writes a <code>char</code> to the file as a 2-byte value, high
* byte first.
*
* @param v a <code>char</code> value to be written.
* @exception IOException if an I/O error occurs.
*/
public final void writeChar(int v) throws IOException {
write((v >>> 8) & 0xFF);
write((v >>> 0) & 0xFF);
}
/**
* Writes an <code>int</code> to the file as four bytes, high byte first.
*
* @param v an <code>int</code> to be written.
* @exception IOException if an I/O error occurs.
*/
public final void writeInt(int v) throws IOException {
write((v >>> 24) & 0xFF);
write((v >>> 16) & 0xFF);
write((v >>> 8) & 0xFF);
write((v >>> 0) & 0xFF);
}
/**
* Writes a <code>long</code> to the file as eight bytes, high byte first.
*
* @param v a <code>long</code> to be written.
* @exception IOException if an I/O error occurs.
*/
public final void writeLong(long v) throws IOException {
write((int)(v >>> 56) & 0xFF);
write((int)(v >>> 48) & 0xFF);
write((int)(v >>> 40) & 0xFF);
write((int)(v >>> 32) & 0xFF);
write((int)(v >>> 24) & 0xFF);
write((int)(v >>> 16) & 0xFF);
write((int)(v >>> 8) & 0xFF);
write((int)(v >>> 0) & 0xFF);
}
/**
* Converts the float argument to an <code>int</code> using the
* <code>floatToIntBits</code> method in class <code>Float</code>,
* and then writes that <code>int</code> value to the file as a
* 4-byte quantity, high byte first.
*
* @param v a <code>float</code> value to be written.
* @exception IOException if an I/O error occurs.
* @see java.lang.Float#floatToIntBits(float)
*/
public final void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
/**
* Converts the double argument to a <code>long</code> using the
* <code>doubleToLongBits</code> method in class <code>Double</code>,
* and then writes that <code>long</code> value to the file as an
* 8-byte quantity, high byte first.
*
* @param v a <code>double</code> value to be written.
* @exception IOException if an I/O error occurs.
* @see java.lang.Double#doubleToLongBits(double)
*/
public final void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
/**
* Writes the string to the file as a sequence of bytes. Each
* character in the string is written out, in sequence, by discarding
* its high eight bits.
*
* @param s a string of bytes to be written.
* @exception IOException if an I/O error occurs.
*/
public final void writeBytes(String s) throws IOException {
int len = s.length();
for (int i = 0 ; i < len ; i++) {
write((byte)s.charAt(i));
}
}
/**
* Writes the character array to the file as a sequence of bytes. Each
* character in the string is written out, in sequence, by discarding
* its high eight bits.
*
* @param b a character array of bytes to be written.
* @param off the index of the first character to write.
* @param len the number of characters to write.
* @exception IOException if an I/O error occurs.
*/
public final void writeBytes( char b[], int off, int len ) throws IOException {
for( int i = off; i < len; i++ ) {
write( (byte)b[i] );
}
}
/**
* Writes a string to the file as a sequence of characters. Each
* character is written to the data output stream as if by the
* <code>writeChar</code> method.
*
* @param s a <code>String</code> value to be written.
* @exception IOException if an I/O error occurs.
* @see java.io.RandomAccessFile#writeChar(int)
*/
public final void writeChars(String s) throws IOException {
int len = s.length();
for (int i = 0 ; i < len ; i++) {
int v = s.charAt(i);
write((v >>> 8) & 0xFF);
write((v >>> 0) & 0xFF);
}
}
/**
* Writes a string to the file using UTF-8 encoding in a
* machine-independent manner.
* <p>
* First, two bytes are written to the file as if by the
* <code>writeShort</code> method giving the number of bytes to
* follow. This value is the number of bytes actually written out,
* not the length of the string. Following the length, each character
* of the string is output, in sequence, using the UTF-8 encoding
* for each character.
*
* @param str a string to be written.
* @exception IOException if an I/O error occurs.
*/
public final void writeUTF(String str) throws IOException {
int strlen = str.length();
int utflen = 0;
for (int i = 0 ; i < strlen ; i++) {
int c = str.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
utflen++;
} else if (c > 0x07FF) {
utflen += 3;
} else {
utflen += 2;
}
}
if (utflen > 65535)
throw new UTFDataFormatException();
write((utflen >>> 8) & 0xFF);
write((utflen >>> 0) & 0xFF);
for (int i = 0 ; i < strlen ; i++) {
int c = str.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
write(c);
} else if (c > 0x07FF) {
write(0xE0 | ((c >> 12) & 0x0F));
write(0x80 | ((c >> 6) & 0x3F));
write(0x80 | ((c >> 0) & 0x3F));
} else {
write(0xC0 | ((c >> 6) & 0x1F));
write(0x80 | ((c >> 0) & 0x3F));
}
}
}
/**
* Create a string representation of this object.
*
* @return a string representation of the state of the object.
*/
public String toString( ) {
return "fp=" + filePosition + ", bs=" + bufferStart +
", de=" + dataEnd + ", ds=" + dataSize +
", bl=" + buffer.length + ", m=" + mode +
", bm=" + bufferModified;
}
/**
* Test the byte operations of the RandomAccessFile class. These are
* the methods that read/write on a byte-by-byte basis. The following checks
* are made:
* <ul>
* <li>Writing random bytes to a file.
* <li>Checking the size of the file is correct.
* <li>Checking that EOF is correctly raised.
* <li>Reading the file back in and verifying its contents.
* </ul>
* The test file is 4.5 times the size of the buffer, in order to test
* paging between buffers, and using files that end in the middle of a
* buffer. A constant seed value is used for the random number generator,
* to ensure any bugs are reproduceable.
*
* @param filename the name of the test file to generate.
* @param bufferSize the size of the buffer to use.
*/
public static void testBytes( String filename, int bufferSize ) {
System.out.println( "\nTesting byte operations..." );
int newFileSize = (int)(bufferSize * 4.5 );
try {
// Create a test file.
RandomAccessFile outFile = new RandomAccessFile( filename,
RandomAccessFile.WRITE |
RandomAccessFile.CREATE, bufferSize );
try {
Random random = new Random( 0 );
byte b = 0;
for( int i = 0; i < newFileSize; i++ ) {
b = (byte)(random.nextInt( ) % 256);
outFile.writeByte( b );
}
} finally {
outFile.close( );
}
// Check that the file length is correct.
if( (new File( filename )).length( ) == newFileSize )
System.out.println( ". File size correct (" + newFileSize + ")." );
else
System.out.println( "X New file size incorrect (should be " + newFileSize +
", but is " + (new File( filename )).length( ) + ")." );
// Read the file, verify and modify its contents.
RandomAccessFile inoutFile = new RandomAccessFile( filename,
RandomAccessFile.READ |
RandomAccessFile.WRITE, bufferSize );
boolean verified = true;
int byteNo = 0;
try {
// Read each byte in the file.
Random random = new Random( 0 );
byte b = 0;
for( byteNo = 0; byteNo < newFileSize; byteNo++ ) {
b = (byte)(random.nextInt( ) % 256);
byte currentByte = inoutFile.readByte( );
// Check the value is correct.
if( currentByte != b )
verified = false;
// Modify selected values.
if( currentByte >=128 ) {
inoutFile.seek( inoutFile.getFilePointer( ) - 1 );
inoutFile.writeByte( 0 );
}
}
// Check the EOF is correctly trapped.
boolean foundEOF = false;
try {
inoutFile.readByte( );
} catch( EOFException e ) {
foundEOF = true;
}
if( foundEOF )
System.err.println( ". EOF found correctly" );
else
System.err.println( "X No EOF found." );
// Trace a premature EOF.
} catch( EOFException e ) {
e.printStackTrace( );
System.err.println( " At byte " + byteNo );
} finally {
inoutFile.close( );
}
// Check that the read was verified.
if( verified )
System.out.println( ". Read/Write verified" );
else
System.out.println( "X Read/Write verification failed" );
// Read the file and verify contents.
RandomAccessFile inFile = new RandomAccessFile( filename,
RandomAccessFile.READ, bufferSize );
verified = true;
byteNo = 0;
try {
// Read each byte in the file.
Random random = new Random( 0 );
byte b = 0;
for( byteNo = 0; byteNo < newFileSize; byteNo++ ) {
b = (byte)(random.nextInt( ) % 256);
byte currentByte = inFile.readByte( );
// Account for the modification.
if( currentByte >= 128 )
currentByte = 0;
// Check the byte's value.
if( currentByte != b )
verified = false;
}
// Trap a premature EOF.
} catch( EOFException e ) {
e.printStackTrace( );
System.err.println( " At byte " + byteNo );
} finally {
inFile.close( );
}
// Check that the read was verified.
if( verified )
System.out.println( ". Update verified" );
else
System.out.println( "X Update verification failed" );
} catch( Exception e ) {
e.printStackTrace( );
}
}
/**
* Test the block operations of the RandomAccessFile class. These
* are the methods that read/write blocks of data. The following checks
* are made:
* <ul>
* <li>Writing blocks of data that are smaller than the buffer size.
* <li>Writing blocks of data that are larger than the buffer size.
* <li>Checking the size of the file is correct.
* <li>Reading small blocks of the file back in and verifying its contents.
* <li>Reading large blocks of the file back in and verifying its contents.
* </ul>
*
* @param filename the name of the test file to generate.
*/
public static void testBlocks( String filename ) {
System.err.println( "\nTesting block operations..." );
// Generate the data.
int bufferSize = 10;
byte data[] = new byte[256];
for( int i = 0; i < data.length; i++ )
data[i] = (byte)(i % 256);
try {
// Write the data in small and large blocks.
RandomAccessFile outFile = new RandomAccessFile(
filename, RandomAccessFile.WRITE |
RandomAccessFile.CREATE, bufferSize );
for( int i = 0; i < data.length; ) {
int blockSize = (i < data.length / 2) ? 3 :
13 ;
blockSize = (i + blockSize >= data.length) ? (data.length - i) :
blockSize;
outFile.write( data, i, blockSize );
i += blockSize;
}
outFile.close( );
// Check that the file length is correct.
if( (new File( filename )).length( ) != data.length )
System.out.println( "X New file size incorrect (should be " + data.length +
", but is " + (new File( filename )).length( ) + ")." );
else
System.out.println( ". File size correct (" + data.length + ")." );
// Reopen the file for reading.
RandomAccessFile inFile = new RandomAccessFile(
filename, RandomAccessFile.READ, bufferSize );
// Read and check random small blocks of data.
boolean verified = true;
int firstFailure = 256;
Random random = new Random( 0 );
byte block[] = new byte[(int)(bufferSize * 0.5)];
for( int i = 0; i < 100; i++ ) {
int index = Math.abs( random.nextInt( ) ) % (data.length - block.length);
inFile.seek( index );
inFile.read( block );
// Verify the block of data.
for( int j = 0; j < block.length; j++ ) {
if( block[j] != data[index + j] ) {
verified = false;
if( index + j < firstFailure )
firstFailure = index + j;
}
}
}
if( verified )
System.err.println( ". Reading small blocks verified." );
else
System.err.println( "X Reading small blocks failed (byte " + firstFailure + ")." );
// Read and check random large (bigger than the bufferSize) blocks
// of data.
verified = true;
random = new Random( 0 );
block = new byte[(int)(bufferSize * 1.5)];
for( int i = 0; i < 100; i++ ) {
int index = Math.abs( random.nextInt( ) ) % (data.length - block.length);
inFile.seek( index );
inFile.read( block );
// Verify the block of data.
for( int j = 0; j < block.length; j++ ) {
if( block[j] != data[j + index] )
verified = false;
}
}
if( verified )
System.err.println( ". Reading large blocks verified." );
else
System.err.println( "X Reading large blocks failed." );
// Close the input file.
inFile.close( );
} catch( Exception e ) {
e.printStackTrace( );
}
}
/**
* Benchmark the performance of the new RandomAccessFile
* class. Its speed is compared to that of a
* java.io.RandomAccessFile, based on reading and writing a test
* file, byte by byte.
*
* @param filename the name of the test file.
* @param bufferSize the buffer size to use. */
public static void benchmark( String filename, int bufferSize ) {
System.out.println( "\nBenchmarking..." );
// Start the clock, and open a file for reading and a file for writing.
long time = (new Date( )).getTime( );
try {
RandomAccessFile inFile = new RandomAccessFile( filename,
RandomAccessFile.READ, bufferSize );
RandomAccessFile outFile = new RandomAccessFile( "temp.data",
RandomAccessFile.WRITE |
RandomAccessFile.CREATE, bufferSize );
// Copy one file to the other.
try {
while( true ) {
outFile.writeByte( inFile.readByte( ) );
}
} catch( EOFException e ) {
} catch( IOException e ) {
e.printStackTrace( );
} finally {
inFile.close( );
outFile.close( );
}
System.out.println( ". RandomAccessFile elapsed time=" +
((new Date( )).getTime( ) - time) );
// Restart the clock, and open RandomAccessFiles for reading and writing.
time = (new Date( )).getTime( );
java.io.RandomAccessFile inFile2 = new java.io.RandomAccessFile( filename, "r" );
java.io.RandomAccessFile outFile2 = new java.io.RandomAccessFile( "temp.data", "rw" );
// Copy one file to the other.
try {
while( true ) {
outFile2.writeByte( inFile2.readByte( ) );
}
} catch( EOFException e ) {
} catch( IOException e ) {
e.printStackTrace( );
} finally {
inFile2.close( );
outFile2.close( );
}
} catch( Exception e ) {
e.printStackTrace( );
}
System.out.println( ". java.io.RandomAccessFile elapsed time=" + ((new Date( )).getTime( ) - time) );
}
/**
* Test the RandomAccessFile class. This involves testing the byte
* methods, the block methods, and benchmarking the performance. By appending
* 'test' or 'benchmark' to the command-line, it can be limited to the tests
* or benchmarking alone. The test filename is only used for the benchmarking,
* the other tests create a file called "temp.data" in the current directory.
* Note that the size of the buffer determines the size of the test file
* (which is 4.5 times the size of the buffer).
*
* @param argv Usage: <testFilename> [bufferSize] [test | benchmark]
* @see #testBytes(String filename, int bufferSize)
* @see #testBlocks(String filename)
* @see #benchmark(String filename, int bufferSize)
*/
public static void main( String argv[] ) {
int defaultPageSize = 4096;
// Parse the command-line arguments.
String filename = null;
int bufferSize = 0;
boolean test = true;
boolean benchmark = true;
if( argv.length < 1 ) {
System.err.println( "Usage: RandomAccessFile <filename> [buffer.length] [benchmark | test]" );
System.exit( -1 );
} else if( argv.length < 2 ) {
filename = argv[0];
bufferSize = defaultPageSize;
} else if( argv.length < 3 ) {
filename = argv[0];
bufferSize = Integer.parseInt( argv[1] );
} else {
filename = argv[0];
bufferSize = Integer.parseInt( argv[1] );
if( argv[2].equals( "benchmark" ) )
test = false;
else if( argv[2].equals( "test" ) )
benchmark = false;
}
System.out.println( "\nRandomAccessFile\n" +
"========================" );
System.out.println( "filename=" + filename +
", bufferSize=" + bufferSize );
System.out.println( "totalMemory=" +
(Runtime.getRuntime( ).totalMemory( ) / 1000) + "k" +
" freeMemory=" + (Runtime.getRuntime( ).freeMemory( ) / 1000) + "k" );
if( test ) {
RandomAccessFile.testBytes( "temp.data", bufferSize );
RandomAccessFile.testBlocks( "temp.data" );
}
if( benchmark ) {
RandomAccessFile.benchmark( filename, bufferSize );
}
System.out.println( "\nEND" );
}
}