In the previous article, we introduced the abstract disk File type File, which is only used to abstract a disk File or directory, but does not have the ability to access or modify the contents of a File.

Java IO stream is a design for reading and writing file content, it can complete the disk file content output to memory or memory data output to disk file data transfer work.

The design of Java IO streams is not perfect. A large number of classes have been designed to increase our understanding of IO streams, but there are two main categories: byte streams for binary files and character streams for text files. In this article, we will first learn the principles and usage scenarios of the related types of byte streams. The specific stream types are as follows:

Base class Input/OutputStream

InputStream and OutputStream are the base classes for the byte read stream and the byte write stream, respectively. All byte related streams must inherit from either of them. As an abstract class, they also define the basic read and write operations.

Take InputStream as an example:

public abstract int read() throws IOException;
Copy the code

This is an abstract method that does not provide a default implementation and requires subclasses to implement it. This method returns the next byte of the current file for you.

Of course, you can also see that the return value of this method is received using the integer type “int”. Why not use “byte”?

First, the value returned by the read method must be an eight-bit binary, and an eight-bit binary can take on values in the “00000000,1111 1111” range [-128,127].

The read method also specifies that -1 is returned when the end of the file is read, i.e. the file has no next byte to read. So if byte is used as the return value type, when the method returns -1, should we determine whether this is the content of the data in the file or the end of the stream?

The first three bytes of an int are all zeros. We use only its lowest byte. When the end of the stream flag is encountered, the four bytes of -1 (32 ones) are returned, which is naturally distinguished from the data value -1 (24 zeros + 8 ones).

Next comes a read method, but InputStream provides the default implementation:

public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
}

