Java BufferedReader
Java BufferedReader类java.io.BufferedReader为Java Reader实例提供缓冲。缓冲可以大大提高IO的速度。 JavaBufferedReader不是一次从底层的Reader中读取一个字符,而是一次读取一个更大的块(数组)。这通常要快得多,尤其是对于磁盘访问和更大的数据量而言。
Java的BufferedReader与BufferedInputStream相似,但是它们并不完全相同。 BufferedReader和BufferedInputStream之间的主要区别是BufferedReader读取字符(文本),而BufferedInputStream读取原始字节。
JavaBufferedReader类是Java Reader类的子类,因此我们可以在需要Reader的任何地方使用BufferedReader。
Java BufferedReader示例
要将缓冲添加到JavaReader实例,只需将其包装在BufferedReader中即可。看起来是这样的:
BufferedReader bufferedReader = new BufferedReader(
new FileReader("c:\data\input-file.txt"));
这个例子创建了一个" BufferedReader",它包装了一个" FileReader"。 BufferedReader将从FileReader中读取一个字符块(通常读入char数组中)。因此,从read()返回的每个字符都从此内部数组返回。完全读取数组后," BufferedReader"会将新的数据块读入数组等。
BufferedReader缓冲区大小
我们可以将缓冲区大小设置为由BufferedReader内部使用。我们将size作为构造函数参数提供,如下所示:
int bufferSize = 8 * 1024;
BufferedReader bufferedReader = new BufferedReader(
new FileReader("c:\data\input-file.txt"),
bufferSize
);
本示例将内部缓冲区设置为8 KB。最好使用1024个字节的倍数的缓冲区大小。这与硬盘等大多数内置缓冲一起使用时效果最佳。
除了向Reader实例添加缓冲外,JavaBufferedReader的行为几乎类似于Reader。不过,BufferedReader还有一个额外的方法,readLine()方法。如果我们需要一次读取输入一行,则此方法很方便。这是一个BufferedReader``readLine()的例子:
String line = bufferedReader.readLine();
readLine()方法将返回从BufferedReader中读取的文本行(找到换行符之前的所有文本)。如果没有更多的数据可以从底层的Reader中读取,则BufferedReader的readLine()方法将返回null。
从BufferedReader读取字符
JavaBufferedReader的read()方法返回一个int值,其中包含下一个读取的字符的char值。如果read()方法返回-1,则说明BufferedReader中没有更多的数据可读取,因此可以将其关闭。也就是说,-1作为int值,而不是-1作为byte或者char值。这里有区别!
这是一个从JavaBufferedReader中读取所有字符的示例:
Reader reader =
new BufferedReader(
new FileReader("/path/to/file/thefile.txt"));
int theCharNum = reader.read();
while(theCharNum != -1) {
char theChar = (char) theCharNum;
System.out.print(theChar);
theCharNum = reader.read();
}
请注意,代码示例如何首先从JavaBufferedReader中读取单个字符并检查char数值是否等于-1. 如果没有,它将处理该char并继续读取,直到从BufferedReader的read()方法返回-1为止。
如前所述," BufferedReader"实际上将从底层的" Reader"中读取一个字符数组,然后逐个返回这些字符,而不是将每个" read()"调用都转发给底层的" Reader"。读取完内部缓冲区中的所有字符后," BufferedReader"会尝试再次填充缓冲区,直到无法从底层的" Reader"中读取更多字符为止。
从BufferedReader读取字符数组
Java的" BufferedReader"类还具有一个" read()"方法,该方法采用" char"数组作为参数,以及起始偏移量和长度。 char数组是read()方法将字符读入的数组。 offset参数是在char数组中的read()方法应该开始读入的位置。 length参数是read()方法应从偏移量开始并向前读取到char数组中的字符数。以下是使用JavaBufferedReader将字符数组读入char数组的示例:
Reader reader =
new BufferedReader(
new FileReader("/path/to/file/thefile.txt"));
char[] theChars = new char[128];
int charsRead = reader.read(theChars, 0, theChars.length);
while(charsRead != -1) {
System.out.println(new String(theChars, 0, charsRead));
charsRead = reader.read(theChars, 0, theChars.length);
}
read(char [],offset,length)方法返回读入char数组的字符数,如果BufferedReader中没有更多的字符可读取,则返回-1,例如已达到" BufferedReader"所连接的文件。
从BufferedReader读取一行
Java的BufferedReader具有一个名为readLine()的特殊读取方法,该方法从BufferedReader的内部缓冲区中读取整行文本。 readLine()方法返回一个String。如果没有更多的行要从" BufferedReader"读取,则" readLine()"方法将返回" null"。这是一个使用JavaBufferedReader逐行读取文本文件行的示例:
BufferedReader bufferedReader =
new BufferedReader(
new FileReader("/path/to/file/thefile.txt"));
String line = bufferedReader.readLine();
while(line != null) {
System.out.println(line);
line = bufferedReader.readLine();
}
读取的性能
一次读取一个字符数组比一次从JavaReader读取单个字符要快。但是,由于" BufferedReader"已经进行了一些内部缓冲,因此这种差异很可能不像不使用缓冲的" Reader"那样明显。不过,我们很可能仍会看到很小的差异。
跳过字符
JavaBufferedReader类有一个名为skip()的方法,可用于跳过我们不想读取的输入中的许多字符。我们将要跳过的字符数作为参数传递给skip()方法。这是一个从JavaBufferedReader跳过字符的示例:
long charsSkipped = bufferedReader.skip(24);
这个例子告诉JavaBufferedReader跳过BufferedReader中接下来的24个字符。 skip()方法返回被跳过的实际字符数。在大多数情况下,该数字与我们请求的跳过数字相同,但是如果BufferedReader中剩余的字符少于我们请求跳过的数字,则返回的跳过字符数可以小于我们请求的字符数跳过了。
关闭BufferedReader
当我们从BufferedReader中读取完字符后,我们应该记住将其关闭。关闭" BufferedReader"也会关闭" BufferedReader"正在读取的" Reader"实例。
关闭" BufferedReader"是通过调用其" close()"方法完成的。这是关闭BufferedReader的样子:
bufferedReader.close();
我们还可以使用Java 7中引入的try-with-resources构造。这是如何使用try-with-resources构造使用和关闭" BufferedReader"外观的方法:
Reader reader = new FileReader("data/data.bin");
try(BufferedReader bufferedReader =
new BufferedReader(reader)){
String line = bufferedReader.readLine();
while(line != null) {
//do something with line
line = bufferedReader.readLine();
}
}
请注意,不再有任何显式的close()方法调用。 try-with-resources构造可以解决这一问题。
还要注意,第一个FileReader实例不是在try-with-resources块内创建的。这意味着try-with-resources块不会自动关闭此FileReader实例。但是,当关闭BufferedReader时,它还将关闭从中读取的Reader实例,因此当BufferedReader关闭时,FileReader实例也将关闭。
可重用的BufferedReader
标准Java" BufferedReader"的缺点之一是只能使用一次。一旦关闭它,它就不再可用。如果需要读取大量文件或者网络流,则必须为要读取的每个文件或者网络流创建一个新的BufferedReader。这意味着我们要创建一个新对象,更重要的是,要创建一个新的char数组,该数组用作BufferedReader中的缓冲区。如果读取的文件或者流的数量很高,并且彼此之间读取很快,这可能会对Java垃圾收集器造成压力。
一种替代方法是创建一个可重用的BufferedReader,其中我们可以替换基础源Reader,以便可以重用BufferedReader及其内部字节数组缓冲区。为了节省麻烦,我创建了这样一个ReusableBufferedReader,并在本教程的后续部分中包含了该代码。首先,我想向我们展示如何使用此" ReusableBufferedReader"外观。
创建一个ReusableBufferedReader
首先,我们需要创建一个" ReusableBufferedReader"。这是一个如何创建ReusableBufferedReader的例子:
ReusableBufferedReader reusableBufferedReader =
new ReusableBufferedReader(new char[1024 * 1024]);
本示例创建一个具有2 MB字符数组(1024 \ * 1024个字符,1个字符= 2个字节)的" ReusableBufferedReader"作为内部缓冲区。
设定来源
当创建了一个" ReusableBufferedReader"时,我们需要在其上设置" Reader"以用作基础数据源。这是在ReusableBufferedReader上设置源Reader的方法:
FileReader reader = new FileReader("/mydata/somefile.txt");
reusableBufferedReader.setSource(reader);
setSource()方法实际上返回对ReusableBufferedReader的引用,因此我们实际上可以创建一个ReusableBufferedReader并在一条指令中设置源:
ReusableBufferedReader reusableBufferedReader =
new ReusableBufferedReader(new byte[1024 * 1024])
.setSource(new FileReader("/mydata/somefile.txt"));
重用一个ReusableBufferedReader
使用完ReusableBufferedReader之后,我们需要关闭它。关闭它只会关闭底层的源Reader。关闭ReusableBufferedReader之后,我们可以再次使用它,只需在其上设置一个新的源Reader。重用ReusableBufferedReader的样子如下:
reusableBufferedReader.setSource(new FileReader("/mydata/file-1.txt"));
//read data from ReusableBufferedReader
reusableBufferedReader.close();
reusableBufferedReader.setSource(new FileReader("/mydata/file-1.txt"));
//read data from ReusableBufferedReader
reusableBufferedReader.close();
ReusableBufferedReader代码
这是上述ReusableBufferedReader的代码。注意,该实现仅覆盖其扩展的Reader类的read()和read(char [] dest,int offset,int length)方法。其余的Reader方法已被省略,以使代码更短,但是我们可以自己实现它们,以备需要时使用。
import java.io.IOException;
import java.io.Reader;
public class ReusableBufferedReader extends Reader {
private char[] buffer = null;
private int writeIndex = 0;
private int readIndex = 0;
private boolean endOfReaderReached = false;
private Reader source = null;
public ReusableBufferedReader(char[] buffer) {
this.buffer = buffer;
}
public ReusableBufferedReader setSource(Reader source){
this.source = source;
this.writeIndex = 0;
this.readIndex = 0;
this.endOfReaderReached = false;
return this;
}
@Override
public int read() throws IOException {
if(endOfReaderReached) {
return -1;
}
if(readIndex == writeIndex) {
if(writeIndex == buffer.length) {
this.writeIndex = 0;
this.readIndex = 0;
}
//data should be read into buffer.
int bytesRead = readCharsIntoBuffer();
while(bytesRead == 0) {
//continue until you actually get some bytes !
bytesRead = readCharsIntoBuffer();
}
//if no more data could be read in, return -1;
if(bytesRead == -1) {
return -1;
}
}
return 65535 & this.buffer[readIndex++];
}
@Override
public int read(char[] dest, int offset, int length) throws IOException {
int charsRead = 0;
int data = 0;
while(data != -1 && charsRead < length){
data = read()%3b.html
if(data == -1) {
endOfReaderReached = true;
if(charsRead == 0){
return -1;
}
return charsRead;
}
dest[offset + charsRead] = (char) (65535 & data);
charsRead++;
}
return charsRead;
}
private int readCharsIntoBuffer() throws IOException {
int charsRead = this.source.read(this.buffer, this.writeIndex, this.buffer.length - this.writeIndex);
writeIndex += charsRead;
return charsRead;
}
@Override
public void close() throws IOException {
this.source.close();
}
}

