I/O
本节将概述 java.io
包。您将学习如何使用它的一些工具来收集和操作各种不同来源的数据。
处理外部数据
在 Java 程序中使用的数据常常来自外部数据源,比如数据库、通过套接字进行的直接字节传输或文件存储。Java 语言提供了许多工具从这些来源获取信息,其中大部分工具都位于 java.io
包中。
文件
在所有 Java 应用程序可用的数据源中,文件是最常见的,常常也是最方便的。如果想在 Java 应用程序中读取一个文件,必须使用 流 将传入的字节解析为 Java 语言类型。
java.io.File
是一个类,它在您的文件系统上定义资源并以一种抽象的方式表示该资源。创建 File
对象很容易:
File f = new File("temp.txt");
File f2 = new File("/home/steve/testFile.txt");
File
构造函数接受它所创建的文件的名称。第一个调用在指定的目录中创建一个名为 temp.txt 的文件。第二个调用在我的 Linux 系统上的具体位置创建一个文件。您可将任何 String
传递至 File
的构造函数,只要文件名对 OS 而言是有效的,无论它引用的文件是否存在都是如此。
此代码向新创建的 File
对象询问该文件是否存在:
File f2 = new File("/home/steve/testFile.txt");
if (f2.exists()) {
// File exists.Process it...
} else {
// File doesn't exist.Create it...
f2.createNewFile();
}
java.io.File
拥有其他方便的方法来:
- 删除文件
- 创建目录(通过将一个目录名称作为参数传递至
File
的构造函数) - 确定一个资源是文件、目录还是符号链接
- 等等
Java I/O 的实际操作是写入和读取数据源,这时就需要使用流。
在 Java I/O 中使用流
可以使用流来访问文件系统上的文件。在最低限度上,流允许程序从来源接收字节或将输出发送至目标。一些流可处理所有类型的 16 位字符( Reader
和 Writer
类型)。其他流只能处理 8 位字节( InputStream
和 OutputStream
类型)。这些分层结构中包含多种风格的流,它们都可在 java.io
包中找到。在最高的抽象级别上是 字符流 和 字节流 。
字节流读( InputStream
和子类)和写( OutputStream
和子类)8 位字节。换句话说,可将字节流看作一种更加原始的流类型。下面总结了两种常见的字节流及其用法:
- FileInputStream / FileOutputStream : 从文件读取字节,将字节写入文件
- ByteArrayInputStream / ByteArrayOutputStream :从内存型数组读取字节,将字节写入内存型数组
字符流
字符流读(Reader
和它的子类)和写(Writer
和它的子类)16 位字符。下面是一个字符流清单及其用法:
**StringReader**
/**StringWriter**
:在内存中的String
中读取以及向其中写入字符。**InputStreamReader**
/**InputStreamWriter**
(和子类**FileReader**
/**FileWriter**
):衔接字节流和字符流。Reader
喜欢从字节流读取字节并将其转换为字符。Writer
喜欢将字符转换为字节,从而将它们放在字节流上。**BufferedReader**
/**BufferedWriter**
:在读取或写入另一个流时缓冲数据,使读写操作更高效。
我不会介绍所有流,而是主要介绍读写文件时推荐使用的流。在大多数情况下,这些都是字符流。
从 File 读取数据
可通过多种方式从 File
读取数据。无疑最简单的方法是:
- 在想要从中读取数据的
File
上创建一个InputStreamReader
。 - 调用
read()
可一次读取一个字符,直至到达文件末尾。
清单 8 是一个从 File
读取数据的示例:
清单 8. 从 File
读取数据
public List<Employee> readFromDisk(String filename) {
final String METHOD_NAME = "readFromDisk(String filename)";
List<Employee> ret = new ArrayList<>();
File file = new File(filename);
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file))) {
StringBuilder sb = new StringBuilder();
int numberOfEmployees = 0;
int character = reader.read();
while (character != -1) {
sb.append((char)character);
character = reader.read();
}
log.info("Read file:\n" + sb.toString());
int index = 0;
while (index < sb.length()-1) {
StringBuilder line = new StringBuilder();
while ((char)sb.charAt(index) != '\n') {
line.append(sb.charAt(index++));
}
StringTokenizer strtok = new StringTokenizer(line.toString(), Person.STATE_DELIMITER);
Employee employee = new Employee();
employee.setState(strtok);
log.info("Read Employee:" + employee.toString());
ret.add(employee);
numberOfEmployees++;
index++;
}
log.info("Read " + numberOfEmployees + " employees from disk.");
} catch (FileNotFoundException e) {
log.logp(Level.SEVERE, SOURCE_CLASS, METHOD_NAME, "Cannot find file " +
file.getName() + ", message = " + e.getLocalizedMessage(), e);
} catch (IOException e) {
log.logp(Level.SEVERE, SOURCE_CLASS, METHOD_NAME, "IOException occurred,
message = " + e.getLocalizedMessage(), e);
}
return ret;
}
写入 File
与从 File
读取一样,可通过多种方式将数据写入 File
。我会再次介绍一下最简单的方法:
- 在想要写入数据的
File
上创建一个FileOutputStream
。 - 调用
write()
写入字符序列。
清单 9 是一个将数据写入 File
的示例:
清单 9. 写入 File
public boolean saveToDisk(String filename, List<Employee> employees) {
final String METHOD_NAME = "saveToDisk(String filename, List<Employee> employees)";
boolean ret = false;
File file = new File(filename);
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file))) {
log.info("Writing " + employees.size() + " employees to disk (as String)...");
for (Employee employee : employees) {
writer.write(employee.getState()+"\n");
}
ret = true;
log.info("Done.");
} catch (FileNotFoundException e) {
log.logp(Level.SEVERE, SOURCE_CLASS, METHOD_NAME, "Cannot find file " +
file.getName() + ", message = " + e.getLocalizedMessage(), e);
} catch (IOException e) {
log.logp(Level.SEVERE, SOURCE_CLASS, METHOD_NAME, "IOException occurred,
message = " + e.getLocalizedMessage(), e);
}
return ret;
}
缓冲流
逐个字符地读和写字符流并不是高效的,所以在大部分情况下,您可能希望使用缓冲的 I/O。要使用缓冲的 I/O 从文件读取数据,代码与清单 28 中所示的类似,但要将 InputStreamReader
包装在一个 BufferedReader
中,如清单 10 所示。