Java IO stream learning summary three: buffered stream - BufferedInputStream, BufferedOutputStream

Java IO stream learning summary three: buffered stream - BufferedInputStream, BufferedOutputStream

Java IO stream learning summary 1: input and output streams
Java IO stream learning summary 2: File
Java IO stream learning summary three: buffered stream - BufferedInputStream, BufferedOutputStream
Java IO Stream Learning Summary IV: Buffered Streams - BufferedReader, BufferedWriter
Java IO stream learning summary five: conversion stream - InputStreamReader, OutputStreamWriter
Java IO stream learning summary six: ByteArrayInputStream, ByteArrayOutputStream
Java IO stream learning summary seven: Commons IO 2.5-FileUtils

2021 Java Okio - a more efficient and easy-to-use IO library

Inheritance Diagram

InputStream
|__FilterInputStream
        |__BufferedInputStream

First of all, a question is thrown, why should there be BufferedInputStream with InputStream?

The two classes BufferedInputStream and BufferedOutputStream are subclasses of FilterInputStream and FilterOutputStream respectively. As decorator subclasses, using them can prevent the actual write operation every time data is read/sent, representing the use of buffers.

It is necessary to know that the operation without buffering requires writing a byte every time a byte is read. Since the IO operation involving the disk is much slower than the operation of the memory, the stream without buffering is very inefficient. With a buffered stream, many bytes can be read at a time, but not written to disk, just put into memory first. When the buffer size is enough, write to the disk at one time. This method can reduce the number of disk operations and improve the speed a lot!

At the same time, because they implement the buffering function, it should be noted that after using BufferedOutputStream to write data, the flush() method or the close() method should be called to force the data in the buffer to be written out. Otherwise the data may not be written out. There are two similar classes, BufferedReader and BufferedWriter.

The question posed at the beginning of this article can now be answered:

The BufferedInputStream and BufferedOutputStream classes are the input stream/output stream that implements the buffering function. Using buffered input and output streams is more efficient and faster.

Summarize:

BufferedInputStream is the buffered input stream. it inherits from FilterInputStream. 

BufferedInputStream The role of is to add some functionality to another input stream, for example, to provide "buffering capabilities" and support mark()mark and reset()reset method.

BufferedInputStream Essentially implemented via an internal buffer array. For example, when creating a new input stream corresponding to BufferedInputStream After that, when we pass read()When reading data from the input stream, BufferedInputStream The data of the input stream will be filled into the buffer in batches. Each time the data in the buffer is read, the input stream fills the data buffer again; this repeats until we finish reading the input stream data position.

Introduction to the BufferedInputStream API

Source code key field analysis

private static int defaultBufferSize = 8192;//Built-in cache byte array size 8KB
	
protected volatile byte buf[];	//Built-in cached byte array
	
protected int count;	//The total number of bytes in the current buf, note that it is not the total number of bytes in the source of the underlying byte input stream
	
protected int pos;		//The next byte index to be read in the current buf
	
protected int markpos = -1;	//The position of the next byte to be read in buf recorded by the last call to the mark(int readLimit) method
	
protected int marklimit;	//After calling mark, before the subsequent call to reset() method fails, the maximum amount of data that Yunxun reads from in, which is used to limit the maximum value of the buffer after being marked

Constructor

BufferedInputStream(InputStream in) //Build bis with default buf size, underlying byte input stream 
BufferedInputStream(InputStream in, int size) //Build bis with the specified buf size and the underlying byte input stream  

General method introduction

int available();  //Returns the number of bytes available for reading from the source corresponding to the underlying stream      
  
void close();  //closes this stream, releasing all resources associated with this stream  
  
boolean markSupport();  //Check if this stream supports mark
  
void mark(int readLimit); //Mark the subscript of the next byte read in the current buf  
  
int read();  //read next byte in buf  
  
int read(byte[] b, int off, int len);  //read next byte in buf  
  
void reset();   //Reset the position in the buf marked by the last call to mark  
  
long skip(long n);  //skip n bytes, not only valid bytes in buf, but also bytes in the source of in 

Introduction to the BufferedOutputStream API

key field

protected byte[] buf;   //Built-in cache byte array, used to store the bytes that the program wants to write to out  
  
protected int count;   //The total number of bytes existing in the built-in cache byte array 

Constructor

BufferedOutputStream(OutputStream out); //Construct bos with default size, underlying byte output stream. The default buffer size is 8192 bytes (8KB)
  
BufferedOutputStream(OutputStream out, int size);  //Constructs a bos using the specified size, underlying byte output stream  

Constructor source code:

/**
  * Creates a new buffered output stream to write data to the
  * specified underlying output stream.
  * @param   out   the underlying output stream.
  */
 public BufferedOutputStream(OutputStream out) {
     this(out, 8192);
 }

 /**
  * Creates a new buffered output stream to write data to the
  * specified underlying output stream with the specified buffer
  * size.
  *
  * @param   out    the underlying output stream.
  * @param   size   the buffer size.
  * @exception IllegalArgumentException if size <= 0.
  */
 public BufferedOutputStream(OutputStream out, int size) {
     super(out);
     if (size <= 0) {
         throw new IllegalArgumentException("Buffer size <= 0");
     }
     buf = new byte[size];
 }

