java IO笔记(PushbackInputStream)

 

本篇讲述的是java io包中的PushbackInputStream类。我们知道,通常情况下我们从流中读取数据时都是顺序操作的,也许流中的数据并不都是我们需要的,按照平常的流,我们要做的是就是将流中的数据依读取取出,并对取出的数据进行筛选,不符合条件的数据就丢弃。PushbackInputStream流则稍有不同,它通过内部的一个缓存从而支持了数据的回推,在上述场景下,当遇到不需要的数据时,PushbackInputStream还可以将数据重新推回到流中,下面先附上源码进行简单的分析。
PushbackInputStream:

package java.io;

public class PushbackInputStream extends FilterInputStream {
    /**
     * 定义了一个byte型数组,作为流中读取数据是的临时缓存,PushbackInputStream中的push back回推功能的实现,主要就是依赖这个缓存数组来实现的。
     */
    protected byte[] buf;

    /**
     * 定义了一个int型数值,该值表示实现回退功能的缓存数组中下一个要读取的字节数据的位置。
     */
    protected int pos;

    /**
     * 该方法用于确定当前流是否保持开启状态,如果当前流未开启则抛出对应异常。
     */
    private void ensureOpen() throws IOException {
        if (in == null)
            throw new IOException("Stream closed");
    }

