[关闭]
@lemonguge 2015-07-01T08:10:07.000000Z 字数 8267 阅读 350

Java I/O系统(三)

I/O


首先我们要来接触一下缓冲流。在Java I/O系统以Buffered开头的流类共有4个。对这4个类查看API可以发现,它们的构造器都必须依赖其他流对象。在I/O中不止是缓冲流,还有很多其他的类,都是依赖于其他流对象,例如行号缓冲字符输入流LineNumberReader、管道流(以Piped开头的流)和转换流(InputStreamReaderOutputStreamWriter)等等。

Java I/O类库之所以提供如此多的对象,是为了可以通过叠合多个对象来获得我们所希望的功能,而这正是装饰器模式(实现动态的为对象添加功能)。虽然给我们提供了相当多的灵活性,但是它同时也增加了代码的复杂度。

那么缓冲流能有给我们带来什么功能呢?它使我们可以高效的进行读写操作。如果使用了缓存流,就应该使用读取单个字符的read()方法。还记得上一篇,在对四个基本流的基本方法进行介绍时,有这么一行话“支持高效的单字符输入或输出的子类应重写此方法”,当然不止是字符,字节也是一样的,在缓存流中其实就对该方法进行了重写。


为什么缓冲流会高效

缓冲流为什么会高效,其实底层使用的就是目标缓冲区数组,重写后的读取单个字符的方法,是从缓冲区数组中进行读取的。因此笔者在上面的例子中并不介绍使用缓冲区数组的读方法。

硬盘磁头寻道将字节码读取并首先查询编码表获得字符加载到内存中,每读取一个字符然后放入内存中,直到结束。使用了缓存字符流的效率会明显高于直接使用底层资源从硬盘上读。

举一个例子:当我在餐馆吃饭时,点了几道菜,此时菜都做好了,需要服务员从厨房将这几道菜拿到我面前。

显然是使用缓冲区效率会更高。将菜先放入到篮子里的操作就是read(char[] cbuf),把菜一道道放在我面前就是从这个字符数组中取,即覆盖后的read()方法。

由于底层是使用了缓冲区数组,所以缓冲流的构造函数还可以指定缓存数组的大小。


缓冲字符输入流

对于纯文本的操作,应该使用字符流,所以就使用BufferedReader缓冲输入字符流来对文件进行读取操作。

  1. import java.io.BufferedReader;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. public class Buffered {
  5. public static void main(String[] args) throws IOException {
  6. read1();
  7. }
  8. public static void read1() throws IOException {
  9. // 读取当前项目下的file.txt文件
  10. FileReader fr = new FileReader("file.txt");
  11. // 创建了一个字符读入流的缓冲区对象,并和指定要被缓冲的流对象相关联
  12. BufferedReader br = new BufferedReader(fr);
  13. int ch = 0;
  14. while ((ch = br.read()) != -1) {
  15. System.out.print((char) ch);
  16. }
  17. br.close(); // 关闭资源,其实关闭的就是被缓冲的流对象。
  18. }
  19. } /* Output:
  20. abc
  21. 你好!Hello World
  22. 你好,世界。
  23. *///:~

对于纯文本的BufferedReader缓冲输入字符流,其中有一个很常用的方法String readLine(),每次读一行,如果已到达流末尾,则返回null

  1. import java.io.BufferedReader;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. public class Buffered {
  5. public static void main(String[] args) throws IOException {
  6. read2();
  7. }
  8. public static void read2() throws IOException {
  9. FileReader fr = new FileReader("file.txt");
  10. BufferedReader br = new BufferedReader(fr);
  11. String line = null;
  12. while((line=br.readLine())!=null){ // 读一行
  13. System.out.println(line);
  14. }
  15. br.close();
  16. }
  17. } /* Output:
  18. abc
  19. 你好!Hello World
  20. 你好,世界。
  21. *///:~

上面这种方式是我们使用缓冲字符流常用的读方式,即读一行readLine()方法。


缓存字符输出流

下面会对BufferedWriter进行讲解。其中BufferedWriter有一个很常用的方法newLine(),写入一个行分隔符(其实底层就是调用了系统属性line.separator)。

  1. import java.io.BufferedReader;
  2. import java.io.BufferedWriter;
  3. import java.io.File;
  4. import java.io.FileReader;
  5. import java.io.FileWriter;
  6. import java.io.IOException;
  7. public class Buffered {
  8. public static void main(String[] args) throws IOException {
  9. read3();
  10. }
  11. public static void read3() throws IOException {
  12. FileReader fr = new FileReader("file.txt");
  13. FileWriter fw = new FileWriter("D:"+File.separator+"copy_file.txt");
  14. BufferedReader br = new BufferedReader(fr);
  15. BufferedWriter bw = new BufferedWriter(fw);
  16. String line = null;
  17. while((line=br.readLine())!=null){
  18. bw.write(line);
  19. bw.newLine(); // 写入一行的同时,要记得行分隔符。
  20. }
  21. br.close();
  22. bw.close();
  23. }
  24. } ///:~

