Personal blog portal

Mark/reset

Android Glide 3.7.0 source code parsing (7), elaborate graphics transform and decoding is mentioned RecyclableBufferedInputStream for mark (int marklimit) and reset () method, This paper is to explore the concrete implementation ideas

mark(int marklimit)The markPos () function creates a section of the stream whose starting point is markPos and length is markLimit that can be read repeatedly when calledreset()Method returns the read position of the stream to markPos, so that markLimit can be read repeatedly

Note:

  • When the read position reaches readPos_2,reset()The method will fail because it is out of the markLimit range
  • mark(int marklimit)The starting point of the mark is markPos, which is the read position of the stream when the method is called (in the figure above, mark is readPos_0, so markPos == readPos_0).

We all know that a Stream can only be read once

Second, implementation ideas

The diagram above is only a view of the black box operation route, the following analysis of the specific implementation of the diagramDue to theInputStreamIt can only be read once and consumed, so to do that we have to use an external tool, in this casebufAn array of bytes [] with an initial size of 64K

  • The right-hand side is still thereInputStreamUnread data in
  • On the left is the data that has been read from the process and existsbuf
  • countRepresents thebufThe amount of valid data in (you have reached the end of the stream, which may be generatedbufReading dissatisfaction)
  • markposRepresents the position of the mark() tag
  • The blue stripeIt needs to support the parts that are read repeatedly,marklimitThat’s its length
  • posRepresents the position that is currently being read

Now that we have BUF as a cache, we can cache Mark’s stream data in BUF so that it can be read repeatedly

Note: BuF size is not constant, buF is doubled when marklimit > buf.length and some other conditions are met

Third, source code analysis

And see what RecyclableBufferedInputStream variables (actually told it introduces the train of thought of the time)

public class RecyclableBufferedInputStream extends FilterInputStream {
  	
  	// buF caches data read from the stream
    private volatile byte[] buf;
    // Buf specifies the length of valid data
    private int count;
    // mark Indicates the length of the data
    private int marklimit;
    // Mark mark position, default -1 indicates no mark
    private int markpos = -1;
    // The current position of reading buF
    private int pos;
    
}
Copy the code

Mark () and reset ()

Take a look at the Mark and reset methods

	public synchronized void mark(int readlimit) {
        marklimit = Math.max(marklimit, readlimit);
        markpos = pos;
    }
	
	public synchronized void reset(a) throws IOException {
        if (buf == null) {
            throw new IOException("Stream is closed");
        }
        if (-1 == markpos) {
            throw new InvalidMarkException("Mark has been invalidated");
        }
        pos = markpos;
    }
Copy the code

Mark just does two things: assignment + assignment

  • Compare the marklimit with the old one to get a maximum value
  • Markpos is set to the current read position

Reset did just one thing

  • The current position pos is reset to Markpos

read()

Let’s see what the read method does

	public synchronized int read(a) throws IOException {
        // Use local refs since buf and in may be invalidated by an
        // unsynchronized close()
        byte[] localBuf = buf;
        InputStream localIn = in;
        if (localBuf == null || localIn == null) {
            throw streamClosed();
        }

        // Are there buffered bytes available?
        if (pos >= count && fillbuf(localIn, localBuf) == -1) {
            // no, fill buffer
            return -1;
        }
        // localBuf may have been invalidated by fillbuf
        if(localBuf ! = buf) { localBuf = buf;if (localBuf == null) {
                throwstreamClosed(); }}// Did filling the buffer fail with -1 (EOF)?
        if (count - pos > 0) {
            return localBuf[pos++] & 0xFF;
        }
        return -1;
    }
Copy the code

The read() method reads a single character and returns only one byte at a time

  • Pos >= count indicatesbufWhen it is finished, call fillbuf(localIn, localBuf) to reorientbufFill in the data
  • Count-pos > 0 indicatesbufIf the data is enough, return localBuf[pos++] & 0xFF

Where return is an int? Int (0, 255); int (0, 255); int (0, 255); LocalBuf [pos++] & 0xFF, which returns 0 255 for all 24 bits

The fillbuf function is implemented in fillbuf

fillbuf()

