HD%20enclosure%20-%20install.png  

LNK2LRN™2009/10.

November 12 to 24

  AP Computer

Science A

Ch.6: File IO in Java.

Weekly Plans and Assignments:

1. Thursday(11/12): Introduction to Ch.6 - FileIO in Java. Reading from a

data file and writing to the file. Here is the DataFile for the TimeLine Program.

Here is a program that reads a data file line by line, FileReaderDemo.

HW: Program 6_01, the Timeline Program.

2. Friday(11/13): Class Presentations of Prog6_01 Timeline, and hand

tracing of algorithmic code. Here is the DataFile for the Basketball Tryout

Stats Program. HW: Program 6_02 BasketballTryoutStats.

3. Monday(11/16): Class Presentations of Prog6_02 BasketballTryoutStats,

 and hand tracing of algorithmic code. Here is the DataFile for the Business

Accounts Program. Here is a program that reads a data file character by

character, FileReaderDemo2. HW: Program 6_03 BusinessAccounts.

4. Tuesday(11/17): Presentations of Prog6_03 BusinessAccounts, and hand

tracing of Java programs. Here is the DataFile for the Multiple-Choice Test

Corrector Program. HW: Program6_04 TestCorrect.

3. Wednesday(11/18): Presentations of Prog6_04 and hand-tracing of

Java programs. Here is the DataFile for the DVD Search Program.

HW: Program6_05 DVDSearch.

4. Thursday(11/19): Presentations of Prog6_05 and hand-tracing of

Java programs. Here is a program that transfers data from one file to

another, FileAccess_out. HW: Program6_06 FileDataTransfer.

5. Friday(11/20): Class-work Handout #1 on review of Java and related

topics. Here is a program that accepts String data from the keyboard

and and then writes it to a file, FileOutputDemo. HW: Program6_07

ToolInventory.

6. Monday(11/23): Class-work Handout #2 on review of Java and related

topics. HW: Program 6_08 BusinessAccountsII.

7. Tuesday(11/24): Test on Ch.6 - FileIo in Java.

HW: Program6_09 ToolInventoryII.

(Click here for assignment details.)

Very Important: If you have any questions or were absent from

class, see me before school (8:00 - 8:30 AM), during Lunch, or

after school. Best to send an email to rpersin@fau.edu.

 

Website Notes: AP Comp Sci A – Java.

I. File I/O Basics:

Since we are relatively new to programming file operations, there are a couple

of things that may not be apparent and can be a source of confusion so let's

clarify them before we go any further.

1. Let's consider the nature of a file. Once you have written data to a file,

you can regard it as just a linear sequence of bytes. The bytes in a file are

referenced by their offset from the beginning, so the first byte is byte 0, the

next byte is byte 1, the third byte is byte 2, and so on through to the end of

the file.

2. If there are n bytes in a file the last byte will be at offset n-1. There is no

specific information in the file about how the data originated or what it

represents unless you explicitly put it there. Even if there is, you need to

know that it's there and read and interpret the data accordingly.

3. For instance if you write a series of 25 binary values of type int to a file, it

will contain 100 bytes. There will be nothing in the file to indicate that the

data consists of four byte integers so there is nothing to prevent you from

reading the data back as 50 Unicode characters or 10 long values followed by

a string, or any other arbitrary collection of data items that corresponds to

100 bytes.

4. Of course, the result is unlikely to be very meaningful unless you interpret

the data in the form in which it was written. This implies that to read data

from a file correctly, you need to have prior knowledge of the structure and

format of the data.

5. There are many ways in which the form of the data in the file may be

recorded or implied. For instance, one way that the format of the data in a

file can be communicated is to use an agreed file name extension for data

of a particular kind, such as .java or .gif or .wav. Each type of file has a

predefined structure so from the file extension you know how to interpret

the data in the file.

6. You can access an existing file to read from or write to it in two different

ways, described as sequential access or random access. The latter is

sometimes referred to as direct access.

7. Sequential access to a file is quite straightforward and works pretty much

as you would expect. Sequential read access involves reading bytes from the