    /**
     * 一个带两个参数的构造方法,第一个参数为一个InputStream对象,第二个参数为一个int型变量,该变量决定了内部缓存数组buf的容量大小。
     */
    public PushbackInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("size <= 0");
        }
        this.buf = new byte[size];
        this.pos = size;
    }

    /**
     * 带有一个参数的构造方法,内部其实是调用上面带两个参数的构造方法,内部缓存数组容量默认为1。
     */
    public PushbackInputStream(InputStream in) {
        this(in, 1);
    }

    /**
     * 每次读取一个字节的数据。从方法中可以看出,优先读取回推缓存中的数据,如果回推缓存中没有数据,那么就正常调用InputStream对应的read方法。
     */
    public int read() throws IOException {
	//在读取数据前先确定当前流处于开启状态
        ensureOpen();
	//pos在初始化时其值等于回推缓存的容量大小,如果pos小于buf.length,则表示回推缓存中已经存在回推的数据,那么读取数据时,先从回推缓存中取数据。
        if (pos < buf.length) {
	    //这里有个小操作&0xfff,也许有人不知道为什么,一个byte型&0xff不就是其本身吗,这里牵扯到了在计算机中数据存储的问题,在计算机中,数据存储都是使
	    //二进制的补码进行存储的,当我们从流中取出一个字节的数据后,当转换成int型是,计算机会自动将其高位用1补位,这时,从二进制数据本身来看,它已经
	    //被改变了,&0xff这样可以保证其低8位的数据位不发生变化,其高位都是0,这样就保证了数据的一致性。
            return buf[pos++] & 0xff;
        }
        return super.read();
    }

    /**
     * 一次读取多个字节的read方法,其包含三个参数,第一个为承载读取的数据的字节数组,第二个参数为从数组的哪个位置开始存储数据,第三个参数则是要进行存储的
     * 数据长度。
     */
    public int read(byte[] b, int off, int len) throws IOException {
	//在读取数据前,先确定当前流状态是否保持开启状态。
        ensureOpen();
	//对传入的参数进行安全检测,如果不符要切的则进行相应的操作(抛出异常或者是直接返回)。
        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型变量avail,用来接收buf.length-pos的值,该值实际表示了在回推缓存中实际存在的数据数量。
        int avail = buf.length - pos;
	//avail>0,表示回推缓存中存在数据。
        if (avail > 0) {
	    //len<avail,表示所要读取的字节数量小于回推缓存中的数据数量,那么此时将avail的值置为len。
            if (len < avail) {
                avail = len;
            }
	    //将回推缓存中指定长度的数据拷贝到传入的字节数组中去。
            System.arraycopy(buf, pos, b, off, avail);
	    //pos+avail表示下一次从回推缓存中取数据的位置。
            pos += avail;
	    //off+avail表示传入的字节数组下一次存储的位置。
            off += avail;
	    //len-avail表示剩余还需要写入的数据长度。
            len -= avail;
        }
	//接下来是正常情况下直接从流中读取多个字节的数据。len大于0表示还需要向传入的字节数组中传入数据。
        if (len > 0) {
	    //直接调用父类读取多个字节的read方法,从流中直接读取。
            len = super.read(b, off, len);
	    //len=-1,表示流中已经没有数据可读,此时判断avail的是否为0,即判断是否从回推缓存中读取过数据,如果没有则直接返回-1,否则返回avail。
            if (len == -1) {
                return avail == 0 ? -1 : avail;
            }
	    //该种情况是同时从流中和回推缓存中读取了数据,所以此时需要返回avial+len的值,从两个地方读取的数据总和才是真正读取的数据总量
            return avail + len;
        }
	//该种情况是回推缓存已经能满足读取数据的要求,此时只需返回avail值即可。
        return avail;
    }

    /**
     * 一次可以回推一个字节数据的unread方法,带一个int型参数,改参数表示了要回推的数据。
     */
    public void unread(int b) throws IOException {
	//在进行回推操作前,要确保当前是处于开启状态。
        ensureOpen();
	//pos等于0,表示回推缓存中已经没有足够的容量再放置回推的数据内容,所以此时抛出对应的异常。
        if (pos == 0) {
            throw new IOException("Push back buffer is full");
        }
	//将传入的数据放置与回推缓存中的相应位置。
        buf[--pos] = (byte)b;
    }

    /**
     * 一次可以回推多个字节数据的unread方法,带有三个参数,第一个参数为一个byte型数组,用于存放所要进行回推操作的数据,第二和第三个参数都是一个int型数值,
     * 分别代表从传入的数组中取出数据的起点,以及其需要回推的数据长度。
     */
    public void unread(byte[] b, int off, int len) throws IOException {
	//在进行回推操作前,需要先确定当前流是否依然处于开启状态。
        ensureOpen();
	//如果len>pos,则表示要进行回推操作的数据长度已经超过了内置的回推缓存数组的容量,那么此时将抛出对应的异常。
        if (len > pos) {
            throw new IOException("Push back buffer is full");
        }
	//该种情况下表示数据可以正常回推到内置的回推缓存区之中。
        pos -= len;
	//利用System.arraycopy方法将指定的数据拷贝到内置的回推缓存区之中。
        System.arraycopy(b, off, buf, pos, len);
    }

    /**
     * 一次可以回推多个字节数据的unread方法,该方法带有一个byte数组型参数,其内部实质是调用上面带3个参数的unread方法,将传入的数组的所有数据都回推到内置
     * 回推缓存中去。
     */
    public void unread(byte[] b) throws IOException {
        unread(b, 0, b.length);
    }

    /**
     * 该方法返回当前流中可以读取的数据总量。
     */
    public int available() throws IOException {
	//进行操作前先确定当前流是否处于开启状态。
        ensureOpen();
	//定义了一个int型变量n用来存方法buf.length-pos的值,该值表达了内置回推缓存中已经回推的数据数量。
        int n = buf.length - pos;
	//定义了一个int型变量avail用来存放调用父类方法available的返回值,该值表达了当前流中可还可以进行读取操作的数据数量。
        int avail = super.available();
	//判断回推的数据数量+流中可以读取的数据数量是否大于Integer.MAX_VALUE,如果大于则返回Integer.MAX_VALUE,否则返回两者之和。
        return n > (Integer.MAX_VALUE - avail)
                    ? Integer.MAX_VALUE
                    : n + avail;
    }

    /**
     * 该方法用于跳过指定的字节数,最后返回实际跳过的字节数。
     */
    public long skip(long n) throws IOException {
	//进行操作前确定当前流处于开启状态。
        ensureOpen();
	//如果传入的参数小于等于0,直接返回0,表示
        if (n <= 0) {
            return 0;
        }
	//定义了一个long型数据pskip,初始时赋值为buf.length-pos,该值代表着内置缓存区中实际推回的数据长度。
        long pskip = buf.length - pos;
	//pskip>0,表示内置的缓存区中实际存在着回推的数据。
        if (pskip > 0) {
	    //如果需要跳过的字节数n小于回推缓存区中回推的字节数,将pskip置为n。
            if (n < pskip) {
                pskip = n;
            }
	    //将回推缓存区读取位置后移pskip的长度,并且将需要跳过的字节数减去pskip。
            pos += pskip;
            n -= pskip;
        }
	//如果回推缓存区的数据不足以满足跳过的长度,那么还需调用父类流中的skip方法。并且将pskip累加上跳过的字节数。
        if (n > 0) {
            pskip += super.skip(n);
        }
        return pskip;
    }

    /**
     * 该方法用于判别当前流是否支持流标记功能,该方法返回false,所以表示当前流不支持标记功能。
     */
    public boolean markSupported() {
        return false;
    }

    /**
     * 该方法用于在流中制定位置留下标记,因为当前流中不支持流标记功能,所以这里是空实现。
     */
    public synchronized void mark(int readlimit) {
    }

    /**
     * 该方法用于将读取位置重置到流中标记的位置,因为当前流不支持流标记功能,所以此处直接抛出相应异常。
     */
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

    /**
     * 该方法用于关闭当前流,并将内置的回推缓存置为null。
     */
    public synchronized void close() throws IOException {
        if (in == null)
            return;
        in.close();
        in = null;
        buf = null;
    }
}

通过上面对源码的简单分析,我们对PushbackInputStream有了简单的了解,下面用一个简单的例子来阐述该类的用法:

 

package PushBackIO;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PushbackInputStream;

public class PushBackIOTest {
	public static void main(String[] args) {
		int data;
		try (PushbackInputStream pis = new PushbackInputStream(
				new FileInputStream(new File("./src/file/testcopy.txt")));
				PrintStream ps = new PrintStream(System.out);) {
			while ((data = pis.read()) != -1) {
				if (data =='1') {
					pis.unread(data);
					System.out.println("\r\nunread data :" + (char)data);
					pis.read();
				}
				ps.write(data);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

执行上述代码后可以看到如下效果:

 



以上为本篇的全部内容。

 


posted @ 2017-10-14 22:03  moonfish  阅读(314)  评论(0编辑  收藏  举报