Blakes 21 Days Chapter 15 Document
Day 15, Working with Input and Output
WEEK III - Java Programming
Chapter |
Title |
Page |
15 |
Working with Input and Output |
419 |
16 |
Using Inner Classes and Closures |
449 |
17 |
Communicating Across the Internet |
469 |
18 |
Accessing Databases with JDBC 4.2 and Derby |
503 |
19 |
Reading and Writing RSS Feeds |
525 |
20 |
XML Web Services |
549 |
21 |
Writing Android Apps with Java |
569 |
- Top
- 419 Working with Input and Output
- 419 Many of the programs you create with Java need to interact with some kind of data source.
- Information can be stored on a computer in many ways, including files on a hard drive or DVD, pages on a website, and even bytes in the computer's memory.
- You might expect to need a different technique to handle each different storage device.
- Fortunately, that isn't the case.
- 419 In Java, information can be stored and retrieved using a communications system called streams,
- which are implemented in the java.io package and are enhanced by the java.nio.file package.
- 419 Today, you learn how to create input streams to read information and output streams to store information.
- You work with the following:
- Byte streams, which are used to handle bytes, integers, and other simple data types.
- Character streams, which handle text files and other text sources
- 419 You can deal with all data in the same way when you know how to work with an input stream,
- whether the information is coming from a disk, the Internet. or even another program.
- The same is true of using output streams to transmit data.
Introduction to Streams
- Top
- 420 In Java, all data is written and read using streams.
- Streams, like the bodies of water that share the same name, carry something from one place to another.
- 420 A stream is a path traveled by data in a program.
- An input stream sends data from a source into a program, and an output stream sends data from a program to a destination.
- 420 You will deal with two types of streams today:
- byte streams and character streams.
- Byte streams carry integers with values that range from 0 to 255.
- A diverse assortment of data can be expressed in byte format, including numeric data, executable programs, Internet communications, and bytecode,
- the class files run by a Java Virtual Machine (JVM).
- 420 In fact, every kind of data imaginable can be expressed using either individual bytes or a series of bytes combined.
- 420 Character streams are a specialized type of byte stream that handles only textual data.
- They're distinguished from byte streams because Java's character set supports Unicode,
- a standard that includes many more characters than could be expressed easily using bytes.
- 420 Any kind of data that involves text should use character streams, including text files, web pages, and other common types of text.
Using a Stream
- Top
- 420 The procedure for using either a byte stream or character stream in Java is largely the same.
- Before you start working with the specifics of the java.io and java.nio.file classes,
- it's useful to walk thru the process of creating and using streams.
- 420 For an input stream, the first step is to create an object associated with the data source.
- For example, if the source is a file on your hard drive, a FileInputStream object could be associated with this file.
- 420 After you have a stream object, you can read information from that stream by using on of the object's methods.
- FileInputStream includes a read() method that returns a byte read from the file.
- 420 When you're finished reading information from the stream, you call the close() method to indicate that you're finished using the stream.
- 420 For an output stream, you begin by creating an object associated with the data's destination.
- One such object can be created from the BufferedWriter class, which represents an efficient way to create text files.
- 421 The write() method is the simplest way to send information to the output stream's destination.
- For instance, a BufferedWriter write() method can send individual characters to an input stream.
- 421 As with input streams, the close() method is called on an output stream when you have no more information to send.
Filtering a Stream
- Top
- 421 The simplest way to use a stream is to create it and then call its methods to send or receive data,
- depending on whether it's an output stream or input stream.
- 421 Many of the classes you will work with today achieve more sophisticated results
- when a filter is associated with a stream before reading or writing any data.
- 421 A filter is a type of stream that modifies how an existing stream is handled.
- The dam regulates the flow of water from the points upstream to the points downstream.
- The dam is a type of filter.
- Remove it, and the water would flow in a less-controlled fashion.
- 421 The procedure for using a filter on a stream is as follows:
- Create a stream associated with a data source or data transmission.
- Associate a filter with that stream.
- Read data from or write data to the filter rather than the original stream.
- 421 The methods you call on a filter are the same methods you would call on a stream.
- There are read() and write() methods, just as there would be on an unfiltered stream.
- 421 You even can associate a filter with another filter, so the following path for information is possible:
- An input stream associated with a text file is filtered thru a no-profanity filter.
- Finally, it is sent to its destination - a human being who wants to read it.
- 421 If this is confusing in the abstract, you will have the opportunities to see the process in practice in the following sections.
Handling Exceptions
- Top
- 421 Several exceptions in the java.io package might occur when you are working with files and streams.
- Two common ones are FileNotFoundException and EOFException.
- 421 A FileNotFoundException occurs when you try to create a stream or file object using a file that couldn't be located.
- 422 An EOFException indicates that the end of a file has been reached unexpectedly as data was being read from the file thru an input stream.
- 422 These exceptions are subclasses of IOException.
- One way to deal with all of them is to enclose all the input and output statements
- in a try-catch block that catches IOException objects.
- Call the exception's toString() or getMessage() methods in the catch block to find out more about the problem.
Byte Streams
- Top
- 422 All byte streams are a subclass of either InputStream or OutputStream.
- These classes are abstract, so you cannot create a stream by creating objects of these classes directly.
- Instead, you create streams thru one of their subclasses, such as the following:
- FileInputStream or FileOutputStream are byte streams stored in files on disk, CD, or other storage devices.
- DataInputStream or DataOutputStream are a filtered byte stream from which data such as integers and floating-point numbers can be read.
- 422 InputStream is the superclass of all input streams.
File Streams
- Top
- 422 The byte streams you'll work with most often are likely to be file streams.
- They are used to exchange data with files on your disk drives, CDs, or other storage devices you can refer to by using a folder path and file name.
- 422 You can send bytes to a file output stream and receive bytes from a file input stream.
File Input Streams
- Top
- 422 A file input stream can be created with the FileInputStream(String) constructor.
- The String argument should be the filename.
- You can include a path reference with the filename, which enables the file to be in a different folder from the class loading it.
- The following statement creates a file input stream from the file scores.dat:
FileInputStream fis = new FileInputStream("scores.dat");
- 422 Path references can be indicated in a manner specific to a platform, such as this example to read a file on a Windows system:
FileInputStream f1 = new FileInputStream("C:\\data\\calendar.txt");
- Top
- 423 NOTE: Because Java uses backslash characters in escape codes, the code \\ must be used in place of \ in path references in Windows.
- 423 Here's a Linux example:
FileInputStream f2 = new FileInputStream("/data/calendar.txt");
- 423 A better way to refer to paths is to use the class variable separator in the File class, which works on any operating system:
char sep = File.separator;
FileInputStream f2 = FileInputStream(sep + "data"
+ sep + "calendar.txt");
- 423 After you create a file input stream, you can read bytes from the stream by calling its read() method.
- This method returns an integer containing the next byte in the stream.
- This method returns -1, which is not a possible byte value, when the end of the file stream has been reached.
- Top
- 423 To read more than one byte of data from the stream, call its read(byte[], int, int) method.
- The arguments to this method are as follows:
- A byte array where the data will be stored
- The element inside the array where the data's first byte should be stored
- The number of bytes to read
- 423 Unlike the other read() method, this does not return data from the stream.
- Instead, it returns either an integer that represents the number of bytes read or -1 if no bytes were read before the end of the stream was reached.
- 423 The following statements use a while loop to read the data in a FileInputStream object called diskfile:
int newByte = 0;
while (newByte != -1) {
newByte = diskfile.read();
System.out.print(newByte + " ");
}
- Top
- 423 This loop reads the entire file referenced by diskfile one byte at a time and displays each byte, followed by a space character.
- It also displays -1 when the end of the file is reached; you could guard against this easily with an if statement.
First Program
- Top
- 424 The ByteReader application, shown in Listing 15.1, uses a similar technique to read a file input stream.
- The input stream's close() method is used to close the stream after the last byte in the file is read.
- Always close streams when you no longer need them; doing so frees system resources.
- Create the ByteReader class in the com.java21days package as an empty Java file in NetBeans.
Listing 15.1
- Top
- page 424
package com.java21days;
import java.io.*;
public class ByteReader {
public static void main(String[] arguments) {
try {
FileInputStream file = new
FileInputStream("save.gif");
) }
boolean eof = false;
int count = 0;
while (!eof) {
int input = file.read();
System.out.print(input + " ");
if (input == -1)
eof = true;
else
count++;
}
file.close();
System.out.println("\nBytes read: " + count);
} catch (IOException e) {
System.out.println("Error -- " + e.toString());
}
}
}
The Explanation
- Top
- 424 This application reads the byte data from the save.gif file in the main folder of the Java21 project.
- That file was used during Day 10, Building a Swing Interface."
- 424 When you run the program, each byte in save.gif is displayed, followed by a count of the total number of bytes.
- Figure 15.1 shows the output.
Figure 15.1 - Reading byte data from a file. - goes here
File Output Streams
- Top
- 425 A file output stream can be created with the FileOutputStream(String) constructor.
- The usage is the same as with the FileInputStream(String) constructor,
- so you can specify a path along with a filename.
- 425 You have to be careful when specifying the file associated with an output stream.
- If it's the same as an existing file, the original is wiped out when you start writing data to the stream.
- 425 You can create a file output stream that appends data after the end of an existing file
- with the FileOutputStream(String, boolean) constructor.
- The string specifies the file, and the Boolean argument should equal true to append data instead of overwriting existing data.
- 425 The file output stream's write(int) method is used to write bytes to the stream.
- After the last byte has been written to the file, the stream's close() method closes the stream.
- Top
- 425 To write more than one byte, you can use the write(byte[], int, int) method.
- This works in a manner similar to the read(byte[], int, int) method described previously.
- The arguments to this method are the byte array containing the bytes to output, the starting point in the array, and the number of bytes to write.
Second Program
- Top
- 425 The ByteWriter application, shown in Listing 15.2, writes an integer array to a file output stream.
- Create it in NetBeans in the com.java21days package.
Listing 15.2
- Top
- page 426
package com.java21days;
import java.io.*;
public class ByteWriter {
public static void main(String[] arguments) {
int[] data = ( 71, 73, 70, 56, 57, 97, 13, 0, 12, 0, 145,
0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 44,
0, 0, 0, 0, 13, 0, 12, 0, 0, 2, 38, 132, 45, 121, 11,
25, 175, 150, 120, 20, 162, 132, 51, 110, 106, 239, 22,
8, 160, 56, 137, 96, 72, 77, 33, 130, 86, 37, 219, 182,
230, 137, 89, 82, 181, 50, 220, 103, 20, 0, 59 );
try (FileOutputStream file = new
int input = file.read();
FileOutputStream("pic.gif")) {
for (int i= 0; i < data.length; i++) {
file.write(data[i]);
}
file.close();
} catch (IOException e) {
System.out.println("Error -- " + e.toString());
}
}
}
The Explanation
- Top
- 426 The following things take place in this program:
- Lines 7 - 12 create an integer array called data and fill it with elements.
- Lines 13 - 14 create a file output stream with the filename pic.gif in the main project folder in NetBeans.
- Lines 16 - 18 use a for loop to cycle thru the data array and write each element to the file stream.
- Line 19 closes the file output stream.
- 426 The FileOutputStream object is created inside the parentheses of the try statement
- to make sure its resources are freed up when the block finishes executing, even in case of error.
- Top
- 426 After you run this program, you can display the pic.gif file in any web browser or graphics-editing tool.
- It's a small image file in GIF format, as shown in Figure 15.2
Figure 15.2 - The pic.gif file (enlarged). - goes here
Filtering a Stream
- Top
- 427 Filtered streams are streams that modify the information sent thru an existing stream.
- They are created using the subclasses FilterInputStream and FilterOutputStream.
- 427 These classes do not handle any filtering operations themselves.
- Instead, they have subclasses, such as BufferInputStream and DataOutputStream, which handle specific types of filtering.
Byte Filters
- Top
- 427 Information is delivered more quickly if it can be sent in large chunks, even if those chunks are received faster than they can be handled.
- 427 For example, consider which of the following book-reading techniques is faster:
- A friend lends you a book, and you read it.
- A friend lends you a book one page at a time and doesn't give you a new page until you have finished the previous one.
- 427 Obviously, the first technique is faster and more efficient.
- The same benefits are true of buffered streams in Java.
- 427 A buffer is a storage place where data can be kept before it is needed by a program that reads or writes that data.
- By using a buffer, you can get data without always going back to the original source of the data.
- 427 Buffers are essential when reading extremely large files.
- Without them, the data from the file could take up all of a Java virtual machine's memory.
Buffered Streams
- Top
- 427 A buffered input stream fills a buffer with data that hasn't been handled yet.
- When a program needs this data, it looks to the buffer before going to the original stream source.
- 427 Buffered byte streams use the BufferedInputStream and BufferedOutputStream classes.
- 428 A buffered input stream is created using one of the following constructors:
- BufferedInputStream(InputStream) creates a buffered input stream for the specified InputStream object.
- BufferedInputStream(InputStream, int) creates the specified InputStream buffered stream with a buffer of the size int.
- 428 The simplest way to read data from a buffered input stream is to call its read() method with no arguments.
- This action normally returns an integer from 0 to 255 representing the next byte in the stream.
- If the end of the stream has been reached an no byte is available, -1 is returned.
- 428 You also can use the read(byte[], int, int) method available for other input streams, which loads stream data into a byte array.
- 428 A buffered output stream is created using one of these two constructors:
- BufferedOutputStream(OutputStream) creates a buffered output stream for the specified OutputStream object.
- BufferedOutputStream(OutputStream, int) creates the specified OutputStream buffered stream with a buffer of size int.
- 428 The output stream's write(int) method can be used to send a single byte to the stream,
- and the write(byte[], int, int) method writes multiple bytes from the specified byte array.
- The arguments to this method are the byte array, array starting point, and number of bytes to write.
- Top
- 428 NOTE: Although the write() method takes an integer as input, the value should be from 0 to 255.
- If you specify a number higher than 255, it is stored as the remainder of the number divided by 256.
- You can test this when running the project you will create later today.
- 428 When data is directed to a buffered stream, it is not output to its destination until the stream fills or the buffered stream's flush() method is called.
Third Program
- Top
- 428 The next project, the BufferDemo application, writes a series of bytes to a buffered output stream associated with a text file.
- The first and last integers in the series are specified as two arguments.
- 429 After writing to the text file, BufferDemo creates a buffered input stream from the file and reads the bytes back in.
- Listing 15.3 contains the source code.
- Put this class in the com.java21days package when creating it in NetBeans.
- Listing 15.3 The Full Text of BufferDemo.java
- pages 429 - 430
package com.java21days;
import java.io.*;
public class BufferDemo {
public static void main(String[] arguments) {
int start = 0;
int finish = 255;
if (arguments.length > 1) {
start = Integer.parseInt(arguments[0]);
finish = Integer.parseInt(arguments[1]);
} else if (arguments.length > 0) {
start = Integer.parseInt(arguments[0]);
}
ArgStream as = new ArgStream(start, finish);
System.out.println("\nWriting: ");
boolean success = as.writeStream();
System.out.println("\nReading: ");
boolean readSuccess = as.readStream();
}
}
class ArgStream {
int start = 0;
int finish = 255;
ArgStream(int st, int fin) {
start = st;
finish = fin;
}
boolean writeStream() {
try (FileOutputStream file = new
FileOutputStream("numbers.dat");
BufferedOutputStream buff = new
BufferedOutputStream(file)) {
for (int out = start; out <= finish; out++) {
buff.write(out);
System.out.print(" " + out);
}
buff.close();
return true;
} catch (IOException e) {
System.out.println("Exception: " + e.getMessage());
return false;
}
}
boolean readStream() {
try (FileInputStream file = new
FileInputStream("numbers.dat");
BufferedInputStream buff = new
BufferedInputStream(file)) {
int in;
do {
in = buff.read();
if (in != -1) {
System.out.print(" " + in);
}
} while (in != -1);
System.out.println();
buff.close();
return true;
} catch (IOException e) {
System.out.println("Exception: " + e.getMessage());
return false;
}
}
}
The Explanation
- Top
- 430 This program's output depends on the two arguments specified when it was run.
- If you use 3 and 19, the output in Figure 15.3 is shown.
Figure 15.3 - Reading and writing buffered streams. - goes here
- Top
- 430 It also can be run without arguments, using 1 and 255 as default values.
- 430 This application consists of two classes: BufferDemo and a helper class called ArgStream.
- BufferDemo gets the two arguments' values, if they are provided, and uses them in the ArgStream() constructor.
- 431 The WriteStream() method of ArgStream is called in line 17 to write the series of bytes to a buffered output stream,
- and the readStream() method is called in line 19 to read back those bytes.
- 431 Even though they are moving data in two directions, the writeStream() and readStream() methods are substantially the same.
- They take the following format:
- The file name, numbers.dat, is used to create a file input or output stream.
- The file stream is used to create a buffered input or output stream.
- The buffered stream write() method is used to send data, or the read() method is used to receive data.
- The buffered stream is closed.
- 431 Because file streams and buffered streams throw IOException objects if an error occurs,
- all operations involving the streams are enclosed in a try-catch block for this exception.
- Top
- 431 TIP: The Boolean return values in writeStream() and readStram()
- indicate whether the stream operation was completed successfully.
- They aren't used in this program, but it's good practice to let callers of these methods know if something goes wrong.
- When the value is false, the operation could be attempted again.
Console Input Streams
- Top
- 431 One of the things many experienced programmers miss when they begin learning Java
- is the ability to read textual or numeric input from the console while running an application.
- No input method is comparable to the output methods System.out.print() and System.out.println().
- 431 Now that you can work with buffered input streams, you can put them to use receiving console input.
- 431 The System class, part of the java.lang package, has a class variable called in that is an InputStream object.
- This object receives input from the keyboard thru the stream.
- 431 You can work with this stream as you would any other input stream.
- The following statement creates a new buffered input stream associated with the System.in input stream:
- BufferedInputStream command = new BufferedInputStream(System.in);
Fourth Program
- Top
- 432 The next project, the ConsoleInput class, contains a class method you can use to receive console input in any of your Java applications.
- Enter the code shown in Listing 15.4 in NetBeans, making sure to put it in the package com.java21days.
Listing 15.4
- Top
- page 432
package com.java21days;
import java.io.*;
public class ConsoleInput {
public static String readLine() {
StringBuilder response = new StringBuilder();
try {BufferedInputStream buff = new
BufferedInputStream file = new
FileInputStream(System.in)) {
int in;
char inChar;
do {
in = buff.read();
inChar = (char) in;
if ((in != -1) & (in != '\n') & (in != '\r')) {
response.append(inChar);
}
} while ((in != -1) & (inChar != '\n') & (in != '\r'));
buff.close();
return response.toString();
} catch (IOException e) {
System.out.println("Exception: " + e.getMessage());
return null;
}
}
public static void main(String[] arguments) {
System.out.print("\nWhat is your name? ");
String input = ConsoleInput.readLine();
System.out.println("\nHello, " + input);
}
}
The Explanation
- Top
- 432 The ConsoleInput class includes a main() method that demonstrates how it can be used.
- When you compile and run it an an application, the output should resemble Figure 15.4
- 432 ConsoleInput reads user input thru a buffered input stream using the stream's read() method,
- which returns -1 when the end of input has been reached.
- This occurs when the user presses the Enter key, a carriage return (character '\r', or a newline (character '\n').
Figure 15.4 - Reading keyboard input from the console window. - goes here
Data Streams
- Top
- 433 If you need to work with data that isn't represented as bytes or characters, you can use data input and data output streams.
- These streams filter an existing byte stream so that each of the following primitive types can be directly read from or written to the stream:
- boolean, byte, double, float, int, long, and short.
- 433 A data input stream is created with the DataInputStream(InputStream) constructor.
- The argument should be an existing input stream such as a buffered input stream or a file input stream.
- 433 A data output stream requires the DataOutputStream(OutputStream) constructor, which indicates the asociated output stream.
- 433 The following read and write methods apply to data input abd output streams, respectfully:
- readBoolean(), writeBoolean(boolean)
- readByte(), writeByte(integer)
- readDouble(), writeDouble(double)
- readFloat(), writeFloat(float)
- readInt(), writeInt(int)
- readLong(), writeLong(long)
- readShort(), writeShort(int)
- 433 Each input method returns the primitive data type indicated by the method's name.
- For example, the readFloat() method returns a float value.
- 433 There also are readUnsignedByte() and readUnsignedShort() methods that read in unsigned byte and short values.
- Java doesn't support these data types, so they are returned as int values.
- reserve
- Top
- 434 NOTE: Unsigned bytes have values ranging from 0 to 255.
- This differs from Java's byte variable type, which ranges from -128 to 127.
- Along the same lines, as unsigned short value ranges from 0 to 65,535, instead of the -32,768 to 32, 767 range supported by Java's short type.
- 434 A data input stream's different read methods do not all return a value that can be used to indicate that the end of the stream has been reached.
- 434 As an alternative, you can wait for an EOFException (end-of-file exception) to be thrown when a read method reaches the end of a stream.
- The loop that reads the data can be enclosed in a try block, and the associated catch statement should handle only EOFException objects.
- You can call close() on the stream and take care of other cleanup tasks inside the catch block.
Fifth Program
- Top
- 434 This is demonstrated in the next project.
- Listings 15.5 and 15.6 contain two programs that use data streams.
- The PrimeWriter application writes the first 400 prime numbers as integers to a file called 400primes.dat.
- The PrimeReader application reads the integers from this file and displays them.
- Both classes are in the com.java21days package.
Listing 15.5 The Full Text of PrimeWriter.java
- Top
- page 434 - 435
package com.java21days;
import java.io.*;
public class PrimeWriter {
public static void main(String[] arguments) {
int[] primes = new int[400];
int numPrimes = 0;
// candidate: the number that might be prime
int candidate = 2;
while (numPrimes < 400) {
if (isPrime(candidate)) {
primes[numPrimes] = candidate;
numPrimes++;
}
candidate++;
}
try (
// Write output to disk
FileOutputStream file = new
FileOutputStream("400primes.dat");
BufferedOutputStream buff = new
BufferedOutputStream(file);
DataOutputStream data = new
DataOutputStream(buff);
) {
for (int i = 0; i < 400; i++)
data.writeInt(primes[i]);
data.close();
} catch (IOException e) {
System.out.println("Error -- " + e.toString());
}
}
public static boolean isPrime(int checkNumber) {
double root = Math.sqrt(checkNumber);
for (int i = 2; i <= root; i++) {
if (checkNumber % i == 0)
return false;
}
return true;
}
}
Sixth Program - Listing 15.6 The Full Text of PrimeReader.java
- Top
- page 435
package com.java21days;
import java.io.*;
public class PrimeReader {
public static void main(String[] arguments) {
try (FileInputStream file = new
FileInputStream("400primes.dat");
BufferedInputStream buff = new
BufferedInputStream(file);
DataInputStream data = new
DataInputStream(buff)) {
try {
while (true) {
int in = data.readInt();
System.out.println(in + " ");
}
} catch (EOFException eof) {
buff.close();
}
} catch (IOException e) {
System.out.println("Error -- " + e.toString());
}
}
}
The Explanations
- Top
- 436 Most of the PrimeWriter application is taken up with logic to find the first 400 prime numbers.
- After you have an integer array containing the first 400 primes, it is written to a data output stream in Listing 15.5 in lines 19 - 34.
- 436 This application is an example of using more than one filter on a stream.
- The stream is developed in a three-step process:
- A file output stream associated with a file called 400primes.dat is created.
- A new buffered output stream is associated with the file stream.
- A new data output stream is associated with the buffered stream.
- 436 The writeInt() method of the data stream is used to write the primes to the file.
- 436 The PrimeReader application is simpler because it doesn't need to do anything regarding prime numbers.
- It just reads integers from a file using a data input stream.
- 436 Lines 7 - 12 of the PrimeReader are nearly identical to statements in the PrimeWriter application, except that input classes are used instead of output classes.
- 436 The try-catch block that handles EOFException objects is in lines 14 - 21 of Listing 15.6.
- The work of loading the data takes place inside the try-with resources block, which was introduced during Day 7, "Exceptions and Threads."
- This approach ensures the input stream objects will be closed properly when no longer needed.
- 436 The while(true) statement creates an endless loop.
- This isn't a problem; an EOFExeception automatically occurs when the end of the stream is encountered at some point as the data stream is being read.
- The readInt() method in line 16 of Listing 15.6 reads integers from the stream.
- Top
- 436 The last several lines of the PrimeReader application's output are shown in Figure 15.5.
Figure 15.5 - Reading prime numbers written to a file as integers. - goes here.
Character Streams
- Top
- 437 After you know how to handle byte streams, you have most of the skills needed to handle character streams as well.
- Character streams are used to work with any text represented by the ASCII character set or Unicode, an international character set that includes ASCII.
- 437 Examples of files that you can work with thru a character stream are plain text files, Web pages, and Java source files.
- 437 The classes used to read and write these streams are all subclasses of Reader and Writer.
- These should be used for all text input instead of dealing directly with byte streams.
Reading Text Files
- Top
- 437 FileReader is the main class used when reading character streams from a file.
- This class inherits from InputStreamReader, which reads a byte stream and converts the bytes into integer values that represent Unicode characters.
- 437 A character input stream is associated with a file using the FileReader(String) constructor.
- The string indicates the file, and it can contain path folder references in addition to a filename.
- 437 The following statement creates a new FileReader called look and associates it with a text file called index.txt:
- FileReader look = new FileReader("index.txt");
- 437 After you have a file reader, you can call the following methods on it to read characters from the file:
- read() returns the next character on the stream as an integer.
- read(char[], int, int) reads characters into the specified character array with the indicated starting point and number of characters read.
- 437 The second method works like similar methods for the byte input stream classes.
- Instead of returning the next character, it returns either the number of characters that were read or -1
- if no characters were read before the end of the stream was reached.
- Top
- 437 The following method loads a text file using the FileReader object text and displays its characters:
FileReader text = new FileReader("readme.txt");
int inByte;
do {
inByte = text.read();
if (inByte != -1) {
System.out.print( (char) inByte );
}
}
while (inByte != -1)
System.out.println("");
text.close();
- 438 Because a character stream's read() method returns an integer,
- you must cast this to a character before displaying it, storing it in an array, or using it to form a string.
- Every character has a numeric code that represents its position in the Unicode character set.
- The integer read from the stream is this numeric code.
- 438 If you want to read an entire line of text at a time instead of reading a file character by character,
- you can use the BufferedReader class in conjunction with a FileReader.
- 438 The BufferedReader class reads a character input stream and buffers it for better efficiency.
- You must have an existing Reader object of some kind to create a buffered version.
- The following constructors can be used to create a BufferedReader:
- BufferedReader(Reader) creates a buffered character stream associated with the specified Reader object, such as FileReader.
- BufferedReader(Reader, int) creates a buffered character stream associated with the specified Reader and with a buffer of size int.
- 438 A buffered character stream can be read using the read() and read(char[], int, int) methods described for FileReader.
- You can read a line of text using the readLine() method.
- 438 The readLine() method returns a String object containing the next line of text on the stream,
- not including the character or characters that represent the end of a line.
- If the end of the stream is reached, the value of the string returned equals null.
- 438 An end-of-line is indicated by any of the following:
- A newline character ('\n')
- A carriage return character ('\r')
- A crriage return followed by a newline ("\n\r")
Seventh Program - Listing 15.7 The Full Text of SourceReader.java
- Top
- 438 The project contained in Listing 15.7 is a Java application, SourceReader, that reads its own source file thru a buffered character stream.
- Create it in the com.java21days package.
- Top
- page 439
package com.java21days;
import java.io.*;
public class SourceReader {
public static void main(String[] arguments) {
try (
FileReader file = new
FileReader("SourceReader.java");
BufferedReader buff = new
BufferedReader(file)) {
boolean eof = false;
while (!eof) {
String line = buff.readLine();
if (line == null) {
eof = true;
} else {
System.out.println(line);
}
}
buff.close();
} catch (IOException e) {
System.out.println("Error -- " + e.toString());
}
}
}
The Explanation
- Top
- 439 Much of this program is comparable to projects created earlier today:
- Lines 8 - 9: An input source is created: the FileReader object associated with the file SourceReader.java.
- Lines 10 - 11: A buffered filter is associated with that input source: the BufferedReader object buff.
- Lines 13 - 21: A readLine() method is used inside a while loop to read the text file one line at a time.
- The loop ends when the method returns the value of null.
- 439 Before you run the program, make a copy of SourceReader.java in the Java21 project's root folder.
- To do this, follow these steps:
- In the Projects pane, right-click SourceReader.java and choose Copy.
- The file is copied to the clipboard.
- Click the Files pane to bring it to the front.
- Right-click Java21 at the top of the Files pane, then choose Paste.
- 440 A copy will appear in that folder.
- Run the program to see the SourceReader application's output, the text file SourceReader.java.
Writing Text Files
- Top
- 440 The FileWriter class is used to write a character stream to a file.
- It's a subclass of OutputStreamWriter, which has behavior to convert Unicode character codes to bytes.
- 440 There are two FileWriter constructors: FileWriter(String) and FileWriter(String, boolean).
- The string indicates the name of the file that the character stream will be directed into, which can include a folder path.
- The optional Boolean argument should equal true if the file is to be appended to an existing text file.
- As with other stream-writing classes, you must be careful not to accidentally overwrite an existing file when you're appending data.
- 440 Three methods of FileWriter can be used to write data to a stream:
- write(int) writes a character.
- write(char[], int, int) writes characters from the specified character array with the indicated starting point and number of characters written.
- write(String, int, int) writes characters from the specified string with the indicated starting point and number of characters written.
- Top
- 440 The following example writes a character stream to a file using the FileWriter class and the write(int) method:
-
FileWriter letters = new FileWriter("alphabet.txt");
for (int i = 65; i < 91; i++)
letters.write( (char) i );
letters.close();
- 440 The close() method is used to close the stream after all characters have been sent to the destination file.
- The following is the alphabet.txt file produced by this code:
- ABCDEFGHIJKLMNOPQRSTUVWXYZ
- 440 The BufferedWriter class can be used to write a buffered stream.
- This class's objects are created with the BufferedWriter(Writer) or BufferedWriter(Writer, int) constructors.
- The Writer argument can be any of the character output stream classes, such as FileWriter.
- The optional second argument is an integer indicating the size of the buffer to use.
- 441 BufferedWriter has the same three output methods as FileWriter:
- write(int), write(char[], int, int), and write(String, int, int).
- 441 Another useful output method is newLine(), which sends the preferred end-of-line character (or characters) for the platform being used to run the program.
- 441 TIP: The different end-of-line markers can create conversion hassles when files are transferred from one operating system to another,
- such as when a Windows 10 user uploads a file from the web server that's running the Linux operating system.
- Using newLine() instead of a literal (such as '/n') makes your program more user-friendly across different platforms.
- Top
- 441 The close() method is called to close the buffered character stream and make sure that all buffered data is sent to the stream's destination.
Files and Paths
- Top
- 441 In all the examples thus far, a string has been used to refer to the file that's involved in a stream operation.
- This often is sufficient for a program that uses files and streams,
- but if you want to copy or rename files or handle other tasks, you can use a Path object from the java.nio.file package.
- 441 Path represents a file or folder reference.
- The following statement gets a path matching the specified string:
- Path source = FileSystems.getDefault().getPath("essay.txt");
- 441 This is a two-step process.
- First, a class method of the FileSystems class is called.
- The getDefault() method returns a FileSystem object that represents the computer's way of storing files.
- Both of these classes also are in the java.nio.file package.
- 441 As soon as you have that FileSystem object, its getPath(String) method returns a Path object matching that specified file or folder reference.
- 441 A File object can be created from a Path by calling the toFile() method of the latter class, as in this statement:
- File sourceFile = source.toFile();
- 442 A Path object can be created from a File by calling its toPath() method.
- 442 You can call several class methods of the Files class in the java.nio.file package when working with files.
- 442 The move(Path, Path) class method renames a file from the first path argument to the second.
- 442 The delete(Path) class method deletes that file.
- 442 Just like any file-handling operation, these methods must be handled with care to avoid deleting the wrong files and folders or wiping out data.
- Top
- 442 These methods throw a SecurityException if the program does not have the security to perform the file operation in question,
- a NoSuchFileException if the paths do not exist, and an IOException for other IO errors.
- If you try to delete a folder that is not empty, a NoSuchFileException exception occurs.
- Therefore, these exceptions need to be dealt with thru a try-catch block or a throws clause in a method declaration.
- reserve
Eigth Program - Listing 15.8 The Full Text of AllCapsDemo.java
- Top
- 442 The AllCapsDemo application shown in Listing 15.8 converts all the text in a file to uppercase characters.
- The file is pulled in using a buffered input stream, and one character is read at a time.
- After the character is converted to uppercase, it is sent to a temporary file using a buffered output stream.
- File objects are used instead of strings to indicate the files involved, which makes it possible to rename and delete files as needed.
- In NetBeans, create an empty Java file called AllCapsDemo in the com.java21days package.
package com.java21days;
import java.io.*;
import java.nio.file.*;
public class AllCapsDemo {
public static void main(String[] arguments) {
if (arguments.length < 1) {
System.out.println("You must specify a filename");
System.exit(-1);
}
AllCaps cap = new AllCaps(arguments[0]);
cap.convert();
}
}
class AllCaps {
String sourceName;
AllCaps(String sourceArg) {
sourceName = sourceArg;
}
void convert() {
try {
// Create file objects
FileSystem fs = FileSystems.getDefault();
Path source = fs.getPath(sourceName);
Path temp = fs.getPath("tmp_" + sourceName);
// Create input stream
FileReader fr = new FileReader(source.toFile());
BufferedReader in = new BufferedReader(fr);
// Create output stream
FileWriter fw = new FileWriter(temp.toFile());
BufferedWriter out = new
BufferedWriter(fw);
boolean eof = false;
int inChar;
do {
inChar = in.read();
if (inChar != -1) {
char outChar = Character.toUpperCase(
(char) inChar);
out.write(outChar);
} else
eof = true;
} while (!eof);
in.close();
out.close();
Files.delete(source);
Files.move(temp, source);
} catch (IOException|SecurityException se) {
System.out.println("Error -- " + se.toString());
}
}
}
The Explanation
- Top
- 443 Before running the program, you need a text file that can be converted to all capital letters.
- One option is to make a copy of AllCapsDemo.java and give it a name like TempFile.java.
- This file should be stored in the root project folder in NetBeans and specified as a command-line argument.
- 444 This program does not produce any output.
- Load the converted file into a text editor to see the results of the application.
Summary
- Top
- 444 Today you learned how to work with streams in two directions,
- pulling data into a program over an input stream and sending data from a program using an output stream.
- 444 You used character streams to handle text and byte streams for any other kind of data.
- Filters were associated with streams to alter how information was delivered thru a stream, or to alter the information itself.
- 444 In addition to these classes, java.io offers other types of streams you might want to explore.
- Piped streams are useful when communicating data among different threads,
- and byte array streams can connect programs to a computer's memory.
- 444 Because the stream classes in Java are so closely coordinated, you already possess most of the knowledge you need to use these other types of steams.
- The constructors, read methods, and write methods are largely identical.
- 444 Streams are a powerful way to extend the functionality of your Java programs because they offer a connection to any kind of data you might want to work with.
- 444 Tommorrow, you will use streams to read and write Java objects.
Q & A
- Top
- Q The C program that I use creates a file of integers and other data. Can I read this using a Java program ?
- A You can, but one thing you have to consider is whether your C program represents integers in the same manner that a Java prgram represents them.
- As you might recall, all data can be represented as an individual byte or a series of bytes.
- An integer is represented in Java using 4 bytes arranged in what is called big-endian order.
- You can determine the integer value by combining the bytes from left to right.
- A C program implemented on an Intel PC is likely to represent integers in little-endian order, which means that the bytes must be arranged from right to left to determine the result.
- You might have to learn about advanced techniques, such as bit shifting, to use a data file created with a programming language other than Java.
- Q Can relative paths be used when specifying the name of a file in Java ?
- A Relative paths are determined according to the current user folder, which is stored in the system properties user.dir.
- You can find out the full path to this folder by using the System class in the main java.lang package, which does not need to be imported.
- Call the System class getProperty(String) method with the name of the property to retrieve, as in this example:
- String userFolder = System.getProperty("user.dir");
- The method returns the path as a string.
- Q The FileWriter class has a write(int) method that's used to send a character to a file. Shouldn't this be write(char) ?
- A The char and int data types are interchangeable in many ways; you can use an int in a method that expects a char, and vice versa.
- This is possible because each character is represented by a numeric code that is an integer value.
- When you call the write() method with an int, it outputs the character associated with that integer value.
- When calling the write() method, you can cast an int value to a char to ensure that it's being used as you intended.
Quiz - Questions
- Top | Review today's material by taking this three-question quiz.
- What happens when you create a FileOutputStream using a reference to an existing file ?
- An exception is thrown.
- The data you write to the stream is appended to the existing file.
- The existing file is replaced with the data you write to the stream.
- What two primitive types are interchangable when you're working with streams ?
- byte and boolean
- char and int
- byte and char
- In Java, what is the maximum value of a byte variable and the maximum value of an unsigned byte in a stream ?
- Both are 255
- Both are 127
- 127 for a byte variable and 255 for an unsigned byte
Answers
- Top | Note A is 1, B is 2, and C is 3
- C. That's one of the things to look out for when using output streams, you can easily wipe out existing files. Constructors can use a Boolean vale to append data to a file instead of replacing the entire thing.
- B. Because Java represents a char internally as an integer value, you often can use the two interchangeably in method calls and other statements.
- C. The byte primitive data type has values ranging from -128 to 127, whereas an unsigned byte can range from 0 to 255.
Certification Practice
- Top
- 092 The following question is the kind of thing you could expect to be asked on a Java programming certification test. Answer it without looking at today's material or using the Java compiler to test the code.
- The answer is available on the book's website at www.java21days.com
- Given:
import java.io.*;
public class Unknown {
public static void main(String[] arguments) {
String command = "";
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
try {
command = br.readLine();
}
catch (IOException e) { }
}
}
- Will this program successfully store a line of console input in the String object named command ?
- Yes.
- No, because a buffered input stream is required to read console input.
- No, because it won't compile successfully.
- No, because it reads more than one line of console input.
Exercise
- Top
- To extend your knowledge of the subjects covered today, try the following exercises:
- Write a modified version of the HexReader program from Day 7, that reads two-digit hexadecimal sequences from a text file and displays their decimal equivalents.
- Write a program that reads a file to determine the number of bytes it contains and then overwrites all those bytes with 0s.
- (For obvious reasons, don't test this program on any file you intend to keep,
- because the file's data will be wiped out.)