It’s a long code, so be patient

	private int fillbuf(InputStream localIn, byte[] localBuf)
            throws IOException {
		// There is no mark, or the read area is out of the mark range, reading data directly from the stream to buF
        if (markpos == -1 || pos - markpos >= marklimit) {
            // Mark position not set or exceeded readlimit
            int result = localIn.read(localBuf);
            if (result > 0) {
                markpos = -1;
                pos = 0;
                count = result;
            }
            return result;
        }
        
        // [mark valid] buf. Length is not enough
        if (markpos == 0 && marklimit > localBuf.length && count == localBuf.length) {
            // Increase buffer size to accommodate the readlimit
            int newLength = localBuf.length * 2;
            if (newLength > marklimit) {
                newLength = marklimit;
            }
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "allocate buffer of length: " + newLength);
            }
            byte[] newbuf = new byte[newLength];
            System.arraycopy(localBuf, 0, newbuf, 0, localBuf.length);
            localBuf = buf = newbuf;
        } 

		// [mark valid] mark position >0 BuF has a batch of data that can be discarded before the mark position
		else if (markpos > 0) {
            System.arraycopy(localBuf, markpos, localBuf, 0, localBuf.length
                    - markpos);
        }
        // Reset the read position and the length of valid data in buF
        pos -= markpos;
        count = markpos = 0;
        // If the BUF is not satisfied (and there is dirty data) start reading the population from the stream
        int bytesread = localIn.read(localBuf, pos, localBuf.length - pos);
        count = bytesread <= 0 ? pos : pos + bytesread;
        return bytesread;
    }
Copy the code

There are three cases

  • The first type: Mark is not involved or mark is invalid (markpos == -1 || pos - markpos >= marklimit), it is normal to read data from the stream, fill the BUF, and reset the current read position of pos.

  • (markpos == 0 && marklimit > localbuf. length && count == localbuf. length) I’m at the far left of buF; And marklimit exceeds the length of buF; Count == localbuf. length Is doubled

    • seecount == localBuf.lengthBuf only reads at the end of the file. When all streams are read, there is no data left. Even if the marklimit exceeds the buF’s length, there is no need to expand the file.
    • Look at these lines of code
    int newLength = localBuf.length * 2;
    if (newLength > marklimit) {
        newLength = marklimit;
    }
    Copy the code

    If it is larger than marklimit after being expanded by 2 times, marklimit shall be used as the criterion; otherwise, it will only be expanded by 2 times and will not be expanded to the size of Marklimit all at once. The use of memory can be said to be extremely tight (it can be interpreted as lazy application, and it will apply for a larger BUF only when it is used).

Marklimit < buf.length * 22 times The length that exceeds the marklimit, use the marklimit length directly. The green part in the figure shows the capacity expansion

Marklimit >= buf.length * 2

  • The third case markpos! = 0 and does not meet expansion conditions

In this case, you just need to clear the data to the left of markpos and then write data to the left of BUFAt this point,fillbuf()The analysis is finished, one is divided into three cases to fillbufIt’s all illustrated above

read(byte[] buffer, int offset, int byteCount)

The function is long, but the logic is relatively simple

public synchronized int read(byte[] buffer, int offset, int byteCount) throws IOException {
        // A series of error checks
        byte[] localBuf = buf;
        if (localBuf == null) {
            throw streamClosed();
        }
        
        if (byteCount == 0) {
            return 0;
        }
        InputStream localIn = in;
        if (localIn == null) {
            throw streamClosed();
        }

		// Read buf directly
        int required;
        if (pos < count) {
            // There are bytes available in the buffer.
            int copylength = count - pos >= byteCount ? byteCount : count - pos;
            System.arraycopy(localBuf, pos, buffer, offset, copylength);
            pos += copylength;
            if (copylength == byteCount || localIn.available() == 0) {
            	// buf is just enough to read, and returns directly
                return copylength;
            }
            offset += copylength;
            // Not enough to read how much more is needed
            required = byteCount - copylength;
        } else {
        	// Buf is already unreadable, calculate how much more is needed
            required = byteCount;
        }

		// Start the loop until you read enough
        while (true) {
            int read;
            // Without mark, read directly from the stream itself
            if (markpos == -1 && required >= localBuf.length) {
                read = localIn.read(buffer, offset, required);
                if (read == -1) {
                	// The stream returns when it has finished reading
                    return required == byteCount ? -1: byteCount - required; }}else {
            	// Start filling the buf array with fillbuf()
                if (fillbuf(localIn, localBuf) == -1) {
                    return required == byteCount ? -1 : byteCount - required;
                }
                // localBuf may have been invalidated by fillbuf
                if(localBuf ! = buf) { localBuf = buf;if (localBuf == null) {
                        throwstreamClosed(); }}// Start reading
                read = count - pos >= required ? required : count - pos;
                System.arraycopy(localBuf, pos, buffer, offset, read);
                pos += read;
            }
            required -= read;
            // If there are enough numbers, return them directly
            if (required == 0) {
                return byteCount;
            }
            // If the stream has run out of data, return the read size
            if (localIn.available() == 0) {
                returnbyteCount - required; } offset += read; }}Copy the code

If there is no mark, read from the stream directly. Otherwise, fillbuf fills the buF and reads from buf again until the stream is exhausted or the desired number is read out of the loop