file starting from the beginning with byte 0. Of course, if you are only

interested in the file contents starting at byte 100, you can just read and

ignore the first 100 bytes.

8. Sequential write access involves writing bytes to the file either starting at

the beginning if you are replacing the existing data or at the end if you are

appending new data to the file.

9. The term random access is often misunderstood initially. Just like

sequential access, random access is just a way of accessing data in a file

and has nothing to do with how the data in the file is structured or how the

physical file was originally written.

10. You can access any file randomly for reading and/or writing. When you

access a file randomly, you can read one or more bytes from the file starting

at any point. For instance, you could read 20 bytes starting at the thirteenth

byte in the file (which will be the byte at offset 12 of course), then read 50

bytes starting at the 101st byte or any other point that you choose.

11. Similarly, you can update an existing file in random access mode by

writing data starting at any point in the file. In random access mode, the

choice of where to start reading or writing and how many bytes you read or

write, is entirely up to you.

12. You just need to know the offset for the byte where a read or write

operation should start. Of course, for these to be sensible and successful

operations, you have to have a clear idea of how the data in the file is

structured.

II. File Input and Output:

1. The new file I/O capabilities introduced in Java 1.4 provide the potential

for substantially improved performance at the cost of some slight increase

in complexity over the I/O facilities of previous releases.

2. There are three kinds of objects involved in reading and writing files

using the new I/O capability:

  • A file stream object that encapsulates the physical file that you are

      working with. You use these for files to which you want to write. We

      will also be using FileInputStream objects for files that we want to read.

  • One or more buffer objects in which you put the data to be written to a

       file, or from which you get the data that has been read.

  • A channel object that provides the connection to the file and does the

       reading or writing of the data using one or more buffer objects.

3. The process for writing and reading files is basically quite simple. To write

to a file, you load data into one or more buffers that you have created, and

then call a method for the channel object to write the data to the file that is

encapsulated by the file stream. To read from a file, you call a method for the

channel object to read data from the file into one or more buffers, and then

retrieve the data from the buffers.

4. There are four classes defined in the java.io package that we will be using

when we are working with files. As we have said, the FileInputStream and

FileOutputStream classes define objects that provide access to a file for

reading or writing respectively.

5. You use an object of type RandomAccessFile when you want to access a

file randomly, or when you want to use a single channel to both read from

and write to a file.

6. You will see from the SDK documentation for the FileInputStream,

FileOutputStream, and RandomAccessFile classes that they each provide

methods for I/O operations. However, we will ignore these, as we will be

using the services of a file channel to perform operations with objects of

these stream classes.

7. The only method from these classes that we will be using is the close()

method, that closes the file and any associated channel.

III. File Channels.

1. These were introduced in the 1.4 release of Java to provide a faster

capability for input and output operations with files, network sockets, and

piped I/O operations between programs than the methods provided by the

stream classes. We will only be discussing channels in the context of files.

2. The channel mechanism can take advantage of buffering and other

capabilities of the underlying operating system and therefore is considerably

more efficient than using the operations provided directly within the file

stream classes.

3. As we said earlier, a channel transfers data between a file and one or

more buffers. We will take a quick look at the overall relationship between

the various classes that define channels and buffers, and then look into the

details of how you use channels with file streams.

4. There are a considerable number of classes and interfaces defining both

channels and buffers. They also have similar names such as ByteBuffer and

ByteChannel. Of course, File and file stream objects are also involved in file

I/O operations so you will be using at least four different types of objects

working together when you read from or write to files.

5. Just to clarify what they all do, here's a summary of the essential role of

each of them in file operations:

  • A File object encapsulates a path to a file or a directory, and such an

      object encapsulating a file path can be used to construct a file stream  

      object.

  • A FileInputStream object encapsulates a file that can be read by a

       channel. A FileOutputstream object encapsulates a file that can be

       written by a channel.

  • RandomAccessFile object can encapsulate a file that can be both read

       and written by a channel.

  • A buffer just holds data in memory. You load into a buffer what is to

       be written to a file using the buffer's put() methods.

  • You use a buffer's get() methods to retrieve data that has been read

       from a file.

  • You obtain a FileChannel object from a file stream object or a

       RandomAccessFile object.

  • A FileChannel object can read and write a file using read() and write()

       methods with a buffer or buffers as the source or destination of data.

