但行好事  莫问前程

java io(二) 推回输入流的原理和简单使用

一. 点睛

业务中使用到流的相关知识,想专门说说java的推回输入流,因为它们在javaio体系里面比较特殊。我们使用输入流从磁盘,网络或者其它的物理介质读取数据都是顺序读取的,在流的内部会维护一个指针,读取数据的同时,指针会向后移动,直到读完为止。

这时候会有个疑问,如果读出来的数据不是我想要的又不能再放回去怎么办?可以使用io提供的推回输入流。使用普通IO输入流如果读取到不想要的数据,只能在程序里面处理掉,而使用IO里面的推回输入流读取数据则可以把数据给推回到输入流的缓冲区中,这也是推回输入流名字中"PushBack"的来源。下面我举个简单的例子来说明问题,不一定准确,但体会含义很重要。

假设你有两个桶,装满水的叫A,空桶叫B,现在你需要用瓢把A里面的水盛装到B,如果现在有一瓢水你不想马上倒入B中,但是不允许瓢里有水,也不允许你再倒回A中去,怎么办?有朋友说找第三个桶C,把水先倒入C。说的很好,这第三个桶C就是普通IO输入流和推回输入流的核心区别,普通的IO流只允许使用桶A和桶B,而推回输入流则允许你使用桶C,由于有了桶C,那么每次盛水你都要先检查C里面是否有水,有的话就要先把C里面的水盛完,然后再去原来的桶A里面盛。这里的桶C在推回输入流里面的名字叫做推回缓冲区。使用推回输入流调用read()方法读取数据的时候,总是先从推回缓冲区里面读取,只有完全读完了推回缓冲区的数据之后才会从原来的输入流中读取。

推回输入流有两个,分别是字节推回输入流PushbackInputStream和字符推回输入流PushbackReader,它们都提供了三个把数据推回缓冲区的方法,如下所示:
PushbackInputStream字节推回输入流:

 //将一个字节的内容推回到推回缓冲区
 public void unread(int b) throws IOException 

 //将一个字节数组的内容推回到推回缓冲区
 public void unread(byte[] b) throws IOException

//将一个字节数组从off位置开始,长度为len字节的内容推回到推回缓冲区
 public void unread(byte[] b,int off,int len) throws IOException

PushbackReader字符推回输入流:

 //将一个字符的内容推回到推回缓冲区
 public void unread(int b) throws IOException 

 //将一个字符数组的内容推回到推回缓冲区
 public void unread(char[] b) throws IOException

//将一个字符数组从off位置开始,长度为len字符的内容推回到推回缓冲区
 public void unread(char[] b,int off,int len) throws IOException

从上面二者的方法声明可以看出,二者基本一致,但是由于字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符,所以PushbackInputStream方法的参数是字节数组,而PushbackReader方法的参数是字符数组。

二. 示例

例子一:

下面使用推回输入流PushbackReader读取6个字符的数据,然后每次都将读取到的数据推回到推回缓冲区,接下来继续读取数据的时候发现是先从推回缓冲区里面读的。实现步骤如下:
1. 在工作目录下新建文本文件repeatRead.txt,内容如下所示:

2.编写代码

package org.light4j.io.pushBack;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;

/**
 * 模拟重复读取数据
 * 
 * @author longjiazuo
 *
 */
public class RepeatReadTest {
    public static void main(String[] args) {

        try 
        (
                // 创建字符输入流,读取pushback.txt的数据
                FileReader fr = new FileReader("repeatRead.txt");
                // 创建字符推回输入流,
                PushbackReader pr = new PushbackReader(fr,12);
        ) 
        {
            // 新建字符数组,大小为6个字节
            char[] cbuff = new char[6];
            int hasRead = 0;

            // 循环读取数据
            while ((hasRead = pr.read(cbuff)) > 0) {
                // 当前读取的内容,把其转换为字符串表示
                String content = new String(cbuff, 0, hasRead);
                //打印输出的字符串
                System.out.println(content);

                //把内容推回到推回缓冲区
                pr.unread(content.toCharArray());// ①

                //继续读取数据,此处实际上是从缓冲区里面读取的
                if((hasRead = pr.read(cbuff)) > 0){
                    //输出
                    System.out.println(new String(cbuff, 0,hasRead));
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }
}

代码解释:

1. 注释掉处的代码pr.unread(content.toCharArray()),运行程序可以看到正常的打印输出,结果如下所示:

2. 处的代码pr.unread(content.toCharArray())注释去掉,运行程序可以看到数据会打印两次,其中一次是从推回缓冲区里面读的,结果如下所示:

例子二:

下面使用字符推回输入流PushbackReader举例说明推回输入流的实际应用,业务含义是:查找程序中"public class"字符串,找到之后只是打印目标串之前的内容。

package org.light4j.io.pushBack;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;

/**
 * PushbackReader推回输入流测试
 * 
 * <p>
 * 查找指定字符串,并输出其前面部分的内容
 * </p>
 * 
 * @author longjiazuo
 * 
 */
public class PushbackReaderTest {
    public static void main(String[] args) {
        // 获取工作目录
        String workDir = System.getProperty("user.dir");
        // 获取类名
        String className = Thread.currentThread().getStackTrace()[1].getClassName();
        // 类的路径
        String classPath = workDir + "\\src\\main\\java\\" + className;
        // 反斜杠替换掉"."
        String currentClass = classPath.replace(".", "\\") + ".java";

        try 
        (
                // 创建字符输入流,目标文件是当前类本身
                FileReader fr = new FileReader(currentClass);
                // 创建字符推回输入流,
                //需要设置缓冲区的大小,设置为字符数组的两倍或者更大,不然报Pushback buffer overflow异常
                PushbackReader pr = new PushbackReader(fr,112);// ①
        ) 
        {
            // 新建字符数组
            char[] cbuff = new char[56];
            int hasRead = 0;

            // 保存上次读取的内容
            String lastContent = "";

            // 循环读取数据
            while ((hasRead = pr.read(cbuff)) > 0) {
                // 当前读取的内容,把其转换为字符串表示
                String currentContent = new String(cbuff, 0, hasRead);
                // 保存目标索引的值
                int targetInde = 0;
                // 内容为上次读取的内容和当前读取内容的拼装
                String content = lastContent + currentContent;
                // 判断是否包含字符串"public class"
                targetInde = content.indexOf("public class");
                // 如果包含字符串"public class"
                if (targetInde > 0) {
                    // 把内容推回到推回缓冲区,内容包含上次内容和本次读取内容之和
                    pr.unread(content.toCharArray());
                    // 读取前面length个字符的内容,防止数组溢出
                    int length = targetInde > 56 ? 56 : targetInde;
                    // 再次读取内容,读取目标串之前的内容,实际上是先从推回缓冲区里面读
                    pr.read(cbuff, 0, length);
                    System.out.println(new String(cbuff, 0, length));
                    // 退出
                    System.exit(0);
                } else {
                    // 打印上次读取到的内容
                    System.out.println(lastContent);
                    // 将本次内容设置为上次读取的内容
                    lastContent = currentContent;
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }
}

代码解释:

① 使用推回缓冲区的时候需要设置推回缓冲区的大小,如果不设置则默认为1,有可能会导致Pushback buffer overflow异常

三. 源代码示例

github地址:点击查看
码云地址:点击查看

打赏
欢迎关注人生设计师的微信公众账号
公众号ID:longjiazuoA

未经允许不得转载:人生设计师 » java io(二) 推回输入流的原理和简单使用

分享到:更多 ()

人生设计师-接受不同的声音

联系我关于我