Java IO - PushbackInputStream&PushbackReader

原创 2016年05月31日 21:39:37

基本概念

这里将 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该类中加了同步代码块,只允许单线程访问。


版权声明:本文为博主原创文章,未经博主允许不得转载。

不断更新的庞大家族——Java IO

一、流的概念 在Java API中,可以从其中读入一个字节序列的对象称作输入流,而可以向其中写入一个字节序列的对象称作输出流。这些字节序列的来源地和目的地可以是文件,而且通常都是文件,但是也可以是网络...

IO:推回输出流PushbackInputStream和PushbackReader的用法

IO:推回输出流PushbackReader的用法: 下面的程序试图找出程序中的"new PushbackReader"字符串,找到后然后打印出目标字符串之前的内容     package n...

java.io.PushbackReader

1.  java.io.PushbackReader与前面提到的PushbackInputStream类似,都拥有一个PushBack缓冲区,只不过PushbackReader所处理的是字符。从这个对...

18.Java IO: FilterInputStream 、FilterOutputStream、PushbackInputStream

FilterInputStreamFilterInputStream是个基类,它为了实现你自己的过滤输入流。它基本上覆盖了所有InputStream里的方法,通过调用FilterInputStream...

Java-IO之PushbackInputStream

1、当程序调用推回输入流的unread()方法时,系统会把指定数组的内容推回到该缓冲区中,而推回输入流每次调用read()方法时,总是先从推回缓冲区读取内容,只有完全读取了推回缓冲区里的内容后,但是还...

Java IO之PushbackInputStream类

PushbackInputStream存在的意义就是允许我试探性的读取数据流,如果不是我们想要的则返还回去,之所以能够这样,因为其内部维护了一个pushback buffer缓冲区。构造函数可以指定返...

IO-PushBackInputStream、ObjectInputStream、ObjectOutputStream

PushBackInputStream回退流
  • yin_jw
  • yin_jw
  • 2014年07月24日 16:17
  • 497

JAVA基础—PushbackInputStream类简单介绍

缓存的新应用之一就是回推(pushback)的实现。回推用于输入流,以允许读取字节,然后再将它们返回(回推)到流中。PushbackInputStream类实现了这一思想,提供了一种机制,可以“偷窥”...

给你第二次机会”——小议PushbackInputStream

正文: jungleford如是说     PushbackInputStream和PushbackReader是Java I/O系统里两个比较让人迷惑的类,我以前对它(们)就不太了解,直到某一...
  • niu0147
  • niu0147
  • 2012年04月01日 13:20
  • 391

PushbackInputStream理解

PushbackInputStream存在的意义就是允许我试探性的读取数据流,如果不是我们想要的则返还回去,之所以能够这样,因为其内部维护了一个pushback buffer缓冲区。构造函数可以指定返...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java IO - PushbackInputStream&PushbackReader
举报原因:
原因补充:

(最多只允许输入30个字)