9. Streams and
Exceptions
To be able to read and
write files
To understand the
concepts of text and
binary streams
To learn how to throw
and catch exceptions
To be able to process
the command line
To be able to
read and write
objects using
serialization
Streams, Readers, and
Writers
There are two
different ways to store
data: text or binary
formats.
In text format, data
items are represented
in human-readable
form, as a sequence of
characters. For
example, the interger
12345 is stored as the
sequence of five
characters:
‘1’ ‘2’ ‘3’ ‘4’ ‘5’
In binary form, data
items are represented
in bytes. For example,
the integer 12345 is
stored as a sequence
of four bytes:
0 0 48 57 (because
12345 = 48*256 + 57)
We use the Reader and
Writer classes and
their subclasses to
process text files:
FileReader reader =
new FileReader
(“input.txt”);
int next = reader.read()
; // read() will return int
char c;
if (next != -1) // returns
-1 if the end of input
c = (char) next;
FileWriter writer =
new FileWriter
(“output.txt”);
Text input and output
are more convenient
for humans.
To read text data from
a disk file, we create a
FileReader object:
FileReader reader =
new FileReader
(“input.txt”);
To write text data to a
disk file, we use
FileWriter:
FileWriter writer =
new FileWriter
(“Output.txt”);
The Reader class has a
method read to read a
single character at a
time. However, the
read method returns an
int, thus we have to
cast it to a char.
Reader reader = …;
int next = reader.read()
;
char c;
if (next != -1)
c = (char)next;
We use the
InputStream and
OutputStream classes
and their subclasses to
process binary files:
FileInputStream in = new
FileInputStream
(“input.dat”);
int next = in.read();
byte b;
if (next != -1) // returns -1 if
the end of input
b = (byte) next;
FileOutputStream
output = new
FileOutputStream
(“output.dat”);
Binary storage is more
compact and more
efficient.
To read binary data
from a disk file, we
create a
FileInputStream
object:
FileInputStream
inputStream = new
FileInputStream
(“input.dat”);
To write binary data to
a disk file, we use
FileOutputStream:
FileOutputStream
outputStream = new
FileOutputStream
(“output.dat”);
The InputStream class
has a method read to
read a single byte at a
time. The method also
returns an int, thus we
have to cast it to a
byte.
InputStream in = …;
int next = in.read();
byte b;
if (next != -1)
b = (byte)next;
The Writer and
FileOutputStream
classes have a write
method to write a
single character or
byte.
Reading and Writing
Text Files
We construct a
FileWriter object from
the file name:
FileWriter writer =
new FileWriter
(“output.txt”);
We then send a
character at a time to
the file by calling the
write method.
However, the output
we have is in the form
of numbers and strings
so we need another
class whose task is to
break up numbers and
strings into individual
characters and send
them to a writer.
This class is called
PrintWriter.
PrintWriter out = new
PrintWriter(writer);
Next, we can use the
print and println
methods to print
numbers, objects, and
strings:
out.print(29.95);
out.println(new
Rectangle(5, 10, 15, 25)
);
out.println(“Hello.”);
Reading text files is
less convenient, we
have to use the
BufferedReader class,
which has a readLine
method .
After reading a line of
input, we can convert
the strings to integer
or double as needed.
Reading and Writing
Text Files
To write:
FileWriter writer =
new FileWriter
(“output.txt”);
PrintWriter out = new
PrintWriter(writer);
out.print(29.95);
out.println(new
Rectangle(5, 10, 15, 25)
);
out.println(“Hello,
World!”);
To read:
FileReader reader =
new FileReader
(“input.txt”);
BufferedReader in =
new BufferedReader
(reader);
String inputLine =
in.readLine();
double x =
Double.parseDouble
(inputLine);
When we are done
reading/writing from a
file, we should close
the files:
reader.close();
writer.close();
Before we can put
these input/output
classes to work, we
need to know about
exception handling.
All classes for reading
and writing are defined
in the java.io package.
Exception Handling
An exception is an
indication that a
problem occurred
during the program’s
execution.
When a method
detects a problematic
situation, what should
it do?
The methods should
return an indicator
whether it succeeded
or failed.
The exception-
handling mechanism
has been designed to
solve these problems.
When we detect an
error, we only need to
throw an appropriate
exception object.
The programmer
encloses in a try block
the code that may
generate an exception.
The try block is
followed by zero or
more catch blocks.
Each catch block
specifies the type of
exception it can catch
and contains an
exception handler.
An optional finally
block provides code
that always executes
regardless of whether
or not an exception
occurs.
If there is no catch
blocks following a try
block, the finally block
is required.
When an exception is
thrown, program
control leaves the try
block and the catch
blocks are searched.
If the type of the
thrown exception
matches the
parameter type in one
of the catch blocks,
the code for that catch
block is executed.
If a finally block
appears after the last
catch block, it is
executed regardless
of whether or not an
exception is thrown.
An exception can be
thrown from
statements in the
method, or from a
method called directly
or indirectly from the
try block.
The point at which the
throw is executed is
called the throw point.
The throw statement
is executed to indicate
that an exception has
occurred (i.e., a
method could not
complete
successfully).
This is called throwing
an exception.
Throwing an exception
is simple, the Java
library provides many
classes to signal all
sorts of exceptional
conditions.
For example, to signal
an unexpected end of
file, you can use the
EOFException, a
subclass of the
IOException class,
which denotes input/
output errors.
We make an object of
the exception class,
and then throw it.
For example, suppose
we write a method
that reads a product in
from a reader:
public class Product
{ private String name;
private double price;
private int score;
public void read
(BufferedReader in)
{ // read product name
name = in.readLine();
// read product price
String inputLine =
in.readLine();
price =
Double.parseDouble
(inputLine);
// read product score
inputLine = in.readLine
();
score =
Integer.parseInt
(inputLine);
}
}
What happens if we
reach the end of input?
String inputLine =
in.readLine();
if (inputline == null)
// do something
We can throw an
exception as follows:
String inputLine =
in.readLine();
if (inputline == null)
{ EOFException
exception = new
EOFException(“End of
file when reading
price.”);
throw exception;
}
Or we can just throw
the object that the
new operator returns:
String inputLine =
in.readLine();
if (inputline == null)
throw new
EOFException(“End of
file when reading
price.”);
Throwing a new
exception like an
example above is used
when you want to
generate an exception
under your control
rather than letting Java
generate it for you.
Throwing Exceptions
Throwing Exceptions
throw
exceptionObject;
throw new
IllegalArgumentExcept
ion();
...
String input =
in.readLine();
if (input == null)
throw new
EOFException(“EOF
when reading”);
When we throw an
exception, the method
exits immediately.
Execution does not
continue with the
method’s caller but
with an exception
handler.
Catching Exceptions
When an exception is
thrown, an exception
handler needs to catch
it.
We must install
exception handler for
all exceptions that our
program might throw.
We install an
exception handler with
the try statement.
Each try block contains
one or more method
calls that may cause an
exception, and catch
clauses for all
exception types.
Catching Exceptions
try
{ statement
}
catch(ExceptionClass
exceptionObject)
{ statement
}
catch(ExceptionClass
exceptionObject)
{ statement
}
finally
{ ….
}
if use "try block" you
must have whichever
"catch" or "finally"
If a try block has a
corresponding finally
block, the finally block
will be executed even
if the try block is
exited with return,
break or continue; then
the effect of the
return, break or
continue will occur.
When the catch
(IOException e) block
is executed, then
some method in the
try block has failed
with an IOException,
and that exception
object is stored in the
variable e.
try
{ BufferedReader in =
new BufferedReader(
new
InputStreamReader
(System.in));
System.out.println
(“How old are you?”);
String input =
in.readLine();
int age =
Integer.parseInt(input)
;
age++;
System.out.println
(“Next year, you’ll be ”
+ age);
}
catch(IOException e)
{ System.out.println
(“Input/Output error ” +
e);
}
catch(
NumberFormatExcepti
on e)
{ System.out.println
(“Input was not a
number”);
}
The try block contains
six statements with
two potential
exceptions that can be
thrown in this code.
The readLine method
can throw an
IOException, and
Integer.parseInt can
throw a
NumberFormatExcepti
on.
If either of these
exceptions is thrown,
the rest of the
instructions in the try
block are skipped.
Finally
Suppose a method
open a file, calls one
or more methods, and
then close the file:
BufferedReader in;
in = new
BufferedReader(new
FileReader(“input.txt”))
;
readProducts(in);
in.close();
Next, suppose that one
of the methods throws
an exception, then the
call to close is never
executed.
We solve this problem
by placing the call to
close inside a finally
clause:
BufferedReader in =
null;
try
{ in = new
BufferedReader(
new FileReader
(“input.txt”));
readProducts(in);
}
// optional catch
clauses here
finally
{ if (in != null)
in.close();
}
Checked Exception
Exceptions fall into
two categories, called
checked and
unchecked exceptions.
When you call a
method that throws a
checked exception,
you must tell the
compiler what we are
going to do about the
exception if it is
thrown.
We do not need to
keep track of
unchecked exceptions.
Exceptions that belong
to subclasses of
RuntimeException are
unchecked.
Checked Exceptions:
For example: All
subclasses of
IOException;
InterruptedException;
invent yourself by
subclassing Exception.
Unchecked Exceptions:
For example:
RuntimeException and
all subclasses;
ArithmeticException;
IllegalArgumentExcept
ion;
NumberFormatExcetpi
on;
IndexOutOfBoundsExce
ption;
NullPointerException;
invent yourself by
subclassing
RuntimeException
Checked exceptions
must be listed in
method's throws
clause, if we do not
provide catch clause.
Exception Class
Hierarchy
Suppose we want to
throw an IOException
in a method that call
BufferedReader.
readLine.
The IOException is a
checked exception, so
we need to tell the
compiler what we are
going to do about it.
We have two choices:
We can place the call
to the readLine inside a
try block and supply a
catch clause.
We can simply tell the
compiler by tagging
the method with a
throws clause.
Throws Clauses
If we want the
exception to go
uncaught and rely on
the propagation
mechanisms to catch
the exception
elsewhere, we use a
throws clause in the
method header.
Use a throws clause
only if a method will
throw one of the
checked exceptions.
public class Product
{ public void read
(BufferedReader in)
throws IOException
{ ... }
}
The throws clause
signals the caller that
it may encounter an
IOException.
If a method can throw
multiple checked
exceptions, we
separate them by
commas:
public void read
(BufferedReader in)
throws IOException,
FileNotFoundException
Error do not need to be
listed, nor do
RuntimeException
Examples: an out-of-
bounds array subscript,
arithmetic overflow,
division by zero,
invalid method
parameters, memory
exhaustion.
Example
In the next example,
we pass along all
exceptions of type
IOException that
readLine may throw.
When we encounter an
unexpected end of file,
we report it as an
EOFException.
For an expected end of
file, if the end of file
has been reached
before the start of a
record, the method
simply returns false.
If the file ends in the
middle of a record (an
unexpected end of file)
, then we throw an
exception.
public class Product
{ public boolean read
(BufferedReader in)
throws IOException
{ // read product name
name = in.readLine();
if (name == null) return
false;
String inputLine =
in.readLine();
if (inputLine == null)
throw new
EOFException (“EOF
when reading price.”);
price =
Double.parseDouble
(inputLine);
inputLIne = in.readLine
();
if (inputLine == null)
throw new
EOFException (“EOF
when reading score.”);
score =
Integer.parseInt
(inputLine);
return;
}
private String name;
private double price;
private int score;
}
Example
The following method
reads product records
and puts them into a
panel.
It is unconcerned with
any exceptions.
If there is a problem
with the input file, it
simply passes the
exception to its caller.
public void
readProducts
(BufferedReader in)
throws IOException
{ boolean done = false;
while (!done)
{
Product p = new
Product();
if (p.read(in))
panel.addProduct(p);
else
done = true;
}
}
Next, we implement
the user interaction.
We ask the user to
enter a file name and
read the product file.
If there is a problem,
we report the problem.
The program is not
terminated, and the
user has a chance to
open a different file.
public void openFile()
{ BufferedReader in =
null;
try
{ // select file name
. . .
in = new
BufferedReader(new
FileReader(selectFile))
;
readProducts(in);
}
catch(
FileNotFoundException
e)
{ JOptionPane.
showMessageDialog
(null, “Bad filename.
Try again.”);
}
catch(IOException e)
{ JOptionPane.
showMessageDialog
(null, “Corrupted file.
Try again.”);
}
finally
{ if (in != null)
try
{ in.close();
}
catch(IOException e)
{ JOptionPane.
showMessageDialog
(null, “Error closing
file.”);
}
}
}
Example
import java.io.*;
public class StringEx2 {
public static void main
(String args[]) throws
IOException {
FileWriter writer = null;
try {
String input2;
InputStreamReader
reader = new
InputStreamReader
(System.in);
BufferedReader
console = new
BufferedReader
(reader);
System.out.print("Enter
filename ");
input2 =
console.readLine();
writer = new
FileWriter(input2);
PrintWriter out = new
PrintWriter(writer);
double gpa;
System.out.print("Enter
Your GPA ");
input2 =
console.readLine();
gpa =
Double.parseDouble
(input2);
System.out.println
("Your GPA is " + gpa);
out.println(gpa);
}
catch(
NumberFormatExcepti
on e)
{ System.out.println
("Input was not a
number.");
}
catch(
FileNotFoundException
e)
{ System.out.println
("Bad filename. Try
again.");
}
finally {
writer.close();
}
}
}
Example: Reading Input
Data in a Graphical
Program
Recall the BestProduct
program that reads
product data from the
keyboard and then
marks the best buys.
In this example, we
will use a text editor
to write the data
values into a file and
then specify the name
of the file when the
data are to be used.
This program will
display its result in a
graph.
Prices grow in the x-
direction, performance
grows in the y-
direction.
// PlotProducts.java
/**
Reads products and
adds them to the
product panel.
@param in the buffered
reader to read from
*/
public void
readProducts
(BufferedReader in)
throws IOException
{ ...
boolean done = false;
while (!done)
{ Product p = new
Product();
if (p.read(in))
panel.addProduct(p);
else // last product
read
done = true;
}
}
// PlotProducts.java
/**
Handles the open file
menu. Prompts user
for file
name and reads
products from file.
*/
public void openFile()
{ BufferedReader in =
null;
try
{ // show file chooser
dialog
JFileChooser chooser
= new JFileChooser();
if (chooser.
showOpenDialog(null)
==
JFileChooser.APPROVE_
OPTION)
{ // construct reader
and read products
File selectedFile =
chooser.
getSelectedFile();
in = new
BufferedReader(new
FileReader
(selectedFile));
readProducts(in);
}
}
catch(
FileNotFoundException
e)
{ JOptionPane.
showMessageDialog
(null, "Bad filename.
Try again.");
}
catch(IOException e)
{ JOptionPane.
showMessageDialog
(null, "Corrupted file.
Try again.");
}
finally
{ if (in != null)
try
{ in.close();
}
catch(IOException e)
{ JOptionPane.
showMessageDialog
(null, "Error closing
file.");
}
}
}
Command Line
Arguments
There are several
methods of starting a
program:
By selecting “Run” in
the compilation
environment
By clicking on an icon
By typing the name of
the program at a
prompt in a terminal or
shell window
The latter method is
called “invoking the
program from the
command line”.
For the latter method,
we can also type in
additional information
that the program can
use.
These additional
strings are called
command line
arguments.
For example, if we
start a program with
the command line
java Prog –v input.txt
then the program
receives two
command line
arguments: the strings
“-v” and “input.txt”.
Java usually interprets
strings with a – as
options and other
strings as file names.
Command line
arguments are placed
in the args parameter
of the main method:
public static void main
(String[] args)
{ …
}
Thus args contains
two strings
args[0] = “-v”
args[1] = “input.txt”
Encryption
Encryption is a method
used for scrambling a
file so that it is
unreadable except to
those who know the
decryption method and
the secret keyword.
The person performing
any encryption
chooses an encryption
key; here we use a
number between 1 and
25 as the key.
This key indicates the
shift to be used in
encrypting each letter.
For example, if the key
is 3, replace A with a
D, B with an E, and so
on.
Encryption
To encrypt/decrpt data
java Crypt input.txt
encrypt.txt
java Crypt -d -k11
encrypt.txt output.txt
The program takes to
following command
line arguments:
- An optional -d flag to
indicate decryption
- An optional
encryption key, -k flag
- input file name
- output file name
If no key is specified,
then 3 is used.
import java.io.*;
public class Crypt
{ public static void
main(String[] args)
{ boolean decrypt =
false;
int key = DEFAULT_KEY;
FileReader infile = null;
FileWriter outfile =
null;
if (args.length < 2 ||
args.length > 4) usage
();
// gather command line
arguments and open
files
try
{ for (int i = 0; i <
args.length; i++)
{ if (args[i].substring(0,
1).equals("-"))
// it is a command line
option
{ String option = args[i]
.substring(1, 2);
if (option.equals("d"))
decrypt = true;
else if (option.equals
("k"))
{ key =
Integer.parseInt
(args[i].substring(2));
if (key < 1 || key >=
NLETTERS)
usage();
}
}
else
{ if (infile == null)
infile = new FileReader
(args[i]);
else if (outfile == null)
outfile = new
FileWriter(args[i]);
}
}
}
catch(IOException e)
{ System.out.println
("Error opening file");
System.exit(0);
}
if(infile == null || outfile
== null) usage();
// encrypt or decrypt
the input
if (decrypt) key =
NLETTERS - key;
try
{ encryptFile(infile,
outfile, key);
infile.close();
outfile.close();
}
catch(IOException e)
{ System.out.println
("Error processing
file");
System.exit(0);
}
}
public static void
usage()
{ System.out.println
("Usage: java Crypt [-
d] [-kn] infile outfile");
System.exit(1);
}
public static char
encrypt(char c, int k)
{ if ('a' <= c && c <= 'z')
return (char)('a' + (c - 'a'
+ k) % NLETTERS);
if ('A' <= c && c <= 'Z')
return (char)('A' + (c -
'A' + k) % NLETTERS);
return c;
}
public static void
encryptFile(FileReader
in, FileWriter out, int k)
throws IOException
{ while (true)
{ int next = in.read();
if (next == -1) return; //
end of file
char c = (char)next;
out.write(encrypt(c, k))
;
}
}
public static final int
DEFAULT_KEY = 3;
public static final int
NLETTERS = 'z' - 'a' + 1;
}
Random Access
Suppose we want to
change the prices of
some products that we
stored in a file, the
simplest technique is:
read data into an array
Update data
Save data back to a file
What happens if the
data is very large?
There will be a lot of
much reading and
writing.
Up til now, we read to
a file and write to a file
one item at a time, this
access pattern is
called sequential
access.
Next, we will learn
how to access specific
locations in a file and
change just those
locations. This access
pattern is called
random access.
Only disk files support
random access. Each
disk file has a special
file pointer position.
Normally, the file
pointer is at the end of
the file, and any output
is appended to the end.
If we move the file
pointer to the middle
of the file, the output
overwrite what is
already there.
Then the next read
command starts
reading at the file point
location.
In Java, we use a
RandomAccessFile
object a access a file
and move a file
pointer.
We can open a file
either for reading only
(“r”) or for reading and
writing (“rw”).
To open a random-
access file, we supply
a file name and a string
to specify the open
mode.
Random Access File
To open the file
product “product.dat”
for both reading and
writing, we write:
RandomAccessFile f =
new
RandomAccessFile
(“product.dat”, "rw");
To move file pointer
to byte n from the
beginning of the file:
f.seek(n);
To find out the current
position of the file
pointer (counted from
the beginning of the
file):
n = f.getFilePointer();
To find out the number
of bytes in a file:
long filelength =
f.length();
When the new data is
longer than the current
data, the update will
overwrite the old data.
For example, if the
price is increased by
50, the new price has
more digits so it
overwrites the
newline symbol that
separates the fields.
We must give each
field a fixed size that is
sufficiently large.
This also makes it
easy to access the nth
data in a file, without
having to read in the
first n-1 data.
When storing numbers
in a file with fixed
record sizes, it is
easier to store them in
binary format, not in
text format.
The readInt and
writeInt methods read
and write integers as
four-byte quantities.
The readDouble and
writeDouble methods
process double-
precision floating-point
numbers as eight-byte
quantities.
To read:
int n = f.readInt();
double x =
f.readDouble();
char c = f.readChar();
To write:
f.writeInt(n);
f.writeDouble(x);
f.writeChar(c);
Example: See
Database.java
- Name: 30 characters
at two bytes each (60
bytes)
- Price: one double (8
bytes)
- Score: one int (4
bytes)
import
java.io.IOException;
import java.io.
RandomAccessFile;
public class Database
{ public static void
main(String[] args)
{ ConsoleReader
console = new
ConsoleReader
(System.in);
System.out.println
("Please enter the data
file name:");
String filename =
console.readLine();
try
{ RandomAccessFile
file
= new
RandomAccessFile
(filename, "rw");
long nrecord =
file.length() / RECORD_
SIZE;
boolean done = false;
while (!done)
{ System.out.println
("Please enter the
record to update (1 - "
+ nrecord + "), new
record (0), quit (-1)");
int pos =
console.readInt();
if (1 <= pos && pos <=
nrecord) // update
record
{ file.seek((pos - 1) *
RECORD_SIZE);
Product p =
readProduct(file);
System.out.println
("Found " + p.getName
()
+ " " + p.getPrice() + " "
+ p.getScore());
System.out.println
("Enter the new
price:");
double newPrice =
console.readDouble();
p.setPrice(newPrice);
file.seek((pos - 1) *
RECORD_SIZE);
writeProduct(file, p);
}
else if (pos == 0) // add
record
{ System.out.println
("Enter new product:");
String name =
console.readLine();
System.out.println
("Enter price:");
double price =
console.readDouble();
System.out.println
("Enter score:");
int score =
console.readInt();
Product p = new
Product(name, price,
score);
file.seek(nrecord *
RECORD_SIZE);
writeProduct(file, p);
nrecord++;
}
else if (pos == -1)
done = true;
}
file.close();
}
catch(IOException e)
{ System.out.println
("Input/Output
Exception " + e); }
}
public static String
readFixedString
(RandomAccessFile f,
int size) throws
IOException
{ String b = "";
for (int i = 0; i < size; i+
+)
b += f.readChar();
return b.trim();
}
public static void
writeFixedString
(RandomAccessFile f,
String s, int size)
throws IOException
{ if (s.length() <= size)
{ f.writeChars(s);
for (int i = s.length(); i <
size; i++)
f.writeChar(' ');
}
else
f.writeChars
(s.substring(0, size));
}
public static Product
readProduct
(RandomAccessFile f)
throws IOException
{ String name =
readFixedString(f,
NAME_SIZE);
double price =
f.readDouble();
int score = f.readInt();
return new Product
(name, price, score);
}
public static void
writeProduct
(RandomAccessFile f,
Product p) throws
IOException
{ writeFixedString(f,
p.getName(), NAME_
SIZE);
f.writeDouble
(p.getPrice());
f.writeInt(p.getScore()
);
}
public static final int
NAME_SIZE = 30;
public static final int
CHAR_SIZE = 2;
public static final int
INT_SIZE = 4;
public static final int
DOUBLE_SIZE = 8;
public static final int
RECORD_SIZE
= CHAR_SIZE * NAME_
SIZE + DOUBLE_SIZE +
INT_SIZE;
}
Object Streams
We can read and write
complete objects with
the ObjectInputStream
and
ObjectOutputStream
Objects are saved in
binary format; hence
we use streams, not
writers.
For example, we write
a Product object to a
file as follows:
Product p = … ;
ObjectOutputStream out
= new
ObjectOutputStream
(new FileOutputStream
(“products.dat”));
out.writeObject(p);
To read objects:
ObjectInputStream in =
new ObjectInputStream
(new FileInputStream
(“products.dat”));
Product p = (Product)
in.readObject( );
We can store a whole
bunch of objects in an
array or vector, or
inside another object,
and then save that
object:
Vector v = new Vector
();
// now add many
Product objects into v
out.writeObject(v);
We can read all of
objects:
Vector v = (Vector)
in.readObject( );
The objects must
belong to classes that
implement Serializable
interface:
class Product
implements
Serializable
{ … }
To save data to disk, it
is best to put them all
into one object and
save that object.
class Catalog
implements
Serializable
{ public void
addProduct(Product p)
{… }
private Vector
products;
}
class ProductEditor
{ public void saveFile()
{ ObjectOutputStream
out = …;
out.writeObject
(productCatalog);
out.close();
}
public void loadFile()
{ ObjectInputStream in
= … ;
productCatalog =
(Catalog)in.readObject
();
in.close();
}
private Catalog
productCatalog;
}
Friday, 1 July 2011
Subscribe to:
Post Comments (Atom)
0 comments:
Post a Comment