6. The channel interfaces and classes that we will be using are in the

java.nio.channels package. The classes that define buffers are defined in

the java.nio package. In a program that reads or writes files we will

therefore need import statements for class names from at least three

packages, the two packages we have just introduced plus the java.io

package.

IV. File Buffers.

1. All the classes that define buffers have the abstract class Buffer as a

base. The Buffer class therefore defines the fundamental characteristics

common to all buffers.

2. A particular buffer can store a sequence of elements of a given type,

and an element can be of any primitive data type other than boolean.

Thus, you can create buffers to store byte values, char values, short

values, int values, long values, float values, or double values.

3. We keep repeating except for type boolean every so often, so we

had better address that. The various types of buffers only provide for

the numerical data types and type boolean does not fit into this

category.

4. Of course, you may actually want to record some boolean values in

a file. In this case, you have to devise a alternative representation.

You could use integer values 0 and 1, or perhaps strings "true" and

"false", or even characters 't' and 'f'.

5. You could even represent a boolean value as a single bit and pack

eight of them at a time into a single byte, but this is only likely to be

worthwhile if you have a lot of them. Which approach you choose will

depend on what is most convenient in the context in which you are

using them.

6. While we have seven different classes defining buffers, a channel

only uses buffers of type ByteBuffer to read or write data. The other

types of buffers are called view buffers, because they are usually

created as views of an existing buffer of type ByteBuffer.

7. Each type of buffer stores elements of a specific kind – a ByteBuffer

object holds bytes, a LongBuffer object holds integers of type long, and

so on for the other buffer types.

8. The capacity of a buffer is the maximum number of elements it can

contain, not the number of bytes – unless, of course, it stores elements

of type byte. The capacity of a buffer is fixed when you create it and

cannot be changed subsequently. You can obtain the capacity for a

buffer object as a value of type int by calling the capacity() method

that it inherits from the Buffer class.

9. Of course, for a buffer that stores bytes, the capacity will be the

maximum number of bytes it can hold, but for a buffer of type

DoubleBuffer for instance that stores double values, the capacity will

be the maximum number of double values you can put in it. Elements

in a buffer are indexed from zero so the index position for referencing

elements in a buffer runs from 0 to capacity-1.

10. A buffer also has a limit and a position, both of which affect read/

write operations executed by a channel using the buffer.

11. The position is the index position of the next buffer element to be

read or written. This sounds a little strange, but keep in mind that a

buffer can be for file input or output. For example, with a buffer used for

output, the position identifies the next element to be written to the file.

For a buffer used for file input, the position identifies where the next

element read from the file will be stored.

12. The limit is the index position of the first element that should not

be read or written. Thus, elements can be read or written starting with

the element at position, and up to and including the element at limit-1.

13. Thus if you want to fill all the elements in a buffer, the position

must be at zero since this is where the first data item will go, and the

limit must be equal to the capacity since the last data item has to be

stored at the last element in the buffer, which is capacity-1.

14. A buffer's position and limit are used for determining what elements

are involved in a read or write operation executed by a channel. How

they affect I/O operations is easier to understand if we take a specific

example. Let's first consider an operation that writes data from the

buffer to a file.

15. When a file write operation is executed by a channel using a given

buffer, elements from the buffer will be written to the file starting at

the index specified by the position. Successive elements will be written

to the file up to, and including, the element at index position limit-1.

16. For a read operation, data that is read from the file is stored in a

buffer starting at the element index given by the buffer position.

Elements will continue to be read, assuming they are available from

the file, up to the index position limit-1.

17. Thus when you want to write all the data from a buffer, the limit

will have to be equal to the capacity. In this case the limit will be an

index value that is one beyond the index value for the last element in

the buffer, so limit-1 will refer to the last element.

