Java Tutorials-05-IO

概述

Java 的 I/O 操作类在包 java.io 下,大概有将近 80 个类,但是这些类大概可以分成四组,分别是:

  • 基于字节操作的 I/O 流接口:InputStreamOutputStream
  • 基于字符操作的 I/O 流接口: WriterReader
  • 基于磁盘操作的 I/O 文件接口: File

参考: 深入分析 Java I/O 的工作机制 @ref

按照流操作对象的类型是字节还是字符, 分为字节流和字符流

  1. 字节流的父类是 InputStream/OutputStream, 读写单个字节/字节数组,
  2. 字符流的父类是 Reader/Writer 用于读写被编码(GBK/UTF8)的字符串, 读写Char/Char数组;

按照功能分为节点流(node stream)和过滤流(filter stream, 或者叫装饰流)

  1. 节点流用来处理从基本位置获取字节(文件, 内存, 管道), FileInputStream, ByteArrayInputStream, PipedInputStream, 这些类提供基本的读写方法;
  2. 过滤流用于包装节点流, 提供了新的方法, 可以更方便的读写高级类型的数据(类序列化, 压缩文件, Java基本类型) ObjectInputStream, ZipInputStream, DataInputStream.

节点流 & 过滤流

图-外层的DataInputStream(过滤流)提供了额外的方法:
Node Stream & Filter Stream

字节流 & 字符流

字符流相关类以及继承关系:

字符流
|-InputStream
| |-FileInputStream [node流] 文件流
| |-ByteArrayInputStream [node流] 内存字符流
| |-PipedInputStream [node流] 管道流
| |-ObjectInputStream
| |-SequenceInputStream
| |-FilterInputStream
| |-DataInputStream
| |-BufferedInputStream
|
|-OutputStream

字节流相关类以及继承关系:

字节流
|-Reader
| |-InputStreamReader
| |-FileReader
| |-PipedReader
| |-BufferedReader
| |-CharArrayReader
| |-StringReader
|
|-Writer
|-PrintWriter 没有对应的Reader, 可以使用java.io.Scaner

字节流 常用类和方法

  • InputStream/OutputStream 提供基本的字符/字符数组读写
    • InputStream.available() : 返回可读的字节数
    • read(), read(byte[]): 阻塞的, read返回读取的一个字节(int)
    • write(int b), write(byte[]): 阻塞的
    • close()
  • FileInputStream/FileOutputStream
  • ByteArrayInputStream/ByteArrayOutputStream: 包含一个内部缓冲区(字节数组), 该缓冲区包含从流中读取的字节
  • PipedInputStream/PipedOutputStream 同上
  • BufferedInputStream/BufferedOutputStream: 为另一个流提供缓冲
  • ObjectInputStream/ObjectOutputStream
    • Object readObject()
    • void writeObject(Object)

字符流 常用类和方法

  • Reader: 提供对char,char[],String类型数据的基本操作
    • read(): 返回字符的Unicode编码(0-65535,双字节范围), 到达流末尾返回-1;
    • read(char[]): 读取字符到数组并返回已读取的字符个数;
    • skip(long n): 跳过n个char
    • mark(int limit): 为流的当前位置增加标记, 下次调用reset可以返回这个标记, 如果调用mark()后读取字符数超过limit, 下次调用reset会失败.
    • reset():
    • close():
  • InputStreamReader
    • getEncoding(): 获取输入流的编码
    • ready(): 如果有数据可读, 返回true
  • FileReader: 继承自 InputStreamReader
    • 构造器: FileReader(String), FileReader(java.io.File)
  • BufferedReader:
    • readLine() : 读取一行并返回字符串(不包括换行符), 如果流已经读尽则返回null
  • Scanner: 不是继承自Reader
  • Writer : 提供对char,char[],String类型数据的基本操作
    • write(char c), write(char[]), write(String)
    • append(char), append(CharSequence)
    • flush(): 让缓冲区的内容立刻写入
    • close() :
  • PrintWriter:

文件

本章主要介绍文件操作类: java.io.Filejava.io.RandomAccessFile

java.io.File

File 是“文件”和“目录路径名”的抽象表示形式。File 直接继承于Object,实现了Serializable接口和Comparable接口。
实现Serializable接口,意味着File对象支持序列化操作。
实现Comparable接口,意味着File对象之间可以比较大小;File能直接被存储在有序集合(如TreeSet、TreeMap中)。

public class FileTest {

public static void testFileDirAPIS() {
// 新建目录
File dir = new File("dir");
dir.mkdir();

// 新建文件
File file1 = new File(dir, "file1.txt");
file1.createNewFile();

// 列出目录下的文件
File[] fs = dir.listFiles();
}
}

java.io.RandomAccessFile