上面是我们使用缓存字符流常用的方式。


缓冲字节流

我们可以使用缓冲字节流来实现一个高效的复制非纯文本文件的操作。

  1. import java.io.BufferedInputStream;
  2. import java.io.BufferedOutputStream;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.FileOutputStream;
  6. import java.io.IOException;
  7. public class Buffered {
  8. public static void main(String[] args) throws IOException {
  9. copy();
  10. }
  11. public static void copy() throws IOException {
  12. File file = new File("D:"+File.separator+"Destroyer.mp3");
  13. FileInputStream fis = new FileInputStream(file);
  14. BufferedInputStream bis = new BufferedInputStream(fis);
  15. FileOutputStream fos = new FileOutputStream("G:"+File.separator+"copy_Destroyer.mp3");
  16. BufferedOutputStream bos = new BufferedOutputStream(fos);
  17. int by = 0;
  18. while((by=bis.read())!=-1){ // 使用了重写后的read和write方法
  19. bos.write(by);
  20. }
  21. bis.close();
  22. bos.close();
  23. }
  24. } ///:~

到这里,对缓冲流的介绍也已经结束了,不知道读者有没有感到Java I/O流其实并没有想象的那么难呢。


行号缓冲字符输入流

跟踪行号的LineNumberReader缓冲字符输入流,该流提供的功能很简单,就是通过行号来方便操作。此类定义了方法setLineNumber(int)getLineNumber(),它们可分别用于设置和获取当前行号。

  1. import java.io.FileReader;
  2. import java.io.IOException;
  3. import java.io.LineNumberReader;
  4. public class LineNumber {
  5. public static void main(String[] args) throws IOException {
  6. // 当前项目下的file.txt文件有三行内容
  7. FileReader fr = new FileReader("file.txt");
  8. LineNumberReader lnr = new LineNumberReader(fr);
  9. lnr.setLineNumber(1); // 定义初始行,默认为0
  10. String line = null;
  11. while ((line = lnr.readLine()) != null) {
  12. System.out.println(lnr.getLineNumber() + ":" + line); // 有一行便加一,所以从2开始显示内容
  13. }
  14. lnr.close();
  15. }
  16. } /* Output:
  17. 2:abc
  18. 3:你好!Hello World
  19. 4:你好,世界。
  20. *///:~

LineNumberReader行号缓冲字符输入流的使用就介绍到这里,它使用还是很简单的。


转换流

转换流有两个类,InputStreamReaderOutputStreamWriter。看起来就像是四个基本流的组合,下面来分别介绍一下这两个流类:

  1. // 两种方式获取系统默认的字符编码
  2. public class Charset {
  3. public static void main(String[] args) throws IOException {
  4. System.out.println(System.getProperty("file.encoding"));
  5. System.out.println(java.nio.charset.Charset.defaultCharset());
  6. }
  7. } /* Output:
  8. GBK
  9. GBK
  10. *///:~

这两个转换流都具有String getEncoding()方法来获取此流使用的字符编码的名称。下面将介绍使用转换流来实现文件的复制。

  1. import java.io.BufferedReader;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.FileOutputStream;
  5. import java.io.IOException;
  6. import java.io.InputStreamReader;
  7. import java.io.OutputStreamWriter;
  8. public class Charset {
  9. private static final String LINE_SEPARATOR = System.getProperty("line.separator");
  10. public static void main(String[] args) throws IOException {
  11. copy();
  12. }
  13. public static void copy() throws IOException {
  14. FileInputStream fis = new FileInputStream("file.txt");
  15. InputStreamReader isr = new InputStreamReader(fis);
  16. String charsetName = isr.getEncoding();
  17. System.out.println("charsetName:" + charsetName); // 查看编码
  18. BufferedReader br = new BufferedReader(isr);
  19. FileOutputStream fos = new FileOutputStream(new File("D:" + File.separator + "copy_file.txt"));
  20. OutputStreamWriter os = new OutputStreamWriter(fos, charsetName);
  21. String line = null;
  22. while ((line = br.readLine()) != null) {
  23. os.write(line);
  24. os.write(LINE_SEPARATOR);
  25. os.flush();
  26. }
  27. br.close();
  28. os.close();
  29. }
  30. } /* Output:
  31. charsetName:GBK // 也就是我系统中默认的字符编码
  32. *///:~

