Java IO - PushbackInputStream&PushbackReader

基本概念

这里将 PushbackInputStream 和 PushbackReader 放到一起讨论主要是二者的原理机制几乎一模一样,掌握其一即可。它们分别表示 字节推回流、字符推回流。

退回:其实就是将从流读取的数据再推回到流中。实现原理也很简单,通过一个缓冲数组来存放推回的数据,每次操作时先从缓冲数组开始,然后再操作流对象。

继承关系:

这里写图片描述

这里写图片描述


实例探究

比如下面的代码中将[ a,b,c,d] 推回流,也可以将 [x,y,z,u] 推回流,那么下一次的读取结果就变成了 [x,y,z,u,h,i,j]。

public class Test {

    // 英文字母
    private static final byte[] ArrayLetters = { 
        0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 
        0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
        0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 
        0x76, 0x77, 0x78, 0x79,0x7A };

    public static void main(String args[]) throws Exception {
        read();
    }

    private static void read() throws IOException {

        //创建字节推回流,缓冲区大小为 7
        PushbackInputStream pis = new PushbackInputStream(new ByteArrayInputStream(ArrayLetters),7);

        byte[] buffer = new byte[7];

        //从流中将7个字节读入数组,如 a,b,c,d,e,f,g
        pis.read(buffer);
        System.out.println(new String(buffer));

        //从数组的第一个位置开始,推回 4 个字节到流中,即推回 a,b,c,d
        pis.unread(buffer,0,4);

        //重新从流中将字节读取数组,输出 a,b,c,d,h,i,j
        pis.read(buffer);
        System.out.println(new String(buffer));
    }
}

原理讲解

①创建字节推回流,推回缓冲区指定大小为 7;要从字节输入流读取字节到 buffer 数组

  • 缓冲区

这里写图片描述

  • 输入流,这里以上面的代码为例子,字节数组输入流中有 26 个字节(偷懒只画 8 个)

这里写图片描述

  • buffer 数组

这里写图片描述


②从流中读取 7 个字节到 buffer数组

  • 缓冲区

这里写图片描述

  • 输入流

这里写图片描述

  • buffer 数组

这里写图片描述


③现在要从 buffer 的 0 下标(第一个位置)开始,推回 4 个字节到流中去(实质是推回到缓冲数组中去)。

  • 缓冲区,字节被推回到缓冲区,从末端开始存放

这里写图片描述

  • 输入流

这里写图片描述

  • buffer 数组

这里写图片描述


④再次进行读取操作,首先从缓冲区开始读取,再对流进行操作

  • 缓冲区

这里写图片描述

  • 输入流 ,后面的数据没有画出来

这里写图片描述

  • buffer 数组

这里写图片描述


源码分析

1.PushbackInputStream

类结构图

这里写图片描述


成员变量

// 推回缓冲区,推回流的字节会被保存在这里
protected byte[] buf;

// 推回缓冲区中的索引位置,默认从 buf.length 开始,即从数组末尾写入数据
protected int pos;

构造函数,它真正只做了两件事:

  • 获取要操作(要过滤)的流

  • 创建推回缓冲数组

//①构造函数,创建指定大小的退回缓冲区
public PushbackInputStream(InputStream in, int size) {

    //获取要操作的流
    super(in);

    if (size <= 0) {
        throw new IllegalArgumentException("size <= 0");
    }

    //创建推回缓冲区,索引位置默认为 size;
    this.buf = new byte[size];
    this.pos = size;
}

//②构造函数,创建默认大小为 1 的退回缓冲区
public PushbackInputStream(InputStream in) {
    this(in, 1);
}

read 方法,这里只定义了两种读取方式。

// ①从此输入流中读取下一个数据字节
public int read() throws IOException {
    // 确保要操作的流不为空(即没有关闭),下面会提到
    ensureOpen();


    // 上面说一开始这两个参数相等,不相等说明缓冲数组有数据,即表明了进行过推回操作
    if (pos < buf.length) {
        // 默认先从缓冲数组读取数据
        return buf[pos++] & 0xff;
    }

    //调用流本身的读取方法
    return super.read();
}


// ②从此输入流将最多 len 个数据字节读入 byte 数组
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;
    }

    //判断 avail 是否为 0,不为 0 表示缓冲数组有推回的字节
    int avail = buf.length - pos;

    //缓冲区有数据,默认从缓冲区先读取
    if (avail > 0) {

        //判断要读取的字节数量是否小于缓冲区的字节数量
        if (len < avail) {
            avail = len;
        }

        //利用数组复制,将缓冲区的数据读入该数组
        System.arraycopy(buf, pos, b, off, avail);

        pos += avail;
        off += avail;
        len -= avail;
    }

    //如果要读取的字节数量超出缓冲数组的字节数量,继续从流读取剩余的字节
    if (len > 0) {
        len = super.read(b, off, len);

        //提前到达 I/O 流末尾,则返回
        if (len == -1) {
            return avail == 0 ? -1 : avail;
        }

        //返回读取的字节数量 = 从缓冲区读取的字节数 + 从流读取的字节数 
        return avail + len;
    }

    return avail;
}


//确保要操作的流不为空(即没有关闭)
private void ensureOpen() throws IOException {
    if (in == null) {
        throw new IOException("Stream closed");
    }
}

unread 方法,即推回操作,这里定义了 3 种推回方式。

// ①推回一个字节
public void unread(int b) throws IOException {

    ensureOpen();

    //上面说过从数组末尾写入数据,为 0,表示缓冲区已满
    if (pos == 0) {
        throw new IOException("Push back buffer is full");
    }

    //往缓冲区添加字节,并减少索引位置
    buf[--pos] = (byte) b;
}

// ②推回 byte 数组的某一部分
public void unread(byte[] b, int off, int len) throws IOException {

    ensureOpen();

    //判断要推回的字节数量大于剩余缓冲容量
    if (len > pos) {
        throw new IOException("Push back buffer is full");
    }

    //修改缓冲区的索引位置
    pos -= len;

    //通过数组复制方式,将数组中的字节 "推回" 到缓冲区
    System.arraycopy(b, off, buf, pos, len);
}

// ③推回一个 byte 数组 
public void unread(byte[] b) throws IOException {
    unread(b, 0, b.length);
}

skip 方法

public long skip(long n) throws IOException {

    ensureOpen();

    if (n <= 0) {
        return 0;
    }

    long pskip = buf.length - pos;

    //先判断缓冲区是否有字节数据可以跳过
    if (pskip > 0) {
        if (n < pskip) {
            pskip = n;
        }
        pos += pskip;
        n -= pskip;
    }

    //n 比换缓冲的字节数大的话,继续从流里面跳跃
    if (n > 0) {
        pskip += super.skip(n);
    }
    return pskip;
}

剩余方法

public int available() throws IOException {
    ensureOpen();

    //还要加上推回缓冲区的字节
    return (buf.length - pos) + super.available();
}


public boolean markSupported() {
    return false;
}


public synchronized void mark(int readlimit) {
}


public synchronized void reset() throws IOException {
    throw new IOException("mark/reset not supported");
}


public synchronized void close() throws IOException {
    if (in == null) {
        return;
    }

    in.close();
    in = null;
    buf = null;
}

2.PushbackReader

类结构图,观察结构图我们可以发现与 PushbackInputStream 的结构几乎一致。不同的是

  • 将字节数组换乘字符数组

  • ready 方法对应 PushbackInputStream 的 available 方法

这里写图片描述

因为实现原理一致,这里不做探究。唯一有较大不同的是 readunread该类中加了同步代码块,只允许单线程访问。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oxf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值