Java I/O系统

251 阅读4分钟

本文内容来自《On Java进阶卷》相关章节学习总结

1 IO流

Java8函数式流和IO流之间并无关联

  1. 字节流用于处理原始的二进制数据
  2. 字符流用于处理字符数据,它会自动处理和本地字符集间的相互转换
  3. 缓存流提升性能,通过减少调用本地API的次数,优化输入和输出

1.1 各种InputStream类型

InputStream用于表示从不同源生成输入的类,可能得源有以下几种:

  • 字节数组
  • 字符串对象
  • 文件
  • 管道
  • 其他流组成的序列
  • 网络连接等其他源

1.2 各种OutputStream类型

决定输出去向的类,可以是字节数组、文件或管道。

1.3 添加属性和有用的接口

FilterInputStream和FilterOutputStream提供了可控制某个特定InputStream和OutputStream的装饰器接口。

  1. 用FilterInputStream从InputStream中读取
功能构造器参数
DataInputStream与DataOutputStream配合使用,从流中读取基本类型(int、char、long等)InputStream
BufferedInputStream增加缓冲操作InputStream和可选的缓存区大小
  1. 用FilterOutputStream向OutputStream中写入
功能构造器参数
DataOutputStream向流中写入基本类型OutputStream
PrintStream用于生成格式化的输出OutputStream和可选参数:boolean,表示是否在每次换行时都清空缓存
BufferedOutputStream增加缓冲操作OutputStream和可选的缓存区大小

1.4 各种Reader和Writer

Reader和Writer提供了兼容Unicode并且基于字符的IO能力。并非用来代替InputStream和OutputStream。

有时需要将“字节”类和“字符”类结合使用,要使用适配器类:InputStreamReader将InputStream转换为Reader,OutputStreamWriter将OutputStream转换为Writer。

  1. 数据的来源和去处 对应字节流,字符流主要有FileWriter、FileReader、StringWriter、StringReader、CharArrayWriter、CharArrayReader等;

  2. 改变流的行为 主要有BufferedReader、BufferedWriter、PrintWriter等

1.5 RandomAccessFile

RandomAccessFile适合用来处理大小已知的记录组成的文件,可以通过seek()在记录上来回移动,读取或修改记录。

1.6 IO流的典型用法

1、缓冲输入文件

为提高速度,需要对文件进行缓冲

public class BufferedInputFile {
  public static String read(String filename) {
    try(BufferedReader in = new BufferedReader(
      new FileReader(filename))) {
      return in.lines()
        .collect(Collectors.joining("\n"));
    } catch(IOException e) {
      throw new RuntimeException(e);
    }
  }
}

2、从内存输入

public static void
  main(String[] args) throws IOException {
    StringReader in = new StringReader(
      BufferedInputFile.read("iostreams/MemoryInput.java"));//String字符串
    int c;
    while((c = in.read()) != -1)
      System.out.print((char)c);
}

3、格式化的内存输入 要读取格式化后的数据,可以使用面向字节的DataInputStream。

public static void main(String[] args) {
  try(
    DataInputStream in = new DataInputStream(
      new BufferedInputStream(
        new FileInputStream("iostreams/TestEOF.java")))
  ) {
    while(in.available() != 0)
      System.out.write(in.readByte());
  } catch(IOException e) {
    throw new RuntimeException(e);
  }
}

4、基本的文件输出

FileWriter对象用于向文件中写入数据。一般通过将输出包装在BufferedWriter中来进行缓冲。此外,FileWriter被装饰为PrintWriter以提供格式化的能力。

static String file = "iostreams/BasicFileOutput.dat";

public static void main(String[] args) {
  try(
    BufferedReader in = new BufferedReader(
      new StringReader(
        BufferedInputFile.read(
          "iostreams/BasicFileOutput.java")));
    PrintWriter out = new PrintWriter(
      new BufferedWriter(new FileWriter(file)))
  ) {
    in.lines().forEach(out::println);
  } catch(IOException e) {
    throw new RuntimeException(e);
  }
  // Show the stored file:
  System.out.println(BufferedInputFile.read(file));
}

文本文件输出的快捷方式

PrintWriter提供了一个辅助构造器,不用自行去实现缓冲。

public PrintWriter(String fileName) throws FileNotFoundException {
    this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName))),
         false);
}

5、存储和恢复数据

要输出可供另一个流恢复的数据,可以使用DataOutputStream来写入数据,并用DataInputStream来恢复数据。

