20.6 The Data Byte Streams
Reading and writing text characters is useful, but you also frequently need to transmit the binary data of specific types across a stream. The DataInput and DataOutput interfaces define methods that transmit primitive types across a stream. The classes DataInputStream and DataOutputStream provide a default implementation for each interface. We cover the interfaces first, followed by their implementations.
20.6.1 DataInput and DataOutput
The interfaces for data input and output streams are almost mirror images. The parallel read and write methods for each type are
Read |
Write |
Type |
readBoolean |
writeBoolean |
boolean |
readChar |
writeChar |
char |
readByte |
writeByte |
byte |
readShort |
writeShort |
short |
readInt |
writeInt |
int |
readLong |
writeLong |
long |
readFloat |
writeFloat |
float |
readDouble |
writeDouble |
double |
readUTF |
writeUTF |
String (in UTF format) |
String values are read and written using a modified form of the UTF-8 character encoding. This differs from standard UTF-8 in three ways: the null byte (\u0000) is encoded in a 2-byte format so that the encoded string does not have embedded null bytes; only 1-byte, 2-byte, or 3-byte formats are used; and supplementary characters are encoded using surrogate pairs. Encoding Unicode characters into bytes is necessary in many situations because of the continuing transition from 8-bit to 16-bit character sets.
In addition to these paired methods, DataInput has several methods of its own, some of which are similar to those of InputStream:
-
public abstract void
readFully(byte[] buf, int offset, int count)
throws IOException
- Reads into part of a byte array. The maximum number of bytes read is count. The bytes are stored from buf[offset] up to a maximum of buf[offset+count-1]. If count is zero then no bytes are read. This method blocks until input is available, the end of the file (that is, stream) is found—in which case an EOFException is thrown—or an exception is thrown because of an I/O error.
-
public abstract void
readFully(byte[] buf)
throws IOException
- Equivalent to readFully(buf,0, buf.length).
-
public abstract int
skipBytes(int count)
throws IOException
- Attempts to skip over count bytes, discarding any bytes skipped over. Returns the actual number of bytes skipped. This method never throws an EOFException.
-
public abstract int
readUnsignedByte()
throws IOException
- Reads one input byte, zero-extends it to type int, and returns the result, which is therefore in the range 0 through 255. This method is suitable for reading a byte written by the writeByte method of DataOutput if the argument to writeByte was a value in the range 0 through 255.
-
public abstract int
readUnsignedShort()
throws IOException
- Reads two input bytes and returns an int value in the range 0 through 65535. The first byte read is made the high byte. This method is suitable for reading bytes written by the writeShort method of DataOutput if the argument to writeShort was a value in the range 0 through 65535.
The DataInput interface methods usually handle end-of-file (stream) by throwing EOFException when it occurs. EOFException extends IOException.
The DataOutput interface supports signatures equivalent to the three forms of write in OutputStream and with the same specified behavior. Additionally, it provides the following unmirrored methods:
-
public abstract void
writeBytes(String s)
throws IOException
- Writes a String as a sequence of bytes. The upper byte in each character is lost, so unless you are willing to lose data, use this method only for strings that contain characters between \u0000 and \u00ff.
-
public abstract void
writeChars(String s)
throws IOException
- Writes a String as a sequence of char. Each character is written as two bytes with the high byte written first.
There are no readBytes or readChars methods to read the same number of characters written by a writeBytes or writeChars invocation, therefore you must use a loop on readByte or readChar to read strings written with these methods. To do that you need a way to determine the length of the string, perhaps by writing the length of the string first, or by using an end-of-sequence character to mark its end. You could use readFully to read a full array of bytes if you wrote the length first, but that won't work for writeChars because you want char values, not byte values.
20.6.2 The Data Stream Classes
For each Data interface there is a corresponding Data stream. In addition, the RandomAccessFile class implements both the input and output Data interfaces (see Section 20.7.2 on page 541). Each Data class is an extension of its corresponding Filter class, so you can use Data streams to filter other streams. Each Data class has constructors that take another appropriate input or output stream. For example, you can use the filtering to write data to a file by putting a DataOutputStream in front of a FileOutputStream object. You can then read the data by putting a DataInputStream in front of a FileInputStream object:
public static void writeData(double[] data, String file) throws IOException { OutputStream fout = new FileOutputStream(file); DataOutputStream out = new DataOutputStream(fout); out.writeInt(data.length); for (double d : data) out.writeDouble(d); out.close(); } public static double[] readData(String file) throws IOException { InputStream fin = new FileInputStream(file); DataInputStream in = new DataInputStream(fin); double[] data = new double[in.readInt()]; for (int i = 0; i < data.length; i++) data[i] = in.readDouble(); in.close(); return data; }
The writeData method first opens the file and writes the array length. It then loops, writing the contents of the array. The file can be read into an array with readData. These methods can be rewritten more simply using the Object streams you will learn about in Section 20.8 on page 549.
Exercise 20.7 : Add a method to the Attr class of Chapter 3 that writes the contents of an object to a DataOutputStream and add a constructor that will read the state from a DataInputStream.