background
BufferedInputStream and other inputstreams are often used together:
BufferedInputStream is set outside some other InputStream and plays the function of caching. It is used to improve the performance of the inner InputStream. It cannot exist independently of the inner InputStream.
For example, FileInputStream reads a file as InputStream. Therefore, you can set BufferedInputStream outside FileInputStream to improve the performance of FileInputStream.
Therefore, this article mainly explores what BufferedInputStream does?
Source code analysis
FileInputStream
package java.io; public class FileInputStream extends InputStream{ /** *Reads a byte from the input stream *This method is private and cannot be called directly. *This method is native because the Java language cannot directly interact with the operating system or computer hardware, *Access to disk data can only be achieved by calling local methods written in C/C + +. */ private native int read0() throws IOException; //Call the native method read0() to read one byte at a time public int read() throws IOException { Object traceContext = IoTrace.fileReadBegin(path); int b = 0; try { b = read0(); } finally { IoTrace.fileReadEnd(traceContext, b == -1 ? 0 : 1); } return b; } /** * Read multiple bytes from the input stream into the byte array * This method is also a private local method. It is not open to users and is only used internally. */ private native int readBytes(byte b[], int off, int len) throws IOException; //Call the native method readBytes(b, 0, b.length) to read multiple bytes at a time public int read(byte b[]) throws IOException { Object traceContext = IoTrace.fileReadBegin(path); int bytesRead = 0; try { bytesRead = readBytes(b, 0, b.length); } finally { IoTrace.fileReadEnd(traceContext, bytesRead == -1 ? 0 : bytesRead); } return bytesRead; } //Read up to len bytes of data into a byte array from this input stream. public int read(byte b[], int off, int len) throws IOException { Object traceContext = IoTrace.fileReadBegin(path); int bytesRead = 0; try { bytesRead = readBytes(b, off, len); } finally { IoTrace.fileReadEnd(traceContext, bytesRead == -1 ? 0 : bytesRead); } return bytesRead; } .... }
Let's focus on the non native read() method. If you use the read() method to read a file, you have to access the hard disk once for every byte you read. This reading method is very inefficient. Even if the read(byte b []) method is used to read multiple bytes at a time, the disk will be operated frequently when the read file is large.
BufferedInputStream
package java.io; public class BufferedInputStream extends FilterInputStream { //The default size of the buffer array is 8192Byte, which is 8K private static int defaultBufferSize = 8192; /** * Internal buffer array, which will be filled as needed. * The default size is 8192 bytes. You can also use the constructor to customize the size */ protected volatile byte buf[]; /** * The number of bytes in the buffer that have not been read * When count=0, it indicates that the contents of the buffer have been read and will be filled again */ protected int count; // Buffer pointer, which records the current read position of the buffer protected int pos; //InputStream is the one that actually reads bytes private InputStream getInIfOpen() throws IOException { InputStream input = in; if (input == null) throw new IOException("Stream closed"); return input; } //Create an empty buffer private byte[] getBufIfOpen() throws IOException { byte[] buffer = buf; if (buffer == null) throw new IOException("Stream closed"); return buffer; } //Create BufferedInputStream of default size public BufferedInputStream(InputStream in) { this(in, defaultBufferSize); } //This constructor can customize the buffer size public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; } /** * Fill buffer array * For the specific implementation of the algorithm, you can see the video of Mr. Bi Xiangdong. The explanation is very detailed, */ private void fill() throws IOException { byte[] buffer = getBufIfOpen(); if (markpos < 0) pos = 0; //.... Part of the source code is omitted count = pos; int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos; } /** * Read a byte * Unlike the read() method in FileInputStream, a byte is read from the buffer array * That is, directly from memory, the efficiency is much higher than the former */ public synchronized int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff; } //Reads more than one byte at a time from the buffer 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; } public synchronized int read(byte b[], int off, int len){ //In order to reduce the length of the article, the source code is not displayed } }
When BufferedInputStream is created, an internal buffer array is created. When reading the bytes in the stream, the internal buffer can be filled again from the included input stream as needed, filling multiple bytes at a time.
In other words, the Buffered class will create a large byte array during initialization, and fill the byte array by reading multiple bytes from the underlying input stream at one time. When the program reads one or more bytes, it can be obtained directly from the byte array. When the bytes in memory are read, it will fill the buffer array with the underlying input stream again.
This way of reading data from direct memory is much more efficient than accessing disk every time.
Decorator mode
Note that we see the construction method of BufferedInputStream:
protected volatile InputStream in; public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; }
Note that super(in), BufferedInputStream inherits from FileInputStream. In fact, when reading and writing, the InputSteam of the incoming constructor is called.
BufferedInputStream is the decorator class and FileInputStream is the decorated class. The former is used to strengthen the existing functions of the latter. Here, it is used to improve the reading and writing efficiency of the data stream.
Construction method definition of BufferedInputStream: public BufferedInputStream(InputStream in) can be seen that Buffered can decorate any subclass of InputSteam.
summary
FileInputStream is a byte stream and BufferedInputStream is a byte buffer stream. Using BufferedInputStream to read resources is more efficient than FileInputStream to read resources (BufferedInputStream's read method will read as many bytes as possible. When reading, read from the buffer first, and then fill the buffer when the buffer data is read.)
Therefore, when the amount of data read each time is very small, the FileInputStream is read from the hard disk every time, while the BufferedInputStream is mostly read from the buffer. The speed of reading memory is much faster than that of reading hard disk, so BufferedInputStream is efficient, and the read method of FileInputStream object will be blocked; The default buffer size for BufferedInputStream is 8192 bytes. When the amount of data read each time approaches or far exceeds this value, there is no significant difference in efficiency between the two.