java.io.RandomAccessFile是随机访问文件(包括读/写)的类。它支持对文件随机访问的读取和写入,即我们可以从指定的位置读取/写入文件数据。
需要注意的是,RandomAccessFile 虽然属于java.io包,但它不是InputStream或者OutputStream的子类;
它也不同于FileInputStream和FileOutputStream。 FileInputStream 只能对文件进行读操作,而FileOutputStream 只能对文件进行写操作;
RandomAccessFile 同时支持文件的读和写,并且它支持随机访问。

RandomAccessFile 大部分功能被JDK1.4中NIO的内存映射文件替代了

RandomAccessFile raf = new RandomAccessFile(args[0], "r");
long position = raf.length();
while (position > 0) {
position -= 1;
raf.seek(position);
byte b = raf.readByte();
}

try-with-resources

旧风格的I/O操作的异常捕获:

private static void printFile() throws IOException {
InputStream input = null;
try {
input = new FileInputStream("file.txt"); // 1

int data = input.read(); // 2
while(data != -1){
System.out.print((char) data);
data = input.read(); //3
}
} finally {
if(input != null){
input.close(); // 4
}
}
}

上面代码中可能会抛出异常. try语句块中有3个地方能抛出异常, finally语句块中有一个地方会能出异常.
不论try语句块中是否有异常抛出, finally语句块始终会被执行.这意味着, 不论try语句块中发生什么, InputStream 都会被关闭, 或者说都会试图被关闭.如果关闭失败, InputStream’s close()方法也可能会抛出异常.
Q: 假设try语句块抛出一个异常, 然后finally语句块被执行.同样假设finally语句块也抛出了一个异常.那么哪个异常会根据调用栈往外传播?
A: 即使try语句块中抛出的异常与异常传播更相关, 最终还是finally语句块中抛出的异常会根据调用栈向外传播.

在JDK7中, try-with-resources 风格的IO异常捕获:
try-with-resources语句会确保在try语句结束时关闭所有资源. 实现了java.lang.AutoCloseablejava.io.Closeable的对象都可以做为在try()代码块内打开的资源, 并且可以在退出try()语句块时被自动关闭.

// try()代码块内打开多个资源:
try (
java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
InputStream ins = new FileInputStream("/a.txt");
java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
// 这里没有分号
)
{
// Enumerate each entry
for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) {
// Get the entry name and write it to the output file
String newLine = System.getProperty("line.separator");
String zipEntryName =
((java.util.zip.ZipEntry) entries.nextElement()).getName() + newLine;
writer.write(zipEntryName, 0, zipEntryName.length());
}
} catch(Exception1 | Exception2 e) { // 新风格的catch
}

当try-with-resources结构中抛出一个异常, 同时资源调用close方法时也抛出一个异常, try-with-resources结构中抛出的异常会向外传播, 而资源被关闭时抛出的异常被抑制了. 这与旧风格代码的例子相反.

API Example

字节流 API Example

/* 基本字节流 InputStream/OutputStream 接口测试: */
byte[] bytes = {72, 101, 108, 108, 111};
OutputStream os = new FileOutputStream("~/testFile");
os.write(bytes); // 1
os.close();

int size; byte[] readbuf;
InputStream is = new FileInputStream("~/testFile");
if((size= is.available()) > 0) { // 2
is.read(readbuf= new byte[size]); // 3
System.out.println(new String(readbuf));
}
in.close();

/* Filter Streams Layered onto Node Stream */
FileOutputStream fileOutputStream = new FileOutputStream("A.txt"); // Node Stream
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); // Filter Stream
DataOutputStream out = new DataOutputStream(bufferedOutputStream);
out.writeInt(3);
out.writeBoolean(true);
out.flush();
out.close();


DataInputStream in = new DataInputStream(
new BufferedInputStream(
new FileInputStream("A.txt")));
// DataInputStream Methods:
in.readInt();
in.readBoolean();
in.close();

字符流 API Example

/* PrintWriter and Scanner */
PrintWriter out = new PrintWriter("A.txt", "UTF-8");
out.println("Hello");
out.close();

Scanner scanner = new Scanner(new FileInputStream("A.txt"), "UTF-8");
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
}
scanner.close();

/* FileReader -> BufferedReader */
FileReader fr=new FileReader("~/testout.txt");
BufferedReader br=new BufferedReader(fr);
int i;
while((i=br.read())!=-1){
System.out.print((char)i);
}
br.close();
fr.close();

/* BufferedReader 逐行读取 */
String line;
InputStream fis = new FileInputStream(ReadFile);
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
BufferedReader br = new BufferedReader(isr);
while((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();

/* 文件字节流 -> 文件字符流 */
FileOutputStream os = new FileOutputStream(WriteFile);
OutputStreamWriter writer = new OutputStreamWriter(os,"UTF-8");
writer.append("Hello\r\n");
writer.close();

/* 字符流处理Socket */
Socket socket = new Socket("127.0.0.1", 8080);
Writer writer = new PrintWriter(socket.getOutputStream());
write.write("Hello");
// close writer and socket