18. The position and limit are involved when you load data into a buffer

or retrieve data from it. The position specifies where the next element

should be inserted in a buffer or retrieved from it. As we shall see, you

will usually have the position automatically incremented to point to the

next available position when you insert or extract elements in a buffer.

19. The limit acts as a constraint to indicate where the data in a buffer

ends, a bit like an end-of-file marker. You cannot insert or extract

elements beyond the position specified by the limit.

20. Since a buffer's position is an index, it must be greater than or equal

to zero. You can also deduce that it must also be less than or equal to

the limit. Clearly, the limit cannot be greater than the capacity of a buffer.

21. Otherwise, we could be trying to write elements to positions beyond

the end of the buffer. However, as we have seen, it can be equal to it.

These relationships can be expressed as:

0 • position • limit • capacity

V. File Buffer Methods.

1. As a general rule, if your code attempts to do things directly or indirectly

that result in certain conventions being violated, an exception will be

thrown.

2. When you create a new independent buffer, its capacity will be fixed at a

value that you specify. It will also have a position of zero and its limit will

be set to its capacity. When you create a view buffer from an existing

ByteBuffer, the contents of the view buffer starts at the current position for

the ByteBuffer.

3. The capacity and limit for the view buffer will be set to the limit for the

original buffer, divided by the number of bytes in an element in the view

buffer. The limit and position for the view buffer will subsequently be

independent of the limit and position for the original buffer.

4. Of course, before you can use a channel to write the contents of a buffer

to a file, you need to load the buffer with the data. Methods for loading

data into a buffer are referred to as put methods. Similarly, when a

channel has read data from a file into a buffer, you are likely to want to

retrieve the data from the buffer. In this case you use the buffer's get

methods.

5. There are two kinds of operations that transfer data elements to or from

 a buffer. A relative put or get operation transfers one or more elements

starting at the buffer's current position. In this case the position is

automatically incremented by the number of elements transferred. In an

absolute put or get operation, you explicitly specify an index for the

position in the buffer where the data transfer is to begin. In this case the

buffer's position will not be updated so it will remain at the index value it

was before the operation was executed.

6. The ByteBuffer class and all the view buffer classes have two put()

methods for transferring a single element of the buffer's type to the buffer.

One is a relative put() method that transfers an element to a given index

position in the buffer and the other is an absolute put method that places

the element at an index position that you specify as an argument.

7. They also have three relative put methods for bulk transfer of elements

of the given type. Each of these methods returns a reference to the buffer

for which they were called. If the buffer is read-only, any of these methods

will throw an exception of type ReadOnlyBufferException.

8. We will see how a buffer can be read-only when we discuss Using View

Buffers in more detail. Each Buffer class that stores elements of a given

basic type – CharBuffer, DoubleBuffer, or whatever – will have put()

methods analogous to these, but with arguments of a type appropriate to

the type of element in the buffer.

9. The ByteBuffer class has some extra methods that enable you to transfer

binary data of other primitive types to the buffer. To start with, we will be

using the simplest write() method for a file channel that writes the data

contained in a single ByteBuffer object to a file.

10. The number of bytes written to the file is determined by the buffer's

position and limit when the write() method executes. Bytes will be written

starting with the byte at the buffer's current position. The number of bytes

written is limit-position, which is the number returned by the remaining()

method for the buffer object.

11. The write() method returns the number of bytes written as a value

type int. A write() method for a channel will only return when the write

operation is complete, but this does not guarantee that the data has

actually been written to the file.

12. Some of the data may still reside in the native I/O buffers. If the

data you are writing is critical and you want to minimize the risk of

losing it in the event of a system crash, you can force all outstanding

output operations to a file that were previously executed by the channel

to be completed by calling the force() method for the FileChannel object.

13. The force() method will throw a ClosedChannelException if the

channel is closed, or an IOException if some other I/O error occurs.

Note that the force() method only guarantees that all data will be

written for a local storage device.

14. Only one write operation can be in progress for a given file channel

at any time. If you call write() while a write() operation initiated by

another thread is in progress, your call to the write() method will block

