Java IO (二),常见的输入/输出流

661 查看

字节流和字符流

InputStream和Reader

InputStream和Reader两个抽象类是所有输入流的基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板.他们的方法是所有输入流都可用的方法.

InputStream中包含如下三个方法

  • int read() 从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转 换为int类型).
  • int read(byte[] b) 从输入流中读取最多b.lenght个字节数据,并将其存贮在数组b中,返回实际读取的字节数.
  • int read(byte[] b,int off,int len) 从输入流中读取最多的len个字符的数据,并存在数组b中,放入数组b中时,从off的位置开始,返回实际读取的字节数.

Reader中包含如下三个方法

  • int read() 从输入流中读取单个字符,返回所读取的字符数据(字符数可以直接转换为int类型)
  • int read(char[] cbuf) 从输入流中读取最多的cbuf.length个字符数据,并将其存储在字符数组cbuf中,返回实际读取的字符数.
  • int read(char cbuf,int off,int len) 从输入流中读取最多len个字符的数据,并将其存在cbuf中,从off位置开始,返回实际的读取的字符数.

  • 使用FileInputStream读取自身

javapublic void readFile() throws IOException {
    //创建字节输入流
    FileInputStream fis = new FileInputStram("FileInputTest.java");
    //创建一个长度为1024的缓冲数组
    byte[] bbuf = new byte[1024];
    //保存实际读取的字节数
    int hasRead = 0;
    //循环读取文件内容
    while ((hasRead = fis.read(bbuf)) > 0) {
       System.out.println(new String(bbuf,0,hasRead));
    }
    //文件IO资源不属于内存资源
    //垃圾回收无法回收该资源
    //必须显式关闭
    fis.close();
}
  • 使用FileReader读取文件自身
public void readFile() throws IOException {
    FileReader fr = new FileReader("FileReaderTest.java");
    char[] cbuf = new char[32];
    int hasRead = 0;
    while ((hasRead = fr.read(cbuf)) > 0) {
        System.out.println(new String(cbuf,0,hasRead));
    }
    fr.clse();
}

此外,InputStream和Reader还支持如下几个方法来支持指针的移动
void mark(int readAheadLimit) 在记录指针当前位置记录一个标记(mark).
boolean markSupported() 判断此输入流是否支持mark()操作
void reset() 将此流的记录指针重新定位到上一次记录标记的位置(mark).
long skip(long n) 记录指针向前移动n个字节/字符.

OutputStream和Writer

OutputStream和Writer也非常相似,两个流都提供了如下三个方法:

  • void write(int c) 将指定的字节/字符输出到输出流中,其中c既可以代表字节,也可以代表字符.
  • void write(byte[]/char[] buf) 将字节数组/字符数组中的数据输出到指定输出流中.
  • void write(byte[]/char[] buf,int off,int len) 将字节数组/字符数组从off位置开始,长度为len的字节/字符输出到输出流中.

Writer里还包含如下两个方法:
* void write(String str) 将str字符串输出到指定输出流中
* void write(String str,int off,int len) 将str字符串里从off位置开始,长度为len输出到指定输出流中.

下面的程序使用FileInputStream来执行输入,通过FileOutputStream执行输出,实现复制文件的功能

javapublic void copyFile() throws IOException {
    FileInputStream fis = null;
    FileOutputStream fos = null;

    try {
        fis = new FileInputStream("outfile.txt");
        fos = new FileOutputStream("newfile.txt");

        byte[] bbuf = new byte[32];
        int hasRead = 0;
        while ((hasRead =fis.read(bbuf)) > 0) {
            fos.write(bbuf,0,hasRead);
        }
    } catch(IOException ioe) {
        ioe.printStatckTrance();
    } finally {
        if (fis != null) {
            fis.close();
        }
        if(fos != null) {
            fos.close();
        }
    }
}

如果希望直接输出字符串内容,使用Writer更为方便.

javapublic void out() {
    FileWriter fw = null;
    try {
        fw = new FileWrite("file.txt");
        fw.write("但我不能放歌,\n");
        fw.write("悄悄是别离的笙箫;\n");
        fw.write("夏虫也为我沉默,\n");
        fw.write("沉默是今晚的康桥!\n");
    } catch (IOException ioe) {

    } finally {
        if(null != fw) {
            fw.close();
        }
    }
}

上面程序会在当前目录下输出file.txt,文件内容就是程序中输出内容.

输入/输出流体系

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
访问字符串 StringReader StringWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedRead BufferedWriter
转换流 InputStreamReader OutputStreamReader
对象流 ObjectInputStream ObjectOutput
抽象基类 FilterInputStream FilterOutPutStream FilterReader FilterWriter
打印流 PrintStream PrintWriter
推回输入流 PushbackInputStream PushbackReader
特殊流 DataInputStream DataOutputStream

上面表中粗体字标出类的代表节点流,必须直接与指定物理节点关联;斜体字标出的为抽象基类,无法创建实例.

字节流比字符流功能强大,因为计算机中所有数据都是二进制的.字节流可以处理所有的二进制文件.如果使用字节流来处理文本文件时,需要使用合适的方式把字节转换成字符,无形中增加了编程的复杂程度.所以通常有个规则,如果需要进行输入/输出的是文本内容,应考虑使用字符流,如果需要进行输入/输出的二进制内容,应考虑使用字节流.

使用字符串作为物理节点:

