I/O
目标
- 了解 java.io.File 类的主要用途
- 了解如何使用字节流和字符流
- 了解如何从文件读取数据和向其中写入数据
处理外部数据
您在 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 的构造方法,只要它是对您的操作系统有效的文件名,无论它引用的文件是否存在。
此代码向新创建的 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() 来一次读取一个字符,直至到达文件末尾。 清单 1 是一个从 File 读取的示例:
清单 1. 从 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() 来写入字符序列。
清单 2 是一个将数据写入 File 的例子:
清单 2. 将数据写入到 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 从文件中读取数据,代码类似于 清单 1,但将 InputStreamReader 包装在一个 BufferedReader 中,如清单 3 所示。
清单 3. 使用缓冲的 I/O 从 File 中读取数据
public List<Employee> readFromDiskBuffered(String filename) {
final String METHOD_NAME = "readFromDisk(String filename)";
List<Employee> ret = new ArrayList<>();
File file = new File(filename);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
String line = reader.readLine();
int numberOfEmployees = 0;
while (line != null) {
StringTokenizer strtok = new StringTokenizer(line, Person.STATE_DELIMITER);
Employee employee = new Employee();
employee.setState(strtok);
log.info("Read Employee: " + employee.toString());
ret.add(employee);
numberOfEmployees++;
// Read next line
line = reader.readLine();
}
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;
}
使用缓冲的 I/O 写入文件的过程相同:将 OutputStreamWriter 包装在一个 BufferedWriter 中,如清单 4 所示。
清单 4. 使用缓冲的 I/O 写入 File
public boolean saveToDiskBuffered(String filename, List<Employee> employees) {
final String METHOD_NAME = "saveToDisk(String filename, List<Employee> employees)";
boolean ret = false;
File file = new File(filename);
try (BufferedWriter writer = new BufferedWriter(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;
}