Java学习笔记(17) Binary I/O

来源:互联网 发布:stussy淘宝哪家正 编辑:程序博客网 时间:2024/06/04 01:17

17.1 Introduction

1Files can be classified as eithertext or binary.(文件可以被归类为文本文件和二进制文件)

2A file that can be processed (read, created, or modified) using a text editor such as Notepad on Windows or vi on UNIX is called atext file.

3All the other files are calledbinary files.

4For example, Java source programs are text files and can be read by a text editor, butJava class files are binary files and are read by the JVM.

5Java offers many classes for performing file input and output. These can be categorized as text I/O classesand binary I/O classes.

 

17.2 How Is Text I/O Handled in Java?

Text data are read using the Scannerclass and written using thePrintWriter class.

An input class contains the methods to read data, and an output classcontains the methods to write data. PrintWriteris an example of an output class, and Scanneris an example of an input class. The following code creates an input object for the filetemp.txt and reads data from the file.

Scanner input = new Scanner(newFile("temp.txt"));

System.out.println(input.nextLine());

Figure 17.1 illustrates Java I/O programming. An input object reads a streamof data from

a file, and an output object writes a stream of data to a file. An input object is also called an input streamand an output object an output stream.




17.3 Text I/O vs. Binary I/O

Binary I/O does not involve encoding or decoding and thus is more efficient than text I/O.

1Computers do not differentiate between binary files and text files. All files are stored in binary format, and thus all files are essentially binary files.

2Text I/O is built upon binary I/O to provide a level of abstraction for character encoding and decoding, as shown in Figure 17.2a.

3The JVM converts Unicode to a file-specific encoding when writing a character, and it converts a file-specific encoding to Unicode when reading a character.

4The JVM converts Unicode to a file-specific encoding when writing a character, and it converts a file-specific encoding to Unicode when reading a character.


 

 


 

5Binary files are independent of the encoding scheme on the host machine and thus are portable.

6Java programs on any machine can read a binary file created by a Java program. This is why Java class files are binary files. Java class files can run on a JVM on any machine.

 

17.4 Binary I/O Classes

 

The abstract InputStreamis the root class for reading binary data, and the abstract OutputStreamis the root class for writing binary data.

 

binary input classes, and OutputStreamis the root for binary output classes. Figures 17.4 and 17.5 list all the methods in the classesInputStream and OutputStream.


 

 

 




 

Note

All the methods in the binary I/O classes are declared to throw java.io.IOException or a subclass ofjava.io.IOException.


 

 


 

17.4.1 FileInputStream/FileOutputStream

1、FileInputStream/FileOutputStreamis for reading/writing bytes from/to files.

2、All the methods in these classes are inherited from InputStreamand OutputStream.

3FileInputStream/FileOutputStreamdoes not introduce new methods. To construct a FileInputStream, use the constructors shown in Figure 17.6.


 


4、A java.io.FileNotFoundException will occur if you attempt to create a FileInputStreamwith a nonexistent file.

 

5、To construct a FileOutputStream, use the constructors shown in Figure 17.7.


 

 


 

If the file does not exist, a new file will be created. If the file already exists, the first two constructors will delete the current content of the file. To retain the current content and append new data into the file, use the last two constructors and pass true to the append parameter.

 

6、Almost all the methods in the I/O classes throw java.io.IOException. Therefore, you have to declare to throwjava.io.IOException in the method or place the code in a trycatch block, as shown below:


 


LISTING17.1 TestFileStream.java

import java.io.*;public class TestFileStream {public static void main(String[] args) throws IOException {try(FileOutputStream output = new FileOutputStream("temp.dat");){for(int i = 1; i <= 10; i++)output.write(i);}try(FileInputStream input = new FileInputStream("temp.dat");){int value;while((value = input.read()) != -1)System.out.print(value + " ");}}}

运行结果:

1 2 3 4 5 6 7 8 9 10


程序说明:

1The program uses the try-with-resources to declare and create input and output streams so that they will be automatically closed after they are used. Thejava.io.InputStream andjava.io.OutputStream classes implement theAutoClosable interface. The AutoClosableinterface defines the close()method that closes a resource. Any object of the AutoClosabletype can be used with the try-with-resources syntax for automatic closing.