until the write that is in progress has been completed.

15. We will just write a series of useful statements to a file called

statmnts.txt, that we will create in the directory MyProjects on drive C:.

If you want to write to a different drive and/or directory, just change

the program accordingly. Here's the code:

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;

public class WriteProverbs {
  public static void main(String[] args) {
    String dirName = "c:/MyProjects";   // Directory for the output file
    String fileName = "statmnts.txt";       // Name of the output file
    String[] sayings = {
      "I think therefore I am.",
      "Only the mediocre are always at their best.",
      "A little knowledge is a dangerous thing.",
      "Calculate what you talk about.",
      "Come to class with the knowledge of the day before.",
      "Who knows most says least.",
      "Success is equal to preparation plus opportunity."
    };
    File aFile = new File(dirName, fileName);

    FileOutputStream outputFile = null;
    try {
      outputFile = new FileOutputStream(aFile, true);
    } catch (FileNotFoundException e) {
      e.printStackTrace(System.err);
      System.exit(1);
    } 
    FileChannel outChannel = outputFile.getChannel();

    // Create a buffer to accommodate the longest string + its length value
    int maxLength = sayings[0].length();
    for (int i = 1; i < sayings.length; i++) { 
      if (maxLength < sayings[i].length()) 
        maxLength = sayings[i].length ();
    }

    ByteBuffer buf = ByteBuffer.allocate(2 * maxLength + 4);

    // Write the file
    try {
      for (int i = 0; i < sayings.length; i++) {
        buf.putInt(sayings[i].length()).asCharBuffer().put(sayings[i]);
        buf.position(buf.position() + 2 * sayings[i].length()).flip();
        outChannel.write(buf);   // Write the buffer to the file channel
        buf.clear();
      }
      outputFile.close();        // Close the output stream & the channel
      System.out.println("Statements written to file.");
    } catch (IOException e) {
      e.printStackTrace(System.err);
      System.exit(1);
    }
    System.exit(0);
  }
}

16. When you execute this it should produce the rather terse output:

Statements written to file.

You can check the veracity of this assertion by inspecting the contents of

the file with a plain text editor. The program writes the strings from the

array, sayings, to the file.

17. We create a String array, sayings[], that contains statements that

are written to the stream in the for loop. We put the length of each

proverb in the buffer using the putInt() method for the ByteBuffer object.

We then use a view buffer of type CharBuffer to transfer the string to the

buffer.

19. The contents of the view buffer will start at the current position for

the byte buffer. This corresponds to the byte immediately following the

string length.

20. Transferring the string into the view buffer only causes the view

buffer's position to be updated. The byte buffer's position is still pointing

back at the byte following the string length where the first character of the

string was written. We therefore have to increment the position for the

byte buffer by twice the number of characters in the string before flipping

it to make it ready to be written to the file.

21. The first time you run the program, the file doesn't exist, so it will be

created. You can then look at the contents. If you run the program again,

the same statements will be appended to the file, so there will be a

second set.

22. Alternatively, you could modify the sayings[] array to contain different

statements the second time around. Each time the program runs, the data

will be added at the end of the existing file.

23. After writing the contents of the byte buffer to the file, we call its

clear() method to reset the position to zero and the limit back to the

capacity. This makes it ready for transferring the data for the next

statement on the next iteration. Remember that it doesn't change the

contents of the buffer though.

VI. Random Access Files

1. Instances of this class support both reading and writing to a random

access file. A random access file behaves like a large array of bytes stored

in the file system. There is a kind of cursor, or index into the implied array,

called the file pointer; input operations read bytes starting at the file

pointer and advance the file pointer past the bytes read.

2. If the random access file is created in read/write mode, then output

operations are also available; output operations write bytes starting at the

file pointer and advance the file pointer past the bytes written.

3. Output operations that write past the current end of the implied array

cause the array to be extended. The file pointer can be read by the

getFilePointer() method and set by the seek() method.