public int read(byte b[], int off, int len) throws IOException{//Copy the code

The two methods are essentially the same. The first method is a special form of the second that allows an array of bytes to be passed in and requires the program to fill the array with bytes read from the file starting at index position 0.

The second method is more general and allows you to specify the starting position and total number of bytes.

There are a couple of other methods in InputStream, but they’re not really implemented, so I’ll leave it to subclasses, so let’s look at them.

  • Public long skip(long n) : Returns the actual number of bytes skipped
  • Public void close() : closes the stream and releases the corresponding resource
  • public synchronized void mark(int readlimit)
  • public synchronized void reset()
  • public boolean markSupported()

The mark method marks the read position of the current stream, and the reset method resets the read pointer to that mark.

In fact, it is not possible for a file to be read back from the reset point, and all bytes between the flag position and the reset point are temporarily saved. When the reset method is called, it is actually read repeatedly from the saved set of temporary bytes, so readlimit is used to limit the maximum cache capacity.

The markSupported method is used to determine whether the current stream supports this “fallback” read operation.

OutputStream and InputStream are similar, except that one writes and the other reads, and we won’t go over them here.

File byte stream FileInput/OutputStream

We’re still focusing on FileInputStream, and FileOutputStream is similar.

First, FileInputStream has the following constructors to instantiate an object:

public FileInputStream(String name) throws FileNotFoundException { this(name ! = null ? new File(name) : null); }Copy the code
public FileInputStream(File file) throws FileNotFoundException { String name = (file ! = null ? file.getPath() : null); SecurityManager security = System.getSecurityManager();if(security ! = null) { security.checkRead(name); }if (name == null) {
        throw new NullPointerException();
    }
    if (file.isInvalid()) {
        throw new FileNotFoundException("Invalid file path");
    }
    fd = new FileDescriptor();
    fd.attach(this);
    path = name;
    open(name);
}
Copy the code

The two constructors are essentially the same; the former is a special form of the latter. In fact, you don’t see the latter method body of a lot of code, most of it is just doing security checks, the core is an open method, used to open a file.

These two constructors, primarily, throw a FileNotFoundException if the file does not exist or if the file path and name are illegal.

Remember that the base InputStream class has an abstract method read that requires all subclasses to implement, whereas FileInputStream implements it using native methods:

public int read() throws IOException {
    return read0 (); } private native intread0() throws IOException;
Copy the code

The exact implementation of this read0 method is unknown, but you must be clear that the read method is used to return the next byte in the stream. If it returns -1, the end of the file has been read and there are no more bytes to read.

In addition, FileInputStream has several other read-related methods, but most of them are implemented using native methods. Here’s a quick look:

  • Public int read(byte b[]) : Reads b.length() bytes into the array
  • Public int read(byte b[], int off, int len) : Reads the specified length of bytes into the array
  • Public native long skip(long n) : Reads data by skipping n bytes
  • Public void close() : Releases stream resources

FileInputStream () ¶ FileInputStream () ¶ FileInputStream () ¶

public static void main(String[] args) throws IOException {
    FileInputStream input = new FileInputStream("C:\\Users\\yanga\\Desktop\\test.txt");
    byte[] buffer = new byte[1024];
    int len = input.read(buffer);
    String str = new String(buffer);
    System.out.println(str);
    System.out.println(len);
    input.close();
}
Copy the code

The output is simple, printing out the contents of the test file and the number of bytes actually read, but careful students will notice, how can you guarantee that the contents of the test file will not exceed 1024 bytes?

One solution is to define a buffer large enough to hold as much of the file as possible in order to read the entire contents of the file.

This approach is obviously undesirable because it is impossible to know the actual size of the file to be read, and creating an excessively large byte array is itself a poor solution.

The second approach is to use our dynamic byte array stream, which dynamically adjusts the size of the internal byte array to the appropriate size, as we’ll explain later.

Another thing to note about FileOutputStream is its constructors, which have the following two:

public FileOutputStream(String name, boolean append)

public FileOutputStream(File file, boolean append)
Copy the code

The append argument specifies whether the write to this stream is to overwrite or append, true for append and false for overwrite.

An array of bytes flow ByteArrayInput/OutputStream

A byte array stream is a stream that runs around a byte array and does not read or write to a file as other streams do.

A byte array stream is not a file-based stream, but it is still an important stream because the byte array it encapsulates is not fixed but dynamically scalable, which is often appropriate for certain scenarios.

ByteArrayInputStream is a read byte array stream that can be instantiated using the following constructor:

protected byte buf[];
protected int pos;
protected int count;

public ByteArrayInputStream(byte buf[]) {
    this.buf = buf;
    this.pos = 0;
    this.count = buf.length;
}

public ByteArrayInputStream(byte buf[], int offset, int length)
Copy the code

Buf is an array of bytes encapsulated inside a ByteArrayInputStream around which all read operations are performed.

Therefore, when instantiating a ByteArrayInputStream object, pass in at least one of the target byte arrays.

The pos property records the position read by the current stream, and count records the position after the last valid byte index of the destination byte array.

With this in mind, the various read methods for it are not difficult:

Public synchronized intread() {
    return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
Copy the code
Public synchronized int public synchronized intread(byte b[], int off, int len){Copy the code

In addition, ByteArrayInputStream is a very simple “repeat read” operation.

public void mark(int readAheadLimit) {
    mark = pos;
}

public synchronized void reset() {
    pos = mark;
}
Copy the code

Because ByteArrayInputStream is based on byte arrays, it’s easy to implement all repeated reads, just index-based implementations.

ByteArrayOutputStream ByteArrayOutputStream ByteArrayOutputStream ByteArrayOutputStream ByteArrayOutputStream ByteArrayOutputStream ByteArrayOutputStream ByteArrayOutputStream

First, these two attributes are required:

protected byte buf[]; // count represents the number of valid bytes in buf.Copy the code

The constructor:

public ByteArrayOutputStream() {
    this(32);
}
    
public ByteArrayOutputStream(int size) {
    if (size < 0) {
        throw new IllegalArgumentException("Negative initial size: "+ size);
    }
    buf = new byte[size];
}
Copy the code

The core task of the constructor is to initialize the internal byte array buF, which allows you to pass in size to explicitly limit the size of the initialized byte array, otherwise the default length will be 32.

Write to ByteArrayOutputStream from the outside:

public synchronized void write(int b) {
    ensureCapacity(count + 1);
    buf[count] = (byte) b;
    count += 1;
}

public synchronized void write(byte b[], int off, int len){
    if ((off < 0) || (off > b.length) || (len < 0) ||
            ((off + len) - b.length > 0)) {
            throw new IndexOutOfBoundsException();
        }
        ensureCapacity(count + len);
        System.arraycopy(b, off, buf, count, len);
        count += len;
}
Copy the code

See, the first step in any write operation is a call to the ensureCapacity method to ensure that the byte array in the current stream can hold the write operation.

If it is found that the internal buF cannot support the write operation, it calls the grow method to perform a capacity expansion. The principle of expansion is similar to ArrayList’s implementation, expanding to twice the original capacity.

In addition, ByteArrayOutputStream also has a writeTo method:

public synchronized void writeTo(OutputStream out) throws IOException {
    out.write(buf, 0, count);
}
Copy the code

Write our internally encapsulated byte array to an output stream.

The remaining methods are also commonly used:

  • Public synchronized byte toByteArray()[] : Returns an internally encapsulated byte array
  • Public synchronized int size() : returns the valid number of buf bytes
  • Public synchronized String toString() : Returns the corresponding String of the array

Note that although these two streams are called “streams”, they do not allocate resources in the same way that real streams do, so we do not need to call their close method.

I won’t show the test cases, but LATER I will upload all the code cases used in this article, so you can download them yourself.

In order to control the length, MORE than dirty study, in the next article.


All the code, images and files in this article are stored in the cloud on my GitHub:

(https://github.com/SingleYam/overview_java)

Welcome to wechat public number: jump on the code of Gorky, all articles will be synchronized in the public number.