2、The file temp.dat created in this example is a binary file. It can be read from a Java program but not from a text editor, as shown in Figure 17.8.

 

Tip

When a stream is no longer needed, always close it using the close()method or automatically close it using a try-with-resource statement. Not closing streams may cause data corruption in the output file, or other programming errors.

 

Note

The root directory for the file is the classpath directory. For the example in this book, the root directory isc:\book, so the file temp.dat is located at c:\book. If you wish to place temp.dat in a specific directory, replace line 6 with

FileOutputStream output = new FileOutputStream ("directory/temp.dat");

 

Note

An instance of FileInputStream can be used as an argument to construct a Scanner, and an instance ofFileOutputStream can be used as an argument to construct a PrintWriter. You can create aPrintWriter to append text into a file using 

new PrintWriter(newFileOutputStream("temp.txt",true));

If temp.txt does not exist, it is created. If temp.txtalready exists, new data are appended to the file.

 

17.4.2 FilterInputStream/FilterOutputStream

Filter streams are streams that filter(过滤) bytes for some purpose.

1Using a filter class enables you to read integers, doubles, and strings instead of bytes and characters. FilterInputStreamand FilterOutputStreamare the base classes for filtering data.

2、When you need to process primitive numeric types(原始数值类型), use DataInputStreamand DataOutputStream to filter bytes.


17.4.3 DataInputStream/DataOutputStream

DataInputStream reads bytes from the stream and converts them into appropriate primitive-type values or strings.DataOutputStream converts primitive-type values or strings into bytes and outputs the bytes to the stream.

DataInputStream extendsFilterInputStream and implements theDataInput interface, as shown in Figure 17.9.DataOutputStream extendsFilterOutputStream and implements theDataOutput interface, as shown in Figure 17.10.


 


DataInputStream implements the methods defined in theDataInput interface to read primitive data-type values and strings.DataOutputStream implements the methods defined in theDataOutput interface to write primitive data-type values and strings.

 

Characters and Strings in Binary I/O

1A Unicode character consists of two bytes.

2The writeChar(char c)method writes the Unicode of character cto the output.

3The writeChars(String s)method writes the Unicode for each character in the string s to the output.

4The writeBytes(String s)method writes the lower byte of the Unicode for each character in the strings to the output.

5The high byte of the Unicode is discarded.

6The writeBytesmethod is suitable for strings that consist of ASCII characters, since an ASCII code is stored only in the lower byte of a Unicode. If a string consists of non-ASCII characters, you have to use thewriteChars method to write the string.

7The writeUTF(String s)method writes two bytes of length information to the output stream, followed by the modified UTF-8 representation of every character in the strings.

8UTF-8 is a coding scheme that allows systems to operate with both ASCII and Unicode. Most operating systems use ASCII. Java uses Unicode. The ASCII character set is a subset of the Unicode character set. Since most applications need only the ASCII character set, it is a waste to represent an 8-bit ASCII character as a 16-bit Unicode character.

The modified UTF-8 scheme stores a character using one, two, or three bytes. Characters are coded in one byte if their code is less than or equal to0x7F, in two bytes if their code is greater than0x7F and less than or equal to0x7FF, or in three bytes if their code is greater than0x7FF.

9、The writeUTF(String s) method converts a string into a series of bytes in the UTF-8 format and writes them into an output stream. ThereadUTF() method reads a string that has been written using thewriteUTF method.

 

The UTF-8 format has the advantage of saving a byte for each ASCII character, because a Unicode character takes up two bytes and an ASCII character in UTF-8 only one byte. If most of the characters in a long string are regular ASCII characters, using UTF-8 is more efficient.


Creating DataInputStream/DataOutputStream

DataInputStream/DataOutputStreamare created using the following constructors (see Figures 17.9 and 17.10):


 


public DataInputStream(InputStream instream)

public DataOutputStream(OutputStream outstream)

The following statements create data streams. The first statement creates an input stream for the filein.dat; the second statement creates an output stream for the fileout.dat.

