一、BufferedInputStream
BufferedInputStream 的作用是什么?能够提高效率吗?
BufferedInputStream 是缓冲输入流。BufferedInputStream 的作用是为另一个输入流添加一些功能,
例如,提供“缓冲功能”以及支持“mark()标记”和“reset()重置方法”。BufferedInputStream 本质上是通过
一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()
读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据
被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。
上边先来了一段总结,让大家有一段感性认识(来自网络,比我写得好)
输入流在默认情况下是不被缓冲区缓存的,也就是说,每个read的调用都会请求操作系统再分发一个字节。
(摘自Java核心技术)
我们来看一下InputStream
public abstract class InputStream implements Closeable {
public abstract int read() throws IOException; //从输入流读取一个字节
//其余的方法是基于 read()变化的
public int read(byte b[], int off, int len) throws IOException {
//...
try {
for (; i < len ; i++) {
c = read(); // 循环调用read(),从而获取字节数组
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
//...
}
从上边的定义中可知, 一次读取一个字节数组 和 调用单字节read() n次获取字节数组 本质上并没有差异。除非具体的输入流有不同的实现,例如:FileInputStream
再来看一下 BufferedInputStream
的实现:
class BufferedInputStream extends FilterInputStream {
protected volatile byte buf[];
//读取单个字节
public synchronized int read() throws IOException {
if (pos >= count) {
fill(); // buf 读取完毕,直接再从依赖的InputStream中读取字节数组,填充缓存
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff; //buf有的话就直接返回
}
//填充buf
private void fill() throws IOException {
//...
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
// 读取字节数组
public synchronized int read(byte b[], int off, int len)
throws IOException
{
// ...
int n = 0;
for (;;) {
int nread = read1(b, off + n, len - n); //读取字节数组
// ...
}
}
//根据条件,有可能直接从输入流读取,有可能先填充buf,再从buf中获取
private int read1(byte[] b, int off, int len) throws IOException {
int avail = count - pos;
if (avail <= 0) {
if (len >= getBufIfOpen().length && markpos < 0) {
return getInIfOpen().read(b, off, len); //从输入流读取字节数组
}
fill();
avail = count - pos;
if (avail <= 0) return -1;
}
int cnt = (avail < len) ? avail : len;
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
pos += cnt;
return cnt;
}
//...
}
BufferedInputStream
通过装饰器模式,增强其它输入流的功能。看到这里我们可以得出一个结论,BufferedInputStream
的缓存功能并没有给普通输入流的效率带来什么大的变化,只是给使用者提供了方便的功能。(当然这句话有个前提,就是所包装输入流 的 read(byte b[], int off, int len) 是基于 int read() 实现)
我们写一段测试代码,实验一下:
//不带缓存
fun readFileNotBuffer2(byteInput: InputStream){
val start = System.currentTimeMillis()
var byte = byteInput.read() // ByteArrayInputStream
while (byte != -1){
byte = byteInput.read()
}
val end = System.currentTimeMillis()
println("readFile NotBuffer2-during ${end - start} ms")
}
// 带缓存
fun readFileWithBuffer2(byteInput: InputStream){
val start = System.currentTimeMillis()
val input = byteInput.buffered() // 构建基于ByteArrayInputStream 的 BufferedInputStream
input.use {
var byte = it.read()
while (byte != -1){
byte = input.read()
}
val end = System.currentTimeMillis()
println("readFile WithBuffer2-during ${end - start} ms")
}
}
fun main() {
val buffer = File("pic.png").source().buffer()
val byteInput = buffer.use {
ByteArrayInputStream(it.readByteArray())
}
//构建的ByteArrayInputStrean都已经读取到内存中了
byteInput.use {
readFileWithBuffer2(it)
}
val buffer2 = File("pic.png").source().buffer()
val byteInput2 = buffer2.use {
ByteArrayInputStream(it.readByteArray())
}
//构建的ByteArrayInputStrean都已经读取到内存中了
byteInput2.use {
readFileNotBuffer2(it)
}
}
看一下结果:(时间不稳定,使用BufferedInputStream没有明显优势)
readFile WithBuffer2-during 5
readFile NotBuffer2-during 2
readFile WithBuffer2-during 4
readFile NotBuffer2-during 13
readFile WithBuffer2-during 3
readFile NotBuffer2-during 5
但是当 BufferedInputStream
和 FileInputStream
搭配时可以很好提高效率,我们看一段测试代码
//不带缓存
fun readFileNotBuffer(){
val start = System.currentTimeMillis()
val input = File("pic.png").inputStream() //直接使用FileInputStream
input.use {
var byte = it.read()
while (byte != -1){
byte = input.read()
}
}
val end = System.currentTimeMillis()
println("readFile NotBuffer-during ${end - start} ms")
}
// 带缓存
fun readFileWithBuffer(){
val start = System.currentTimeMillis()
val input = File("pic.png").inputStream().buffered() //BufferedInputStream包装了FileInputStream
input.use {
var byte = it.read()
while (byte != -1){
byte = input.read()
}
}
val end = System.currentTimeMillis()
println("readFile WithBuffer-during ${end - start} ms")
}
fun main() {
readFileWithBuffer()
readFileNotBuffer()
}
运行结果:(很稳定,基本上都是5 到 10倍的效率提升)
readFile WithBuffer-during 7 ms
readFile NotBuffer-during 67 ms
为什么和FileInputStream搭配就会有很好的效率提升呢?
看那一下 FileInputStream
实现
class FileInputStream extends InputStream{
public int read() throws IOException {
return read0(); //本地方法
}
private native int read0() throws IOException; //本地方法
//读取字节数组,也是基于native方法
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len); //本地方法
}
//本地方法
private native int readBytes(byte b[], int off, int len) throws IOException;
}
从代码我们可以知道,FileInputStream
的 read() 和 read(byte b[], int off, int len)都是基于 native 方法实现,read对于字节数组的获取已经不是基于read()单字节的实现。有了这层保证,我们知道配合BufferedInputStream
的使用,可以极大减少对于io的访问次数,直接从内存获取数据,效率肯定得到很好提升。
总结:聊了这些应该对于BufferedInputStream的作用,以及BufferedInputStream对效率的提升有没有作用,也有了一个清晰的认识。还是建议用上Buffered,就算没有效率提升,毕竟没有什么坏处。
二、OutputStream
public abstract class OutputStream implements Closeable, Flushable {
//像输出流 写如单个字节
public abstract void write(int b) throws IOException;
// write(byte b[], int off, int len) 也是基于 write(int b)单字节写入实现
// flush 输出流,强制缓存的字节写入输出流
public void flush() throws IOException {}
}
OutputStream
定义了输出流的行为规范,具体的功能需要子类去实现。flush
的目的是想对输出流有一个约束,就是尽量不要每次都通过write把字节写出到输出流,而是先缓存下来,然后在何时的时间通过一次性写入OutputStream。其实说,这个有没有用呢,得看具体的输出流实现,如果write(byte b[]) 是 基于 write(int b)实现的,那基本上效率也并没有太大的变化。
看一下FileOutputStream 的实现:
class FileOutputStream extends OutputStream{
private native void write(int b, boolean append) throws IOException;
public void write(int b) throws IOException {
write(b, append);
}
public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len, append);
}
private native void writeBytes(byte b[], int off, int len, boolean append)
throws IOException;
}
FileOutputStream
针对单字节 和 字节数组的输出都做了处理,这时候通过先缓存在 buffer里,然后在合适的时候,再写出到输出流效率肯定会提升很多,因为毕竟 io 次数大大减少了。
看一下 BufferedOutputStream 的实现就一目了然了。
class BufferedOutputStream extends FilterOutputStream {
protected byte buf[];
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
//flush
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();
}
/** Flush the internal buffer */
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}
}
网友评论