下面将介绍将文件输入主控台

  1. import java.io.BufferedReader;
  2. import java.io.FileInputStream;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.io.OutputStreamWriter;
  6. public class Charset {
  7. public static void main(String[] args) throws IOException {
  8. FileInputStream fis = new FileInputStream("file.txt");
  9. // 字节流转字符流
  10. InputStreamReader isr = new InputStreamReader(fis);
  11. // 高效装饰
  12. BufferedReader br = new BufferedReader(isr);
  13. // 字符流转标准输出流
  14. OutputStreamWriter osw= new OutputStreamWriter(System.out);
  15. BufferedWriter bw = new BufferedWriter(osw);
  16. String line = null;
  17. while((line=br.readLine())!=null){
  18. bw.write(line);
  19. bw.newLine();
  20. bw.flush(); // 如果没有这行,在主控台看不到任何输出
  21. }
  22. br.close();
  23. }
  24. } /* Output:
  25. abc
  26. 你好!Hello World
  27. 你好,世界。
  28. *///:~

标准输入流和标准输出流都不应该调用close()方法。


打印输出流

打印输出流有两个,分别为PrintWriter字符流和PrintStream字节流。我们其实很早就接触到打印流了,只是一直不知道而已,System.out标准输出流就是PrintStream类型。

这两个流类通过构造器可以看出,要么对其他流进行装饰,要么指定打印输出的目的地为文件。要注意PrintWriter这个字符打印流,它可以对一个字节流进行装饰。(此便捷构造方法创建必要的中间 OutputStreamWriter,后者使用默认字符编码将字符转换为字节。)

尽管打印流的某些构造方法可能抛出异常,但是它们的方法永远不会抛出IOException

这两个类有大类重载的print()println()方法,正因如此,可以保持任意类型数据表现形式的原样性,将数据输出到目的地(字节流write一个字节,低8位;字符流write一个字符,低16位)。

打印流装饰其他流的时候,可以在构造时指定另一个参数为true,这将对println()printf()format()具有自动刷新的功能。

  1. // print方法保证了数据的原样性
  2. public class Print {
  3. public static void main(String[] args) {
  4. printStream();
  5. }
  6. public static void printStream() {
  7. System.out.print(98);
  8. System.out.print(610);
  9. // 0110-0010
  10. System.out.write(98);
  11. // 10-0110-0010
  12. System.out.write(610); // 输出低8位
  13. System.out.flush(); // 不调用将看不到输出“bb”
  14. }
  15. } /* Output:
  16. 98610bb // 看到了b字符,是因为底层已经将字节查询了系统默认编码
  17. *///:~
  1. import java.io.BufferedReader;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. import java.io.PrintWriter;
  5. // 将文本文件打印输出到控制台
  6. public class Print {
  7. public static void main(String[] args) throws IOException {
  8. printWriter1();
  9. }
  10. public static void printWriter1() throws IOException {
  11. BufferedReader br = new BufferedReader(new FileReader("file.txt"));
  12. PrintWriter pw = new PrintWriter(System.out, true); // 使println方法具有自动刷新
  13. String line = null;
  14. while ((line = br.readLine()) != null) {
  15. pw.println(line); // 换行和自动刷新
  16. // pw.flush(); // 不必调用
  17. }
  18. br.close();
  19. }
  20. } /* Output:
  21. abc
  22. 你好!Hello World
  23. 你好,世界。
  24. *///:~
  1. import java.io.BufferedReader;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. import java.io.PrintWriter;
  5. // 复制文本文件
  6. public class Print {
  7. public static void main(String[] args) throws IOException {
  8. printWriter2();
  9. }
  10. public static void printWriter2() throws IOException {
  11. BufferedReader br = new BufferedReader(new FileReader("file.txt"));
  12. PrintWriter pw = new PrintWriter(new FileOutputStream("copy_file.txt"), true);
  13. String line = null;
  14. while((line=br.readLine())!=null){
  15. pw.println(line);
  16. }
  17. br.close();
  18. pw.close();
  19. }
  20. } ///:~

打印流最常见用于Web编程(服务器将所请求的内容显示到客户端浏览器上,就是使用了PrintWriter),如果与文件结合使用也一般是纯文本,因为装饰其他流使具有自动刷新功能,常和println()方法结合使用,输出一行的前提是读取一行,只有缓冲字符流和行号缓存字符流才具有读行的功能,读入的操作是字符,所以打印输出往往为文本文件。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注