public static void main(String[] args) {
  try(
    DataOutputStream out = new DataOutputStream(
      new BufferedOutputStream(
        new FileOutputStream("iostreams/Data.txt")))
  ) {
    out.writeDouble(3.14159);
    out.writeUTF("That was pi");
    out.writeDouble(1.41413);
    out.writeUTF("Square root of 2");
  } catch(IOException e) {
    throw new RuntimeException(e);
  }
  try(
    DataInputStream in = new DataInputStream(
      new BufferedInputStream(
        new FileInputStream("iostreams/Data.txt")))
  ) {
    System.out.println(in.readDouble());
    // Only readUTF() will recover the
    // Java-UTF String properly:
    System.out.println(in.readUTF());
    System.out.println(in.readDouble());
    System.out.println(in.readUTF());
  } catch(IOException e) {
    throw new RuntimeException(e);
  }
}

注意:写入不同类型的数据,在读取时,必须使用相应类型的读取方法才能正确获取。因此,必须对文件中的数据使用固定的格式,或者在要解析的文件中加入额外的信息来确定数据所在的位置。

推荐使用对象序列化或者XML来进行存取复杂数据结构。

读写随机访问文件

使用RandomAccessFile类似于组合使用DataInputStream和DataOutputStream(都实现了相同的接口DataInput和DataOutput)。此外可以使用seek()来回移动并修改值。

static String file = "iostreams/rtest.dat";
public static void display() {
  try(
    RandomAccessFile rf =
      new RandomAccessFile(file, "r")
  ) {
    for(int i = 0; i < 7; i++)
      System.out.println(
        "Value " + i + ": " + rf.readDouble());
    System.out.println(rf.readUTF());
  } catch(IOException e) {
    throw new RuntimeException(e);
  }
}
public static void main(String[] args) {
  try(
    RandomAccessFile rf =
      new RandomAccessFile(file, "rw")
  ) {
    for(int i = 0; i < 7; i++)
      rf.writeDouble(i*1.414);
    rf.writeUTF("The end of the file");
    rf.close();
    display();
  } catch(IOException e) {
    throw new RuntimeException(e);
  }
  try(
    RandomAccessFile rf =
      new RandomAccessFile(file, "rw")
  ) {
    rf.seek(5*8);//double类型占8个字节,则第5个double的位置为5*8 
    rf.writeDouble(47.0001);
    rf.close();
    display();
  } catch(IOException e) {
    throw new RuntimeException(e);
  }
}

2 标准IO

程序的所有输入都可以来自标准输入,所有输出都可以发送到标准输出,所有错误都可以发送到标准错误。

2.1 从标准输入中读取

Java实现了System.out、System.err和System.in,其中System.out和System.err已经被预先封装为PrintStream对象,而System.in是未经包装的原生InputStream,必须在读取前先进行包装。

public static void main(String[] args) {
  TimedAbort abort = new TimedAbort(2);
  new BufferedReader(
    new InputStreamReader(System.in))
    .lines()
    .peek(ln -> abort.restart())
    .forEach(System.out::println);
  // Ctrl-Z or two seconds inactivity
  // terminates the program
}

2.2 将System.out转换为PrintWriter

PrintWriter提供了一个以OutputStream为参数的构造器,内部使用OutputStreamWriter和BufferedWriter进行了包装。

public static void main(String[] args) {
  PrintWriter out =
    new PrintWriter(System.out, true);
  out.println("Hello, world");
}

2.3 标准IO重定向

System类通过静态方法可以将IO标准流重定向:

  • setIn(InputStream in)
  • setOut(PrintStream out)
  • setErr(PrintStream err)
public static void main(String[] args) {
  PrintStream console = System.out;
  try(
    BufferedInputStream in = new BufferedInputStream(
      new FileInputStream("Redirecting.java"));
    PrintStream out = new PrintStream(
      new BufferedOutputStream(
        new FileOutputStream("Redirecting.txt")))
  ) {
    System.setIn(in);//标准输入重定向到文件
    System.setOut(out);//标准输出重定向到文件
    System.setErr(out);//标准错误重定向到文件
    new BufferedReader(
      new InputStreamReader(System.in))
      .lines()
      .forEach(System.out::println);//读取文件内容,再写入另一个文件
  } catch(IOException e) {
    throw new RuntimeException(e);
  } finally {
    System.setOut(console);//恢复指向原始的System.out对象
  }
}

3 NIO系统

NIO内容较多,且比较重要,所以另外进行总结。