当前位置:   article > 正文

Java IO流之BufferedInputStream和BufferedOutputStream分析_bufferedinputstream bufferedoutputstream和普通流的区别

bufferedinputstream bufferedoutputstream和普通流的区别

简介

    BufferedInputStream和BufferedOutputStream都是缓冲流,缓冲流实际上里面有一个缓冲区,即字节数组,用流读取存储在硬盘上较大文件的时,频繁从硬盘中读取数据效率比较低,花费时间较长.缓冲流的作用就是将数据先读取到内存里面,然后从内存里面读取数据,读取速度得到很大的提升.

BufferedInputStrem介绍

1.构造方法

  1. public BufferedInputStream(InputStream in) {this(in, DEFAULT_BUFFER_SIZE);}
  2. public BufferedInputStream(InputStream in, int size) {}
  • 无size参数的构造方法,创建的默认大小8192字节的字节数组.
  • 有size的参数构造方法,创建的是指定size大小字节数组的缓冲输入流.

2.内部变量

  1. protected volatile byte buf[];
  2. protected int count;
  3. protected int pos;
  4. protected int markpos = -1;
  5. protected int marklimit;
  • buf---是缓冲输入流中的缓冲区,即字节数组buf.
  • count---缓冲输入流里面的有效字节数.
  • pos---缓冲区当前的位置.
  • markpos---缓冲区标记的位置,当调用mark()方法的时候,会将缓冲区当前位置pos保存到markpos.
  • marklimit---表示的是mark()方法标记后再调用reset()方法之前最多读取的字节.

3.内部方法

  1. public synchronized int read() throws IOException {}
  2. public synchronized int read(byte b[], int off, int len) throws IOException {}
  3. public synchronized long skip(long n) throws IOException {}
  4. public synchronized int available() throws IOException {}
  5. public synchronized void mark(int readlimit) {}
  6. public synchronized void reset() throws IOException {}
  7. public boolean markSupported() {}
  8. public void close() throws IOException {}
  • read()---从缓冲区读取一个字节.
  • read(byte b[],int off,int len)---将缓冲区的数据读取到字节数组b的off位置开始,长度为len.
  • skip(long n)---跳过字节的数.
  • available()---从输入流中可读取字节总数的估算值.
  • mark(int readlimit)---对缓冲区当前位置pos,进行标记.并设置标记后可读取的最大值readlimit.
  • markSupported()---是否支持标记.
  • close()---关闭输入流.

BufferedOutputStream介绍

1.构造方法

  1. public BufferedOutputStream(OutputStream out) { this(out, 8192);}
  2. public BufferedOutputStream(OutputStream out, int size) {}
  • 无size的构造方法,创建默认大小为8192字节的缓冲输出流.
  • 有size的构造方法,创建指定大小size的缓冲输出流.

2.内部变量

  1. protected byte buf[];
  2. protected int count;
  • buf---缓冲输出流中缓冲区,即为一字节数组buf.
  • count----缓冲输出流中有效的字节数.

3.内部方法

  1. public synchronized void write(int b) throws IOException {}
  2. public synchronized void write(byte b[], int off, int len) throws IOException {}
  3. public synchronized void flush() throws IOException {}
  • write(int b)---将数据b写到缓冲数组buf中.
  • write(byte b[],int off, int len)---将字节数组b中off索引开始,长度为len个字节写到缓冲区中.
  • flush()---刷新缓冲区,将缓冲区里面的数据写到输出流中.

案例:

通过BufferedInputStream和BufferedOutputStream复制,效率极大提高.

  1. public class BufferedInputStreamDemo {
  2. public static void main(String[] args) throws IOException {
  3. File source = new File("D:\\软件.zip");
  4. File dest = new File("D:\\copy\\soft.zip");
  5. copyFile(source, dest);
  6. }
  7. private static void copyFile(File source, File dest) throws IOException {
  8. BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
  9. BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest));
  10. byte buffer[] = new byte[1024];
  11. int len = 0;
  12. while ((len = bis.read(buffer)) != -1) {
  13. bos.write(buffer);
  14. }
  15. bis.close();
  16. bos.close();
  17. }
  18. }

