Java IO笔记(PushbackInputStream)


(最近刚来到简书平台,以前在CSDN上写的一些东西,也在逐渐的移到这儿来,有些篇幅是很早的时候写下的,因此可能会看到一些内容杂乱的文章,对此深感抱歉,以下为正文)


正文

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

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();
        }
    }
}

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


运行结果

以上为本篇的全部内容。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,423评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,147评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,019评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,443评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,535评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,798评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,941评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,704评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,152评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,494评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,629评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,295评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,901评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,978评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,333评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,499评论 2 348

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,092评论 1 32
  • 1、IO流 1.1、概述 之前学习的File类它只能操作文件或文件夹,并不能去操作文件中的数据。真正保存数据的是文...
    Villain丶Cc阅读 2,660评论 0 5
  • 一. Java基础部分.................................................
    wy_sure阅读 3,805评论 0 11
  • 前提 参考资料: 《Java I/O》 -- 这本书没有翻译版,需要自己啃一下。 《Java I/O》这本书主要介...
    zhrowable阅读 1,164评论 0 1
  • tags:io categories:总结 date: 2017-03-28 22:49:50 不仅仅在JAVA领...
    行径行阅读 2,171评论 0 3