DataInputStream input = new DataInputStream(newFileInputStream("in.dat"));

DataOutputStream output=new DataOutputStream(new FileOutputStream("out.dat"));


LISTING17.2 TestDataStream.java

import java.io.*;public class TestDataStream {public static void main(String[] args) throws IOException {try(DataOutputStream output = new DataOutputStream(new FileOutputStream("temp.dat"));){output.writeUTF("John");output.writeDouble(85.5);output.writeUTF("Jim");output.writeDouble(185.5);output.writeUTF("George");output.writeDouble(105.25);}try(DataInputStream input = new DataInputStream(new FileInputStream("temp.dat"));){System.out.println(input.readUTF() + " " + input.readDouble());System.out.println(input.readUTF() + " " + input.readDouble());System.out.println(input.readUTF() + " " + input.readDouble());}}}


John 85.5

Jim 185.5

George 105.25

 

DataInputStream andDataOutputStream read and write Java primitive-type values and strings in a machine-independent fashion, thereby enabling you to write a data file on one machine and read it on another machine that has a different operating system or file structure.

DataInputStream filters data from an input stream into appropriate primitive-type values or strings.DataOutputStream converts primitive-type values or strings into bytes and outputs the bytes to an output stream. You can viewDataInputStream/FileInputStream andDataOutputStream/FileOutputStreamworking in a pipe line as shown in Figure 17.11.


 

 

 

Caution

You have to read data in the same order and format in which they are stored. For example, since names are written in UTF-8 usingwriteUTF, you must read names using readUTF.

 

Detecting the End of a File

If you keep reading data at the end of an InputStream, anEOFException will occur. This exception can be used to detect the end of a file, as shown in Listing 17.3.


LISTING17.3 DetectEndOfFile.java

import java.io.*;public class DetectEndOfFile {public static void main(String[] args) {try{try(DataOutputStream output = new DataOutputStream(new FileOutputStream("test.dat"))){output.writeDouble(4.5);output.writeDouble(43.25);output.writeDouble(3.2);}try(DataInputStream input = new DataInputStream(new FileInputStream("test.dat"))){while(true)System.out.println(input.readDouble());}}catch(EOFException ex){System.out.println("All data were read");}catch(IOException ex){ex.printStackTrace();}}}

4.5

43.25

3.2

All data were read

 

17.4.4 BufferedInputStream/BufferedOutputStream

BufferedInputStream/BufferedOutputStreamcan be used to speed up input and output by reducing the number of disk reads and writes. UsingBufferedInputStream, the whole block of data on the disk is read into the buffer in the memory once. The individual data are then delivered to your program from the buffer, as shown in Figure 17.12a.

Using BufferedOutputStream, the individual data are first written to the buffer in the memory. When the buffer is full, all data in the buffer are written to the disk once, as shown in Figure 17.12b.

 

 

 

BufferedInputStream/BufferedOutputStreamdoes not contain new methods. All the methods in BufferedInputStream/BufferedOutputStreamare inherited from the InputStream/OutputStreamclasses. BufferedInputStream/BufferedOutputStream manages a buffer behind the scene and automatically reads/writes data from/to disk on demand.

You can wrap a BufferedInputStream/BufferedOutputStreamon any InputStream/OutputStreamusing the constructors shown in Figures 17.13 and 17.14.


 

 

 

If no buffer size is specified, the default size is 512bytes. You can improve the performance of the TestDataStreamprogram in Listing 17.2 by adding buffers in the stream in lines 6–7 and lines 19–20, as follows:

 

DataOutputStream output = new DataOutputStream( newBufferedOutputStream(newFileOutputStream("temp.dat")));

DataInputStream input = new DataInputStream(newBufferedInputStream(newFileInputStream("temp.dat")));

 

Tip

You should always use buffered I/O to speed up input and output. For small files, you may not notice performance improvements. However, for large files—over 100 MB— you will see substantial improvements using buffered I/O.

 

17.5 Case Study: Copying Files

This section develops a useful utility for copying files.

In this section, you will learn how to write a program that lets users copy files. The user needs to provide a source file and a target file as command-line arguments using the command:

java Copy source target