BufferedInputStream源码分析

  1. public class BufferedInputStream extends FilterInputStream {
  2. // 默认缓冲区的大小
  3. private static int DEFAULT_BUFFER_SIZE = 8192;
  4. // 缓冲字节数组可扩展到最大字节
  5. private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
  6. // 缓冲区为一个字节数组
  7. protected volatile byte buf[];
  8. // 返回的是一个原子更新器,保证字节数组buf操作是原子性的.
  9. private static final AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
  10. AtomicReferenceFieldUpdater.newUpdater(BufferedInputStream.class, byte[].class, "buf");
  11. // 缓冲区里面有效的字节数
  12. protected int count;
  13. // 缓冲区里面当前的位置索引
  14. protected int pos;
  15. // 缓冲区里面标记的位置,也就是最后一次调用mark()方法时,记录pos的值.调用reset()时会将pos值重置为markpos的值
  16. protected int markpos = -1;
  17. // marklimit是mark()标记后,在调用reset()之前可读取的最大的字节数,限制标记后buffer的最大值
  18. protected int marklimit;
  19. // 获取输入流,将输入流数据读取到缓冲区里面
  20. private InputStream getInIfOpen() throws IOException {
  21. InputStream input = in;
  22. if (input == null)
  23. throw new IOException("Stream closed");
  24. return input;
  25. }
  26. // 获取缓冲字节数组
  27. private byte[] getBufIfOpen() throws IOException {
  28. byte[] buffer = buf;
  29. if (buffer == null)
  30. throw new IOException("Stream closed");
  31. return buffer;
  32. }
  33. // 创建缓冲输入流,里面缓冲区是一字节数组,大小为默认的8192字节
  34. public BufferedInputStream(InputStream in) {
  35. this(in, DEFAULT_BUFFER_SIZE);
  36. }
  37. // 创建缓冲输入流,并且指定缓冲区的大小
  38. public BufferedInputStream(InputStream in, int size) {
  39. super(in);
  40. if (size <= 0) {
  41. throw new IllegalArgumentException("Buffer size <= 0");
  42. }
  43. buf = new byte[size];
  44. }
  45. // 调用fill()方法将输入流中数据填充到缓冲区里面.
  46. // 1.没标记的情况下,读完缓冲区数据,不再保存已被读取过的数据.
  47. // 2.有标记的情况下,
  48. // 1)标记的位置markpos>0,需要保存标记之后的数据,标签之前的数据可丢弃
  49. // 2)标记的位置为0,而marklimit小于buffer.length,标记失效
  50. // 3)标记的位置为0,而marklimit大于buffer.lenght,可以扩容buffer.
  51. private void fill() throws IOException {
  52. byte[] buffer = getBufIfOpen();
  53. if (markpos < 0)
  54. pos = 0; /* no mark: throw away the buffer */
  55. else if (pos >= buffer.length) /* no room left in buffer */
  56. if (markpos > 0) { /* can throw away early part of the buffer */
  57. int sz = pos - markpos;
  58. System.arraycopy(buffer, markpos, buffer, 0, sz);
  59. pos = sz;
  60. markpos = 0;
  61. } else if (buffer.length >= marklimit) {
  62. markpos = -1; /* buffer got too big, invalidate mark */
  63. pos = 0; /* drop buffer contents */
  64. } else if (buffer.length >= MAX_BUFFER_SIZE) {
  65. throw new OutOfMemoryError("Required array size too large");
  66. } else { /* grow buffer */
  67. int nsz = (pos <= MAX_BUFFER_SIZE - pos) ? pos * 2 : MAX_BUFFER_SIZE;
  68. if (nsz > marklimit)
  69. nsz = marklimit;
  70. byte nbuf[] = new byte[nsz];
  71. System.arraycopy(buffer, 0, nbuf, 0, pos);
  72. if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
  73. throw new IOException("Stream closed");
  74. }
  75. buffer = nbuf;
  76. }
  77. count = pos;
  78. int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
  79. if (n > 0)
  80. count = n + pos;
  81. }
  82. // 此方法表示的是从缓冲区里面获取一个字节
  83. public synchronized int read() throws IOException {
  84. // 当缓冲区数据全部被读取完,会先填充缓冲区
  85. if (pos >= count) {
  86. fill();
  87. if (pos >= count)
  88. return -1;
  89. }
  90. return getBufIfOpen()[pos++] & 0xff;
  91. }
  92. // 将缓冲区的数据写到字节数组b里面,off是字节数组b的起始位置,长度为len
  93. private int read1(byte[] b, int off, int len) throws IOException {
  94. int avail = count - pos;
  95. // 缓冲区没有可读数据,如果要读取的字节数长度大于缓冲区长度,且没有标记的情况下
  96. // 直接从输入流中读取字节
  97. if (avail <= 0) {
  98. if (len >= getBufIfOpen().length && markpos < 0) {
  99. return getInIfOpen().read(b, off, len);
  100. }
  101. // 否则调用fill()填充缓冲区
  102. fill();
  103. avail = count - pos;
  104. if (avail <= 0)
  105. return -1;
  106. }
  107. // 实际能够读取的长度是avail
  108. int cnt = (avail < len) ? avail : len;
  109. // 缓冲区当前位置pos,复制cnt长度到字节数组b中
  110. System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
  111. pos += cnt;
  112. return cnt;
  113. }
  114. // 将缓冲区的数据写到字节数组b里面,off是字节数组的起始位置,长度为len
  115. public synchronized int read(byte b[], int off, int len) throws IOException {
  116. getBufIfOpen(); // Check for closed stream
  117. if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
  118. throw new IndexOutOfBoundsException();
  119. } else if (len == 0) {
  120. return 0;
  121. }
  122. int n = 0;
  123. for (;;) {
  124. int nread = read1(b, off + n, len - n);
  125. //nread<=0,没有可读的字节数
  126. if (nread <= 0)
  127. return (n == 0) ? nread : n;
  128. n += nread;
  129. if (n >= len)
  130. return n;
  131. // if not closed but no bytes available, return
  132. InputStream input = in;
  133. if (input != null && input.available() <= 0)
  134. return n;
  135. }
  136. }