general method

//Mention here, BufferedOutputStream does not have its own close method. When it calls the method of the parent class FilterOutputStrem to close, it will indirectly call the flush method implemented by itself to flush the remaining bytes in buf to out, and then out.flush( ) into the destination, and so does the DataOutputStream.

void  flush();  will write bos data in flus arrive out In the specified destination, note that this is not flush arrive out , because it internally calls out.flush()  
  
write(byte b);      write a byte to buf middle  
  
write(byte[] b, int off, int len);      Will b part of the write buf middle 

So when does flush() work?
The answer is: when the OutputStream is a BufferedOutputStream.

When the effect of flush() is required to write a file, it is required
FileOutputStream fos = new FileOutputStream("c:\a.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
That is to say, you need to pass in FileOutputStream as a parameter of the BufferedOutputStream constructor, and then write to BufferedOutputStream to use buffering and flush().

Looking at the source code of BufferedOutputStream, it is found that the so-called buffer is actually a byte[].
Each write of BufferedOutputStream actually writes the content to byte[]. When the buffer capacity reaches the upper limit, a real disk write will be triggered.
Another way to trigger disk writes is to call flush().

1.BufferedOutputStream will automatically flush when close()
2.BufferedOutputStream needs to call flush only when the buffer is not full without calling close(), and the content of the buffer needs to be written to a file or sent to other machines through the network.

Practical exercise 1: Copy files.
Operation: Use the cache stream to copy the name in the root directory of the F drive: 123.png to abc.png

package com.app;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;


public class A3 {

	public static void main(String[] args) throws IOException {

		String filePath = "F:/123.png" ;
		String filePath2 = "F:/abc.png" ;
		File file = new File( filePath ) ;
		File file2 = new File( filePath2 ) ;
		copyFile( file , file2 );

	}
	
	/**
	 * copy file
	 * @param oldFile
	 * @param newFile
	 */
	public static void copyFile( File oldFile , File newFile){
		InputStream inputStream = null ;
		BufferedInputStream bufferedInputStream = null ;

		OutputStream outputStream = null ;
		BufferedOutputStream bufferedOutputStream = null ;

		try {
			inputStream = new FileInputStream( oldFile ) ;
			bufferedInputStream = new BufferedInputStream( inputStream ) ;

			outputStream = new FileOutputStream( newFile ) ;
			bufferedOutputStream = new BufferedOutputStream( outputStream ) ;

			byte[] b=new byte[1024];   //Represents a read of up to 1KB of content at a time

			int length = 0 ; //Represents the number of bytes actually read
			while( (length = bufferedInputStream.read( b ) )!= -1 ){
				//length represents the number of bytes actually read
				bufferedOutputStream.write(b, 0, length );
			}
            //Write the contents of the buffer to the file
			bufferedOutputStream.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}catch (IOException e) {
			e.printStackTrace();
		}finally {

			if( bufferedOutputStream != null ){
				try {
					bufferedOutputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if( bufferedInputStream != null){
				try {
					bufferedInputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			if( inputStream != null ){
				try {
					inputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			if ( outputStream != null ) {
				try {
					outputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

		}
	}
}

Effect picture:

How to properly close a stream

In the above code, our code to close the stream is written like this.

 finally {

		if( bufferedOutputStream != null ){
			try {
				bufferedOutputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		if( bufferedInputStream != null){
			try {
				bufferedInputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		if( inputStream != null ){
			try {
				inputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		if ( outputStream != null ) {
			try {
				outputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

Thinking: Do we still need to close the node stream after the processing stream is closed?

Let's take a look at the source code with the question:

bufferedOutputStream.close();

   /**
     * Closes this input stream and releases any system resources
     * associated with the stream.
     * Once the stream has been closed, further read(), available(), reset(),
     * or skip() invocations will throw an IOException.
     * Closing a previously closed stream has no effect.
     *
     * @exception  IOException  if an I/O error occurs.
     */
    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 role of the close() method
1. Close the input stream and release system resources
2. BufferedInputStream decorates an InputStream to have a buffering function. To close it, it only needs to call the close() method of the object that is finally decorated, because it will eventually call the close() method of the real data source object. Therefore, it is possible to just call the close method of the outer stream to close its decorated inner stream.

So if we want to close the streams one by one, how can we do it?

The answer is: turn off the outer flow first, then turn off the inner flow. In general, it is: open first and then close, and then open and close first; another case: look at the dependencies, if stream a depends on stream b, you should close stream a first, and then close stream b. For example, the processing flow a depends on the node flow b, and the processing flow a should be closed first, and then the node flow b should be closed.

After understanding how to close the stream correctly, then we can optimize the above code and only close the outer processing stream.

finally {

		if( bufferedOutputStream != null ){
			try {
				bufferedOutputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		if( bufferedInputStream != null){
			try {
				bufferedInputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

Tags: Java Interview

Posted by scriptkiddie on Sat, 22 Oct 2022 09:51:27 +1030