Next to the previous article, we will continue to learn byte stream operations in Java.
Decorator buffered stream BufferedInput/OutputStream
The decorator stream is actually a file IO stream based on a design pattern "decorator mode", and our buffer stream is just one of them. Let's take a look.
Before this, we used file read and write streams FileInputStream and FileOutputStream both read and write from disk byte or byte, which is very time-consuming.
Our buffer stream can pre-read the number of bytes of a specified capacity from the disk at one time into memory, and the subsequent read operations will be read directly from the memory to improve efficiency. Let's take a look at the specific implementation of buffered streams:
Let’s take BufferedInputStream as an example first, let’s briefly mention its core properties:
buf is a byte array used for buffering reads. Its value will be filled continuously as the stream is read, and subsequent read operations can be directly based on this buffered array.
DEFAULT_BUFFER_SIZE specifies the size of the default buffer, that is, the array length of buf. MAX_BUFFER_SIZE specifies the upper limit of the buffer.
count points to the last valid byte index in the buffered array. pos Points to the next byte index position to be read.
markpos and marklimit are used to repeat read operations.
Next, let's take a look at several example constructors of BufferedInputStream:
public BufferedInputStream(InputStream in) { this(in, DEFAULT_BUFFER_SIZE);} public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size];}Overall, the former only needs to pass in an InputStream instance that is "decorated" and use a buffer of default size. The latter can explicitly indicate the size of the buffer.
In addition, super(in) saves this InputStream instance into the in attribute field of the parent class FilterInputStream, and all actual disk read operations are issued by this InputStream instance.
Let's take a look at the most important read operations and how the buffer is filled.
public synchronized int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff;}I believe everyone is already familiar with this method. It reads the next byte from the stream and returns it, but the implementation in details is still slightly different.
count points to the next position of the valid byte index in the buffered array, and pos points to the next position of the byte index to be read. In theory, pos cannot be greater than count, at most equal to.
If pos is equal to count, it means that all valid bytes in the buffer array have been read. At this time, the "useless" data in the buffer needs to be discarded and a batch of new data is reloaded from the disk to fill the buffer.
In fact, the fill method is what it does. It has a lot of code, so I won’t take you to parse it. If you understand its function, it is probably easy to analyze its implementation.
If pos is still equal to count after the fill method is called, it means that the InputStream instance has not read any data from the stream, that is, there is no data in the file stream to read. For this, see line 246 of fill method.
In general, if the buffer is successfully filled, our read method will take a byte directly from the buffer and return it to the caller.
Public synchronized int read(byte b[], int off, int len){ //.....}This method is also an "acquaintance", no longer has any unnecessary explanation, the implementation is similar.
The skip method is used to skip the number of bytes of a specified length for continued reading of the file stream:
public synchronized long skip(long n){ //......}One thing to note is that the skip method tries to skip n bytes, but it is not guaranteed to skip n bytes. The method returns the actual number of bytes skipped. If the remaining number of available bytes in the buffered array is less than n, the actual number of bytes that can be skipped in the buffered array will eventually be skipped.
Finally, let’s talk about this close method:
public void close() throws IOException { byte[] buffer; while ( (buffer = buf) != null) { if (bufUpdater.compareAndSet(this, buffer, null)) { InputStream input = in; in = null; if (input != null) input.close(); return; } // Else retry in case a new buf was CASed in fill() }}The close method will empty the "decorated" stream and call its close method to release relevant resources, which will eventually clear the memory space occupied by the buffer array.
BufferedInputStream provides read buffering capabilities, while BufferedOutputStream provides write buffering capabilities, that is, the memory write operations will not be updated to disk immediately, and will be temporarily saved in the buffer, and will be written together when the buffer is full.
protected byte buf[]; protected int count;
buf represents the internal buffer, and count represents the actual data capacity in the buffer, that is, the number of effective bytes in buf, rather than the length of the buf array.
public BufferedOutputStream(OutputStream out) { this(out, 8192);}public BufferedOutputStream(OutputStream out, int size) { super(out); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size];}With the same implementation idea, it is necessary to provide an OutputStream output stream instance, and the buffer size can also be optionally specified.
public synchronized void write(int b) throws IOException { if (count >= buf.length) { flushBuffer(); } buf[count++] = (byte)b;}The write method will first check whether the buffer can still accommodate this write operation. If a disk write operation cannot be initiated, all buffer data will be written to the disk file, otherwise the buffer will be written to the buffer first.
Of course, BufferedOutputStream also provides a flush method to provide an interface to the outside, that is, you don’t have to wait until the buffer is full before writing data to the disk. You can also explicitly call this method to clear the buffer and update the disk files.
public synchronized void flush() throws IOException { flushBuffer(); out.flush();}Regarding buffered streams, the core content is introduced as above. This is a stream that can significantly improve efficiency. Through it, the number of disk accesses can be reduced and program execution efficiency can be improved.
We will not discuss the object serialization stream ObjectInput/OutputStream and the decorator stream DataInput/OutputStream based on basic types. When we learn serialization, we will discuss these two byte streams again.
All codes, images, and files in the article are stored in the cloud on my GitHub:
(https://github.com/SingleYam/overview_java)
You can also choose to download locally.
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.