关于缓冲输入流填充方法fill().存在多种情况,分析如下:

  • count是从输入流读取数据填充到缓冲区有效字节数.
  • pos是从缓冲区读取字节数据的当前位置.
  • marklimit是调用mark()后,在调用reset()方法之前从缓冲区可读取的最大的字节数.

   1.缓冲区没有标记的情况下,即markpos<0时,缓冲区中已被读取过的字节不需要保存.即pos>=count时,会将pos和count重置为0.然后从输入流读取数据填充缓冲区0到buffer.length之间的位置.

                                                                  

   2.缓冲区标记的情况下.即markpos>=0情况下,

       1)当markpos>0的时,下次刷新缓冲的时候,需要将markpos到pos之间数据保留.从输入流中读取数据填充pos到buffer.length之间的位置.如下图,当markpos>0时,并且pos>=buffer.length.下次读取输入流的里面数据的时候,会将markpos到pos之间的数据保存.

                                                                

       2)当markpos=0的时候,buffer.length>marklimit,并且pos>buffer.length表示的是缓冲区里面没有剩余的位置.如果继续保留markpos到pos之间数据,那么刷新缓冲区之后,缓冲区依旧没有剩余位置,因为pos>=buffer.length.缓冲区没有空出位置,所以marklimit存在意义就是进行一个限定,根据buffer.length与marklimit大小来判断标记是否失效.源码中buffer.length>=marklimit的时候,标记失效.缓冲区里面0到buffer.length之间的位置是空的.可以进行填充.

       3)当markpos=0.buffer.length<marklimit,并且pos>buffer.length表示缓冲区没有剩余位置.则需要扩容.

如下,扩充后容量的是2*pos和marklimit中的最小值.扩容后缓冲区会保留原先0-pos之间的数据.然后从输入流继续读取数据.

                                                           

BufferedOutputStream源码分析

  1. public class BufferedOutputStream extends FilterOutputStream {
  2. // 缓冲输出流中的缓冲区为字节数组
  3. protected byte buf[];
  4. // 缓冲区有效的数据大小
  5. protected int count;
  6. // 创建默认大小为8192字节的缓冲输出流
  7. public BufferedOutputStream(OutputStream out) {
  8. this(out, 8192);
  9. }
  10. // 创建指定大小size的缓冲输出流
  11. public BufferedOutputStream(OutputStream out, int size) {
  12. super(out);
  13. if (size <= 0) {
  14. throw new IllegalArgumentException("Buffer size <= 0");
  15. }
  16. buf = new byte[size];
  17. }
  18. // 将缓冲区里面有效字节数写到输出流out中
  19. private void flushBuffer() throws IOException {
  20. if (count > 0) {
  21. out.write(buf, 0, count);
  22. count = 0;
  23. }
  24. }
  25. // 将数据b写到缓冲区里面
  26. public synchronized void write(int b) throws IOException {
  27. // 缓冲区已满情况下,会将数据写到输出流中
  28. if (count >= buf.length) {
  29. flushBuffer();
  30. }
  31. // 将字节b写到缓冲区里面
  32. buf[count++] = (byte) b;
  33. }
  34. // 将数据写入到缓冲区里面
  35. public synchronized void write(byte b[], int off, int len) throws IOException {
  36. // 如果写入的长度大于缓冲区长度,先刷新缓冲区,将缓冲区所有的数据写到输出流out去
  37. // 然后将要写入缓冲区的字节数组b,直接写到输出流
  38. if (len >= buf.length) {
  39. /*
  40. * If the request length exceeds the size of the output buffer, flush the output buffer and then write the data
  41. * directly. In this way buffered streams will cascade harmlessly.
  42. */
  43. flushBuffer();
  44. out.write(b, off, len);
  45. return;
  46. }
  47. // 如果写入缓冲区的数据长度大于缓冲区剩余的长度,将缓冲区里面数据写到输出流
  48. // 然后将字节数组b,起始位置off,长度为len数据写到缓冲区里面
  49. if (len > buf.length - count) {
  50. flushBuffer();
  51. }
  52. System.arraycopy(b, off, buf, count, len);
  53. count += len;
  54. }
  55. // 刷新缓冲区,将缓冲区里面数据写到输出流中
  56. public synchronized void flush() throws IOException {
  57. flushBuffer();
  58. out.flush();
  59. }
  60. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/147023?site
推荐阅读
相关标签
  

闽ICP备14008679号