The program copies the source file to the target file and displays the number of bytes in the file. The program should alert the user if the source file does not exist or if the target file already exists. A sample run of the program is shown in Figure 17.15.

 

 

 


LISTING17.4 Copy.java

import java.io.*;public class Copy {/** Main method * @param args[0] for sourcefile * @param args[1] for target file */public static void main(String[] args) throws IOException{//check command-line parameter usageif(args.length != 2){System.out.println("Usage: java Copy sourceFile targetFile");System.exit(1);}//check if source file existsFile sourceFile = new File(args[0]);if(!sourceFile.exists()){System.out.println("Source file " + args[0] + "does not exist");System.exit(2);}//check if target file existsFile targetFile = new File(args[1]);if(targetFile.exists()){System.out.println("Target file "+ args[1] + " already exists");System.exit(3);}try(//Create an input streamBufferedInputStream input = new BufferedInputStream(new FileInputStream(sourceFile));//Create an output streamBufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(targetFile));){//Continuously read a byte from input and write it to outputint r, numberOfBytesCopied = 0;while((r = input.read()) != -1){output.write((byte)r);numberOfBytesCopied++;}System.out.println(numberOfBytesCopied + "bytes copied");}}}

 

17.6 Object I/O

ObjectInputStream/ObjectOutputStreamclasses can be used to read/write serializable objects.

 

DataInputStream/DataOutputStreamenables you to perform I/O for primitive-type values and strings.

 

 ObjectInputStream/ObjectOutputStreamenables you to perform I/O for objects in addition to primitive-type values and strings. Since ObjectInputStream/ObjectOutputStreamcontains all the functions of DataInputStream/DataOutputStream, you can replaceDataInputStream/DataOutputStreamcompletely with ObjectInputStream/ObjectOutputStreamObjectInputStreamextends InputStream and implements ObjectInput and ObjectStreamConstants, as shown in Figure 17.16.

 

ObjectInput is a subinterface of DataInput(DataInput is shown in Figure 17.9).ObjectStreamConstants contains the constants to support ObjectInputStream/ObjectOutputStream.


 

 

 

ObjectOutputStream extendsOutputStream and implementsObjectOutput and ObjectStreamConstants, as shown in Figure 17.17.ObjectOutput is a subinterface of DataOutput(DataOutput is shown in Figure 17.10).


 

 


You can wrap an ObjectInputStream/ObjectOutputStreamon any InputStreamOutputStreamusing the following constructors:

// Create an ObjectInputStream

public ObjectInputStream(InputStream in)

// Create an ObjectOutputStream

public ObjectOutputStream(OutputStream out)

Listing 17.5 writes student names, scores, and the current date to a file namedobject.dat.

 

LISTING17.5 TestObjectOutputStream.java

import java.io.*;public class TestObjectOutputStream {public static void main(String[] args) throws IOException{try(ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("object.dat"));){output.writeUTF("John");output.writeDouble(85.5);output.writeObject(new java.util.Date());}}}

 

To improve performance, you may add a buffer in the stream using the following statement to replace lines 6 and 7:

 

ObjectOutputStream output = new ObjectOutputStream( new BufferedOutputStream(new FileOutputStream("object.dat")));

 

Multiple objects or primitives can be written to the stream. The objects must be read back from the correspondingObjectInputStream with the same types and in the same order as they were written. Java’s safe casting should be used to get the desired type. Listing 17.6 reads data fromobject.dat.

 

LISTING 17.6 TestObjectInputStream.java

import java.io.*;public class TestObjectInputStream {public static void main(String[] args) throws ClassNotFoundException, IOException{try(ObjectInputStream input = new ObjectInputStream(new FileInputStream("object.dat"));){String name = input.readUTF();double score = input.readDouble();java.util.Date date = (java.util.Date)(input.readObject());System.out.println(name + " " + score + " " + date);}}}


 

17.6.1 The Serializable Interface

Not every object can be written to an output stream. Objects that can be so written are said to beserializable. A serializable object is an instance of the java.io.Serializableinterface, so the object’s class must implement Serializable.

The Serializable interface is a marker interface. Since it has no methods, you don’t need to add additional code in your class that implementsSerializable. Implementing this interface enables the Java serialization mechanism to automate the process of storing objects and arrays.

Suppose you wish to store an ArrayListobject. To do this you need to store all the elements in the list. Each element is an object that may contain other objects. As you can see, this would be a very tedious process. Fortunately, you don’t have to go through it manually. Java provides a built-in mechanism to automate the process of writing objects. This process is referred asobject serialization, which is implemented in ObjectOutputStream.

In contrast, the process of reading objects is referred as object deserialization, which is implemented inObjectInputStream.

Many classes in the Java API implement Serializable. All the wrapper classes for primitive type values, java.math.BigInteger,java.math.BigDecimal, java.lang.Stringjava.lang.StringBuilder,java.lang.StringBuffer,java.util.Date, and java.util.ArrayListimplement java.io.Serializable. Attempting to store an object that does not support theSerializable interface would cause a NotSerializableException.

When a serializable object is stored, the class of the object is encoded; this includes the class name and the signature of the class, the values of the object’s instance variables, and the closure of any other objects referenced by the object. The values of the object’s static variables are not stored.

 

Note

Nonserializable fields

If an object is an instance of Serializablebut contains nonserializable instance data fields, can it be serialized? The answer is no. To enable the object to be serialized, mark these data fields with thetransient keyword to tell the JVM to ignore them

when writing the object to an object stream. Consider the following class:

public class C implements java.io.Serializable {

private intv1;

private static doublev2;

private transientA v3 = new A();

}

class A { } // A is not serializable

When an object of the C class is serialized, only variablev1 is serialized. Variable v2is not serialized because it is a static variable, and variable v3 is not serialized because it is markedtransient. If v3 were not marked transient, a java.io.NotSerializableExceptionwould occur.

 

Note

Duplicate objects

If an object is written to an object stream more than once, will it be stored in multiple copies? No, it will not. When an object is written for the first time, a serial number is created for it. The JVM writes the complete contents of the object along with the serial number into the object stream. After the first time, only the serial number is stored if the same object is written again. When the objects are read back, their references are the same since only one object is actually created in the memory.

 

17.6.2 Serializing Arrays

An array is serializable if all its elements are serializable. An entire array can be saved into a file usingwriteObject and later can be restored usingreadObject. Listing 17.7 stores an array of fiveint values and an array of three strings and reads them back to display on the console.

 

LISTING17.7 TestObjectStreamForArray.java

import java.io.*;public class TestObjectStreamForArray {public static void main(String[] args) throws ClassNotFoundException, IOException {int[] numbers = {1, 2, 3, 4, 5};String[] strings = {"John", "Susan", "Kim"};try ( // Create an output stream for file array.datObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("array.dat", true));) {// Write arrays to the object output streamoutput.writeObject(numbers);output.writeObject(strings);}try ( // Create an input stream for file array.datObjectInputStream input = new ObjectInputStream(new FileInputStream("array.dat"));) {int[] newNumbers = (int[])(input.readObject());String[] newStrings = (String[])(input.readObject());// Display arraysfor (int i = 0; i < newNumbers.length; i++)System.out.print(newNumbers[i] + " ");System.out.println();for (int i = 0; i < newStrings.length; i++)System.out.print(newStrings[i] + " ");}}}

1 2 3 4 5

John Susan Kim

 

17.7 Random-Access Files

Java provides the RandomAccessFileclass to allow data to be read from and written to at any locations in the file.

 

All of the streams you have used so far are known as read-only orwrite-only streams. These streams are called sequential streams. A file that is opened using a sequential stream is called asequential-access file. The contents of a sequential-access file cannot be updated.

Java provides the RandomAccessFile class to allow data to be read from and written to at any locations in a file. A file that is opened using the RandomAccessFileclass is known as a random-access file.

The RandomAccessFile class implements theDataInput and DataOutput interfaces, as shown in Figure 17.18. TheDataInput interface (see Figure 17.9) defines the methods for reading primitive-type values and strings (e.g.,readInt, readDouble,readCharreadBoolean,readUTF) and the DataOutput interface (see Figure 17.10) defines the methods for writing primitive-type values and strings (e.g.,writeInt, writeDoublewriteChar,writeBoolean, writeUTF).

When creating a RandomAccessFile, you can specify one of two modes:r or rw. Mode rmeans that the stream is read-only, and mode rwindicates that the stream allows both read and write. For example, the following statement creates a new stream,raf, that allows the program to read from and write to the filetest.dat:

RandomAccessFile raf = new RandomAccessFile("test.dat","rw");

If test.dat already exists, rafis created to access it; if test.dat does not exist, a new file named test.datis created, and raf is created to access the new file. The methodraf.length() returns the number of bytes intest.dat at any given time. If you append new data into the file, raf.length()increases.


 

 

Tip

If the file is not intended to be modified, open it with the rmode. This prevents unintentional modification of the file.

 

A special marker called a file pointer is positioned at one of these bytes. A read or write operation takes place at the location of the file pointer. When a file is opened, the file pointer is set at the beginning of the file. When you read or write data to the file, the file pointer moves forward to the next data item. For example,

if you read an int value usingreadInt(), the JVM reads4 bytes from the file pointer, and now the file pointer is4 bytes ahead of the previous location, as shown in Figure 17.19. 

For a RandomAccessFile raf, you can use theraf.seek(position) method to move the file pointer to a specified position.raf.seek(0) moves it to the beginning of the file, andraf.seek(raf.length()) moves it to the end of the file. Listing 17.8 demonstrates RandomAccessFile. A large case study of usingRandomAccessFile to organize an address book is given in Supplement VI.D.


 


LISTING17.8 TestRandomAccessFile.java

import java.io.*;public class TestRandomAccessFile {public static void main(String[] args) throws IOException {try ( // Create a random access fileRandomAccessFile inout = new RandomAccessFile("inout.dat", "rw");) {// Clear the file to destroy the old contents if existsinout.setLength(0);// Write new integers to the filefor (int i = 0; i < 200; i++)inout.writeInt(i);// Display the current length of the fileSystem.out.println("Current file length is " + inout.length());// Retrieve the first numberinout.seek(0); // Move the file pointer to the beginningSystem.out.println("The first number is " + inout.readInt());// Retrieve the second numberinout.seek(1 * 4); // Move the file pointer to the second numberSystem.out.println("The second number is " + inout.readInt());// Retrieve the tenth numberinout.seek(9 * 4); // Move the file pointer to the tenth numberSystem.out.println("The tenth number is " + inout.readInt());// Modify the eleventh numberinout.writeInt(555);// Append a new numberinout.seek(inout.length()); // Move the file pointer to the endinout.writeInt(999);// Display the new lengthSystem.out.println("The new length is " + inout.length()); // Retrieve the new eleventh numberinout.seek(10 * 4); // Move the file pointer to the eleventh numberSystem.out.println("The eleventh number is " + inout.readInt());}}}

 

Current file length is 800

The first number is 0

The second number is 1

The tenth number is 9

The new length is 804

The eleventh number is 555

 

CHAPTER17 SUMMARY

1. I/O can be classified intotext I/O and binary I/O. Text I/O interprets data in sequences of characters. Binary I/O interprets data as raw binary values. How text is stored in a file depends on the encoding scheme for the file. Java automatically performs encoding and decoding for text I/O.

2. The InputStreamand OutputStream classes are the roots of all binary I/O classes. FileInputStream/FileOutputStreamassociates a file for input/output. BufferedInputStream/BufferedOutputStreamcan be used to wrap any binary I/O stream to improve performance.DataInputStream/DataOutputStreamcan be used to read/write primitive values and strings.

3. ObjectInputStream/ObjectOutputStreamcan be used to read/write objects in addition to primitive values and strings. To enable objectserialization, the object’s defining class must implement the java.io.Serializablemarker interface.

4. The RandomAccessFileclass enables you to read and write data to a file. You can open a file with ther mode to indicate that it is read-only or with therw mode to indicate that it is updateable. Since theRandomAccessFile class implementsDataInput andDataOutput interfaces, many methods inRandomAccessFile are the same as those inDataInputStream and DataOutputStream.


0 0
原创粉丝点击