Buffered Readers and Writers
Input and output can be time-consuming operations. It's often quicker to read or write text in large chunks rather than in many separate smaller pieces, even when you only process the text in the smaller pieces. The java.io.BufferedReader and java.io.BufferedWriter classes provide internal character buffers. Text that's written to a buffered writer is stored in the internal buffer and only written to the underlying writer when the buffer fills up or is flushed. Likewise, reading text from a buffered reader may cause more characters to be read than were requested; the extra characters are stored in an internal buffer. Future reads first access characters from the internal buffer and only access the underlying reader when the buffer is emptied.
|
20.9.1. Buffering Writes
The java.io.BufferedWriter class is a subclass of java.io.Writer that you chain to another Writer class to buffer characters. This allows more efficient writing of text.
public class BufferedWriter extends Writer
There are two constructors. One has a default buffer size (8192 characters); the other lets you specify the buffer size:
public BufferedWriter(Writer out) public BufferedWriter(Writer out, int size)
For example:
BufferedWriter bw = new BufferedWriter(new FileWriter("37.html"));
BufferedWriter overrides most of its superclass's methods, but all changes are purely internal. write( ), flush( ), close( ), etc. are all used exactly as they are for any writer object.
There is one new method in this class, newLine( ). This method writes a platform-dependent line terminator string: on Unix, on the Mac, on Windows. The value of this string is taken from the system property line.separator.
public String newLine( ) throws IOException
|
Example 20-3 is a revised version of Example 20-1 that uses a BufferedWriter to increase efficiency and handle platform-dependent line separators.
Example 20-3. BufferedUnicodeTable
import java.io.*; public class BufferedBMPTable { public static void main(String[] args) throws IOException { // Use platform default with a fallback to Latin-1 if necessary String encoding = System.getProperty("file.encoding", "ISO-8859-1"); String lineSeparator = System.getProperty("line.separator", " "); OutputStream target = System.out; if (args.length > 0) target = new FileOutputStream(args[0]); if (args.length > 1) encoding = args[1]; BufferedWriter out = null; try { out = new BufferedWriter(new OutputStreamWriter(target, encoding)); } catch (UnsupportedEncodingException ex) { // platform default encoding out = new BufferedWriter(new OutputStreamWriter(target)); } try { for (int i = Character.MIN_VALUE; i <= Character.MAX_VALUE; i++) { if (!Character.isDefined(i)) continue; char c = (char) i; if (Character.isHighSurrogate(c) || Character.isLowSurrogate(c)) continue; out.write(i + ": " + c); out.newLine( ); } } finally { out.close( ); } } } |
This is actually not the fastest you can go. BufferedWriter is internally synchronized. Each call to one of its methods is atomic. If two threads try to write onto the same BufferedWriter at the same time, one of them blocks. This prevents the threads from corrupting the data. However, this synchronization has a performance cost, even when only one thread has access to the writer. You can often improve performance by replacing the stock BufferedWriter from java.io with an unsynchronized version such as shown in Example 20-4. When you don't need to worry about synchronization, this version can increase speed by 30-50%, though as always exact performance gains are likely to vary from one VM to the next.
Example 20-4. UnsynchronizedBufferedWriter
package com.elharo.io; import java.io.*; public class UnsynchronizedBufferedWriter extends Writer { private final static int CAPACITY = 8192; private char[] buffer = new char[CAPACITY]; private int position = 0; private Writer out; private boolean closed = false; public UnsynchronizedBufferedWriter(Writer out) { this.out = out; } public void write(char[] text, int offset, int length) throws IOException { checkClosed( ); while (length > 0) { int n = Math.min(CAPACITY - position, length); System.arraycopy(text, offset, buffer, position, n); position += n; offset += n; length -= n; if (position >= CAPACITY) flushInternal( ); } } public void write(String s) throws IOException { write(s, 0, s.length( )); } public void write(String s, int offset, int length) throws IOException { checkClosed( ); while (length > 0) { int n = Math.min(CAPACITY - position, length); s.getChars(offset, offset + n, buffer, position); position += n; offset += n; length -= n; if (position >= CAPACITY) flushInternal( ); } } public void write(int c) throws IOException { checkClosed( ); if (position >= CAPACITY) flushInternal( ); buffer[position] = (char) c; position++; } public void flush( ) throws IOException { flushInternal( ); out.flush( ); } private void flushInternal( ) throws IOException { if (position != 0) { out.write(buffer, 0, position); position = 0; } } public void close( ) throws IOException { closed = true; this.flush( ); out.close( ); } private void checkClosed( ) throws IOException { if (closed) throw new IOException("Writer is closed"); } } |
All characters are first written into an internal byte array of length 8192. Only when that buffer fills up is it flushed to the underlying writer. The java.io.BufferedWriter class is organized very much like this, except that it also has a number of synchronized blocks to permit threadsafe usage.
20.9.2. Buffering Reads
BufferedReader is a subclass of Reader that is chained to another Reader class to buffer input. This allows more efficient reading of characters and lines.
public class BufferedReader extends Reader
For example:
BufferedReader br = new BufferedReader(new FileReader("37.html"));
There are two constructors. One has a default buffer size (8192 characters); the other requires the programmer to specify the buffer size:
public BufferedReader(Reader in, int buffer_size) public BufferedReader(Reader in)
In a BufferedReader, the two multicharacter read( ) methods try to completely fill the specified array or subarray of text by reading repeatedly from the underlying reader. They return only when the requested number of characters have been read, the end of the data is reached, or the underlying reader would block. This is not the case for most readers which attempt only one read from the underlying data source before returning.
BufferedReader does support marking and resetting, at least up to the length of the buffer. Another reason to use a BufferedReader is to enable marking and resetting on a reader that otherwise wouldn't support it, such as an InputStreamReader.
Besides buffering, BufferedReader is notable for its readLine( ) method that allows you to read text a line at a time. This replaces the common but deprecated readLine( ) method in DataInputStream.
public String readLine( ) throws IOException
This method returns a string that contains a line of text from a text file. , , and are assumed to be line breaks and are not included in the returned string. This method is often used when reading user input from System.in since most platforms only send the user's input to the running program after the user has typed a full line (that is, hit the Enter key).
|
Example 20-5 uses a BufferedReader and readLine( ) to read all files named on the command line, line by line, and copy them to System.out. In essence it implements the Unix cat or the DOS type utility.
Example 20-5. The cat program
import java.io.*; class Cat { public static void main (String[] args) { String thisLine; for (int i=0; i < args.length; i++) { try { BufferedReader br = new BufferedReader(new FileReader(args[i])); while ((thisLine = br.readLine( )) != null) { System.out.println(thisLine); } // end while } // end try catch (IOException ex) {System.err.println(ex);} } // end for } // end main } |
20.9.3. Line Numbering
LineNumberReader is a subclass of BufferedReader that keeps track of which line it's currently reading. It also has methods to get and set the line number. This class replaces the deprecated LineNumberInputStream class.
public class LineNumberReader extends BufferedReader
This class has two constructors. Both chain this reader to an underlying reader; the second also sets the size of the buffer.
public LineNumberReader(Reader in) public LineNumberReader(Reader in, int size)
LineNumberReader has all the methods of BufferedReader, including readLine( ). These are overridden to keep track of the line number. The behavior of these methods is not changed.
LineNumberReader also introduces two methods for inspecting and changing the line number:
public int getLineNumber( ) public void setLineNumber(int lineNumber)
The setLineNumber( ) method does not change the line that you're reading in the file. It just changes the value getLineNumber( ) returns. For example, it would allow you to start counting from -5 if you knew there were six lines of header data, and you wanted line 1 to be the first line of the body text.
Example 20-6 uses a LineNumberReader and readLine( ) to read all files named on the command line, line by line, and copy them to System.out, prefixing each line with its line number.
Example 20-6. The LineCat Program
import java.io.*; class LineCat { public static void main (String[] args) { String thisLine; for (int i=0; i < args.length; i++) { try { LineNumberReader br = new LineNumberReader(new FileReader(args[i])); while ((thisLine = br.readLine( )) != null) { System.out.println(br.getLineNumber( ) + ": " + thisLine); } // end while } // end try catch (IOException ex) {System.err.println(ex);} } // end for } // end main } |