javapublic void StringNodeTest() {

    String src = "软泥上的青荇,\n"
                +"油油的在水底招摇;\n"
                +"在康河的柔波里,\n"
                +"我甘心做一条水草。";

    StringReader sr = new StringReader(src);
    char[] cbuf = new char[32];
    int hasRead = 0;
    try {
        while((hasRead =sr.read(cbuf)) > 0) {
            System.out.println(new String(cbuf,0,hasRead));
        }
    } catch (IOException ioe) {
        ioe.
    } finally {
        if (null != sr){
            sr.close();
        }
    }
}

转换流

IO体系中提供了2个转换流,用于实现将字节流转换成字符流.InputStreamReader将字节输入流转换成字符输入流,OutputStreamWriter将字节输出流转换成字符输出流.

Java并没有提供字符流转换成字节流接口,看似设计遗漏.考虑下字符流和字节流的差别,字节流比字符流使用范围更广,但字符流操作比字节流方便,如果已经有一个字符流,为什么还要换成字节流,既然知道字节流的内容是文本,那么转换成字符流更方便一些,所以Java没有提供字符流到字节流的转换.

下面的例子通过使用键盘输入(System.in).这个标准的输入流是InputStream实例,使用起来不太方便,键盘输入的都是纯文本,使用InputStreamReader将其转换为字符输入流,普通Reader读起来依旧不方便,于是再转换成BufferedReader,利用它的readLine()方法可以一次读一行.

javapublic void convInput() {
    BufferedReader br = null;

    try {
        //System.in转换成Reader对象
        InputStreamReader reader = new InputStreamReader(System.in);
        //将Reader包装成BufferedReader
        br = new BufferedReader(reader);
        String buffer = null;
        While((buffer = br.readLine()) != null) {
            if("exit".equals(buffer)) {
                System.exit(1);
            }
            System.out.println("输入内容为:" + buffer);
        }
    } catach (IOException ioe) {

    } finally {
        try {
            if (null != br) {
                br.close();
            }
        } catach (IOException ioe) {

        }
    }
}

推回输入流

在IO体系中,有2个特殊的流.PushbackInputStream和PushbackReader.提供如下三个方法:
* void unread(byte[]/char[] buf) 将一个字节数组内容推回到推回缓冲区里,从而允许重复读取刚刚的内容.
* void unread(byte[]/char[] b,int off,int len) 将一个字节/字符数组从off开始,长度为len字节/ 字符的内容推回到推回缓冲区里,从而允许重复读取刚刚的内容.
* void unread(int b) 将一个字节/字符推回到推回缓冲区,从而允许重复读取刚刚读取的内容.

这三个方法和InputStream和Reader里三个read方法一一对应,这两个推回输入流都带有一个推回缓冲区,当程序调用这两个推回输入流的unread方法时,系统会把指定数组的内容推回到该缓冲区里,从而允许重复读取刚刚读取的内容.

java/**省略无数代码**/

PushbackReader pr = null;

        try {
            pr = new PushbackReader(new FileReader("E:\\workplace1\\review\\src\\io\\PushbackReaderTest.java"), 64);
            char[] buf = new char[32];
            //保存上次读取的字符串内容
            String lastContent = "";
            int hasRead = 0;
            while ((hasRead = pr.read(buf)) > 0) {
                //读取到内容转换成字符串
                String content = new String(buf,0,hasRead);
                int targetIndex = 0;
                //将上一次内容和本次内容拼起来 检查是否包含目标字符串
                //如果包含目标字符串
                if ((targetIndex = (lastContent + content).indexOf("new PushbackReader")) > 0) {
                    //将本次内容上次内容一起退回到推回缓冲区
                    pr.unread((lastContent + content).toCharArray());
                    //再次读取指定程度的内容(目标字符串之前的内容)
                    pr.read(buf,0,targetIndex);
                    //打印读取内容
                    System.out.println(new String(buf,0,targetIndex));
                    System.exit(0);
                } else {
                    System.out.println(lastContent);
                    lastContent = content;
                }
            }

/**省略无数代码**/

重定向标准输入/输出

Java的标准输入输出分别通过System.in和System.out来代表,默认情况下他们分别代表键盘和显示器.
System类中提供三个重定向输入/输出标准的方法:
* static void setErr(PrintStream err) 重定向"标准"错误输出流
* static void setIn(InputStream in) 重定向"标准"输入流
* static void setOut(PrintStream out) 重定向"标准"输出流

下面的程序通过重定向标准输出流,将System.out的输出重定向到到文件输出:

java/**省略无数代码**/
        PrintStream ps = null;

        try {
            //创建重定向需要的输出流
            ps = new PrintStream(new FileOutputStream("out.txt"));
            //将标准输出流重定向到ps输出流
            System.setOut(ps);
            //测试输出
            System.out.println("普通字符串");
            System.out.println(new RedirectOut());
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if (null != ps) {
                ps.close();
            }
        }
/**省略无数代码**/

下面的程序通过重定向标准输入流,将System.in重定向到指定文件而不是键盘:

java/**省略无数代码**/
        FileInputStream fis = null;

        try {
            fis = new FileInputStream("E:\\workplace1\\review\\src\\io\\RedirectIn.java");
            //将标准输入重定向到fis输入流
            System.setIn(fis);
            //获取标准输入
            Scanner sc = new Scanner(System.in);
            //设置只把回车作为分隔符
            sc.useDelimiter("\n");
            while (sc.hasNext()) {
                System.out.println("键盘输入内容为:" + sc.next());
            }
/**省略无数代码**/