Java 7 Source Analysis Article 13 - Byte Input and Output Streams

Keywords: Java encoding less

Original Link: http://www.cnblogs.com/riasky/p/3507581.html

The previous article introduced the Java class framework for Byte Input and Output Streams, but also briefly introduced the role of each class. Here's a detailed look at how these classes implement these functions.


1. InputStream and OutputStream


The source code for the InputStream class is as follows:

 

public abstract class InputStream implements Closeable {

    private static final int MAX_SKIP_BUFFER_SIZE = 2048;//The maximum number of bytes that can be skipped

    // Gets the next byte of data and returns the int value (range 0-255), or -1 if the stream ends
    public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {//Reads a byte and returns the byte read
        return read(b, 0, b.length);
    }
    //Read len bytes and place them in the following off start byte array b, returning the number of bytes actually read
    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }
        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;
        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }
    //Read pointer skips n bytes unread and returns the number of bytes actually skipped
    public long skip(long n) throws IOException {
        long remaining = n;
        int nr;
        if (n <= 0) {
            return 0;
        }
        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                break;
            }
            remaining -= nr;
        }
        return n - remaining;
    }

    // The return value is the number of bytes in the stream that have not been read, and this method should be overridden by subclasses
    public int available() throws IOException {
        return 0;
    }

    public void close() throws IOException {}
    // Record the location of the current pointer.
    // The readlimit parameter indicates that the position of the pointer marked after the readlimit bytes read by the read pointer is valid.
    public synchronized void mark(int readlimit) {}
    //Repoint the read pointer to the location recorded with the mark method
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }
    //Does the current stream support recording of read pointers
    public boolean markSupported() {
        return false;
    }

}

To explain the mark() and reset() methods, see the following figure.


Specific classes are explained in detail.

Here's the source code for InputStream, as follows:

 

public abstract class OutputStream implements Closeable, Flushable {
    /**
       The byte to be written is the eight low-order bits of the argument b. The 24
     * high-order bits of b are ignored.
     */
    public abstract void write(int b) throws IOException;
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

    public void write(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }
    }

    public void flush() throws IOException {    }
    public void close() throws IOException {    }

}

As above is mainly to write bytes to the output stream, the specific write method (int b) is an abstract method, depending on the implementation of the specific implementation class.

 


2. PipedInputStream and ipedOutputStream

     

PipedInputStream class and PipedOutputStream class are used to create pipeline communication in the application. A PipedInputStream instance object must be connected to a PipedOutputStream instance object to create a communication pipeline. PipedOutputStream can write data to the pipeline, and PipedInputStream can read PipeThe data that dOutputStream writes to the pipeline. These two classes are mainly used to complete communication between threads. A thread's PipeedInputStream object can read data from another thread's PipeedOutputStream object.
The implementation of PipedInputStream and PipedEdOutputStream is similar to the "producer-consumer" principle. PipedOutputStream is the producer, PipedInputStream is the consumer, and there is a buffer byte array in PipedInputStream with a default size of 1024. As a buffer, it stores what the "producer" produces.Variables in and out.In is used to record how much the "producer" produces, out is used to record how much the "consumer" consumes, in is -1 to indicate that the consumption is exhausted, in==out to indicate that the production is full. When the consumer has nothing to consume, that is, when in is -1, the consumer waits until there is something to consume.
In their constructors, each provides a method for connecting to each other, receiving each other's pipe instances, and then calling their own connect() methods to connect, such as PipedInputStream:

 

    //  PipedInputStream
    public PipedInputStream(PipedOutputStream src, int pipeSize)
            throws IOException {
         initPipe(pipeSize);
         connect(src);
    }
    private void initPipe(int pipeSize) {
         if (pipeSize <= 0) {
            throw new IllegalArgumentException("Pipe Size <= 0");
         }
         buffer = new byte[pipeSize];
    }
    public void connect(PipedOutputStream src) throws IOException {
        src.connect(this);
    }

You can also specify the size of the buffer, see PipedOutputStream:

 

 

private PipedInputStream sink;
    public PipedOutputStream(PipedInputStream snk)  throws IOException {
        connect(snk);
    }
    public PipedOutputStream() {    }
    //  PipedOutputStream
    public synchronized void connect(PipedInputStream snk) throws IOException {
        if (snk == null) {
            throw new NullPointerException();
        } else if (sink != null || snk.connected) {
            throw new IOException("Already connected");
        }
        sink = snk;
        snk.in = -1;
        snk.out = 0;
        snk.connected = true;
    }