4. Here are some of the methods that you may need:

  • close() Closes the random access file and releases any system

       resources associated with the file.

  • length() Returns the length of the file

  • readDouble() Reads a double from the file

  • readFloat() Reads a float from the file

  • readInt() Reads a signed 32-bit integer from the file

  • readLong() Reads a signed 64-bit integer from a file

  • seek ( long pos ) Sets the file pointer offset, measured from the

       beginning of the file, at which the next read or write occurs

  • writeDouble(double v) Writes a double as an eight-byte quantity
  • writeFloat(float v) Writes a float as a four-byte quantity

  • writeInt(int v) Writes an int as four bytes

  • writeLong (long v) Writes a long to the file as eight bytes

5. The following code shows how to use the RandomAccessFile class.

The random access file in this example is a binary file called accounts.

import java.io.*;

public class RandomFile
{
  final static int LONG_SIZE = 8;
  final static int DOUBLE_SIZE = 8;
  final static int RECORD_SIZE = LONG_SIZE + DOUBLE_SIZE;

  static RandomAccessFile file;
  static long numAccounts = 0;
  
  public static void updateHeader ( double newBalance ) throws IOException
  {
    file.seek ( 0 );
    file.writeLong ( numAccounts );

    double tmpBal;
    if ( numAccounts == 0 )
      tmpBal = newBalance;
    else
    {
      tmpBal = file.readDouble ();
      tmpBal += newBalance;
    }
    file.seek ( LONG_SIZE );
    file.writeDouble ( tmpBal );
  }
  
  public static void writeHeader ( ) throws IOException
  {
    file.seek ( 0 );
    System.out.println ( "The number of accounts = " + file.readLong() );
    System.out.println ( "The total balance = " + file.readDouble() );
  }

  public static void createAccount ( double newBalance ) throws IOException
  {
    numAccounts++;
    file.seek ( numAccounts * RECORD_SIZE );
    file.writeLong ( numAccounts );
    file.writeDouble ( newBalance );
    updateHeader ( newBalance );
  }

  public static int fileSize ( ) throws IOException
  {
    return ( int ) ( file.length() / RECORD_SIZE );
  }

  public static void scanFile () throws IOException
  {
    file.seek ( 0 );
    long numRecords = file.readLong ();
    double sumBalance = file.readDouble ();

    long account;
    double balance;

    for (long i = 1; i <= numRecords; i++ )
    {
      account = file.readLong ();
      balance = file.readDouble ();
      System.out.println ("Account number: " + account + "  " +
			  "Balance: " + balance );
    }
  }

  public static void main ( String args[] ) throws IOException
  {
    // Create random access file and update header
    file = new RandomAccessFile ("accounts", "rw");
    double sumBalance = 0.0;
    updateHeader ( sumBalance );

    // Create account and update header
    double newBalance = 1000.0;
    createAccount ( newBalance );

    // Create another account and update header
    newBalance = 2000.0;
    createAccount ( newBalance );

    // Create another account and update header
    newBalance = 3000.0;
    createAccount ( newBalance );

    // Write out the size of the file
    System.out.println ( "The number of records in the file is " + fileSize( ) );

    // Write out the header of the file
    writeHeader ();

    // Scan the file
    scanFile ();
  }
}

The Back Burner

Back-Burner Assignments

(Click on the back burner of the stove for descriptions.)

1. Computerized Player Evaluation - due Nov. 16.
2. The Rhine Test - due Nov. 18.
3. The 23 Matches - due Nov. 20.
4. Your Own Game - due Nov. 30.
5. The AI Program - due Dec. 9.

 

 

animated open door gifThe Vault  

CH.1_Introduction to Computer Science  

CH.2_Java Basics 

CH.3_Char, Loop, Selection Statements 

Ch.4  Java Char and String Classes.

CH.5_Using Arrays in Java

CH.6  File IO in Java

MIT Artificial Intelligence
AI Sussex, UK
The Rhine Test

View the Ch.5 Powerpoint

Anti-Phishing Game

8 Queens
ASCII Table
Review of Matrix Algebra

Sun Microsystems Java

Elements of Java

University of Virginia Oracle

Review of Java

Honda ASIMO