There is no rule on who to connect to, as long as the connection is made, the effect is the same.Look at the write() method in the output pipeline as follows:

 

 public void write(int b)  throws IOException {
        if (sink == null) {
            throw new IOException("Pipe not connected");
        }
        sink.receive(b);
    }
    public void write(byte b[], int off, int len) throws IOException {
        if (sink == null) {
            throw new IOException("Pipe not connected");
        } else if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        sink.receive(b, off, len);
    }

The method calls the receive method in the output pipeline after it has written to the byte[] array cache data.The receive() method in the output pipeline is as follows:

/**
     * Receives a byte of data.  This method will block if no input is
     * available.
     */
    protected synchronized void receive(int b) throws IOException {
        checkStateForReceive();
        writeSide = Thread.currentThread();
        if (in == out)//in==out implies the buffer is full
            awaitSpace();
        if (in < 0) {//Input pipeline has no data
            in = 0;
            out = 0;
        }
        buffer[in++] = (byte)(b & 0xFF);
        if (in >= buffer.length) {
            in = 0;// Buffer is full, wait for next write from scratch
        }
    }

    /**
     * Receives data into an array of bytes.  This method will
     * block until some input is available.
     */
    synchronized void receive(byte b[], int off, int len)  throws IOException {
        checkStateForReceive();
        writeSide = Thread.currentThread();
        int bytesToTransfer = len;
        while (bytesToTransfer > 0) {
            if (in == out)
                awaitSpace();
            int nextTransferAmount = 0;
            if (out < in) {
                nextTransferAmount = buffer.length - in;
            } else if (in < out) {
                if (in == -1) {
                    in = out = 0;
                    nextTransferAmount = buffer.length - in;
                } else {
                    nextTransferAmount = out - in;
                }
            }
            if (nextTransferAmount > bytesToTransfer)
                nextTransferAmount = bytesToTransfer;
            assert(nextTransferAmount > 0);
            System.arraycopy(b, off, buffer, in, nextTransferAmount);
            bytesToTransfer -= nextTransferAmount;
            off += nextTransferAmount;
            in += nextTransferAmount;
            if (in >= buffer.length) {
                in = 0;
            }
        }
    }

Once input management receives data through the corresponding method above and saves it to the input buffer, the following can be read out using the read() method, as follows:

 

    public synchronized int read()  throws IOException {
        if (!connected) {
            throw new IOException("Pipe not connected");
        } else if (closedByReader) {
            throw new IOException("Pipe closed");
        } else if (writeSide != null && !writeSide.isAlive()
                   && !closedByWriter && (in < 0)) {
            throw new IOException("Write end dead");
        }

        readSide = Thread.currentThread();
        int trials = 2;
        while (in < 0) {
            if (closedByWriter) {
                /* closed by writer, return EOF */
                return -1;
            }
            if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
                throw new IOException("Pipe broken");
            }
            /* might be a writer waiting */
            notifyAll();
            try {
                wait(1000);
            } catch (InterruptedException ex) {
                throw new java.io.InterruptedIOException();
            }
        }
        int ret = buffer[out++] & 0xFF;
        if (out >= buffer.length) {
            out = 0;
        }
        if (in == out) {
            /* now empty */
            in = -1;
        }
        return ret;
    }

    /**
     * Reads up to len bytes of data from this piped input stream into an array of bytes. Less than len bytes
     * will be read if the end of the data stream is reached or if len exceeds the pipe's buffer size.
     * If len  is zero, then no bytes are read and 0 is returned;otherwise, the method blocks until
     * at least 1 byte of input is available, end of the stream has been detected, or an exception is
     */
    public synchronized int read(byte b[], int off, int len)  throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }
        /* possibly wait on the first character */
        int c = read();
        if (c < 0) {
            return -1;
        }
        b[off] = (byte) c;
        int rlen = 1;
        while ((in >= 0) && (len > 1)) {
            int available;
            if (in > out) {
                available = Math.min((buffer.length - out), (in - out));
            } else {
                available = buffer.length - out;
            }
            // A byte is read beforehand outside the loop
            if (available > (len - 1)) {
                available = len - 1;
            }
            System.arraycopy(buffer, out, b, off + rlen, available);
            out += available;
            rlen += available;
            len -= available;

            if (out >= buffer.length) {
                out = 0;
            }
            if (in == out) {
                /* now empty */
                in = -1;
            }
        }
        return rlen;
    }

Here is an example:

public class test04 {
    public static void main(String [] args) {  
        Sender sender = new Sender();  
        Receiver receiver = new Receiver();  
          
        PipedOutputStream outStream = sender.getOutStream();  
        PipedInputStream inStream = receiver.getInStream();  
        try {  
            //inStream.connect(outStream); //As in the next sentence  
            outStream.connect(inStream);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        sender.start();  
        receiver.start();  
    }  
}  
  
class Sender extends Thread {  
    private PipedOutputStream outStream = new PipedOutputStream();  
    public PipedOutputStream getOutStream() {  
        return outStream;  
    }  
    public void run() {  
        String info = "hello, receiver";  
        try {  
            outStream.write(info.getBytes());  
            outStream.close();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}  
  
class Receiver extends Thread {  
    private PipedInputStream inStream = new PipedInputStream();  
    public PipedInputStream getInStream() {  
        return inStream;  
    }  
    public void run() {  
        byte[] buf = new byte[1024];  
        try {  
            int len = inStream.read(buf);  
            System.out.println("receive message from sender : " + new String(buf, 0, len));  
            inStream.close();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }     
}  

The result of the final run is as follows: receive message from sender: hello, receiver

 


3. ByteArrayInputStream and ByteArrayOutputStream

Let's start with the write() method in the ByteArrayOutputStream class:

 

 // Writes the specified byte to this byte array output stream.
    public synchronized void write(int b) {
        ensureCapacity(count + 1);
        buf[count] = (byte) b;
        count += 1;
    }
    /**
     * Writes len bytes from the specified byte array
     * starting at offset off to this byte array output stream.
     */
    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;
    }

This class is also passed numerically by assigning values to the array, and the size of the byte array can be specified by the constructor when creating the ByteArrayOutputStream, with a default size of 32. If the write() method is called, the capacity of the byte array is guaranteed.If it is too small, it will expand automatically.This allows you to write the data you need into a byte array.You can also write to other output streams by calling the writeTo() method with the following source code:

 

 

 /**
     * Writes the complete contents of this byte array output stream to
     * the specified output stream argument, as if by calling the output
     * stream's write method using out.write(buf, 0, count).
     */
    public synchronized void writeTo(OutputStream out) throws IOException {
        out.write(buf, 0, count);
    }


Continue with the ByteArrayInputStream class, which reads data from byte arrays with the following source code:

 

 

 public synchronized int read() {
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }

    public synchronized int read(byte b[], int off, int len) {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        }

        if (pos >= count) {
            return -1;
        }

        int avail = count - pos;
        if (len > avail) {
            len = avail;
        }
        if (len <= 0) {
            return 0;
        }
        System.arraycopy(buf, pos, b, off, len);
        pos += len;
        return len;
    }

There are also some other methods available, such as skip() methods that can skip bytes, avaible() methods that can see the remaining valid bytes, and so on, which you can look at yourself if you are interested.Here is an example of a specific application, as follows:

 

 

byte[] bytes = { 0,2, 3, 4, 5 };
		try (ByteArrayOutputStream out = new ByteArrayOutputStream();
			 ByteArrayInputStream in = new ByteArrayInputStream(bytes);){
			out.write(bytes);
			System.out.println(out.size());//5
			System.out.println(in.read());//solution
			in.skip(1);//2
			in.mark(4);
			System.out.println(in.read());//3
			in.reset();// Restart reading from index 2
			System.out.println(in.read());//3 
			System.out.println(in.read());
			
		} catch (IOException e) {
			e.printStackTrace();
		}


4,StringBufferInputStream

This class is not advocated now. I think it is due to coding.Look at the source code after this class, as follows:

 

 public synchronized int read() {
        return (pos < count) ? (buffer.charAt(pos++) & 0xFF) : -1;
    }

     public synchronized int read(byte b[], int off, int len) {
        if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        }
        if (pos >= count) {
            return -1;
        }
        if (pos + len > count) {
            len = count - pos;
        }
        if (len <= 0) {
            return 0;
        }
        String  s = buffer;
        int cnt = len;
        while (--cnt >= 0) {
            b[off++] = (byte)s.charAt(pos++);
        }

        return len;
    }

 

It is found that this class actually converts a character in a string to a byte for reading. If all the characters of this string can be represented by ISO-8859-1 encoding, it will be read normally.But usually Java is unicode, two characters, so if there are Unicode characters in it, such as Chinese, the reading will be inaccurate.Examples include the following:

 

String str = "Ma Zhi AB";
		StringBufferInputStream st = new StringBufferInputStream(str);
		byte[] j = new byte[16];
		st.read(j);
		System.out.println(new String(j)); //lzAB

You may also know that two read() methods, after acquiring the character of this string (s.charAt()), force the conversion to byte or to sum with 0xff, which only results in an 8-bit lower encoding. For two-byte coded Chinese characters, there must be an error.

 

 





















 

 

































 





















 

 

Reprinted at: https://www.cnblogs.com/riasky/p/3507581.html

Posted by Bad HAL 9000 on Mon, 22 Jul 2019 09:53:58 -0700