这些Java教程是针对JDK 8编写的。本页面中描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
请参阅Java语言变更,了解Java SE 9及其后续版本中更新的语言特性的摘要。
请参阅JDK版本说明,了解所有JDK版本的新功能、增强功能以及已删除或不推荐使用的选项的信息。
本页面讨论了读取、写入、创建和打开文件的细节。有各种各样的文件I/O方法可供选择。为了帮助理解API,以下图表按照复杂性排列了文件I/O方法。
图表最左侧是实用方法readAllBytes、readAllLines和write,设计用于简单的常见情况。这些方法右侧是用于迭代流或文本行的方法,如newBufferedReader、newBufferedWriter,然后是newInputStream和newOutputStream。这些方法与java.io包是可互操作的。这些方法右侧是处理ByteChannels、SeekableByteChannels和ByteBuffers的方法,如newByteChannel方法。最后,最右侧的方法使用FileChannel进行文件锁定或内存映射I/O的高级应用。
本页面包含以下主题:
本节中的几个方法都有一个可选的OpenOptions参数。该参数是可选的,API会告诉您当未指定时该方法的默认行为是什么。
支持以下StandardOpenOptions枚举:
WRITE – 打开文件以进行写入访问。APPEND – 将新数据追加到文件末尾。此选项与WRITE或CREATE选项一起使用。TRUNCATE_EXISTING – 将文件截断为零字节。此选项与WRITE选项一起使用。CREATE_NEW – 创建新文件,如果文件已存在则抛出异常。CREATE – 如果文件存在则打开文件,如果不存在则创建新文件。DELETE_ON_CLOSE – 在流关闭时删除文件。此选项对于临时文件很有用。SPARSE – 提示新创建的文件将是稀疏的。此高级选项适用于某些文件系统,如NTFS,其中具有数据“间隙”的大文件可以以更高效的方式存储,而这些空的间隙不会占用磁盘空间。SYNC – 使文件(内容和元数据)与底层存储设备同步。DSYNC – 使文件内容与底层存储设备同步。如果您有一个较小的文件,并希望一次读取其全部内容,您可以使用readAllBytes(Path)或readAllLines(Path, Charset)方法。这些方法会为您处理大部分工作,例如打开和关闭流,但不适用于处理大文件。以下代码显示了如何使用readAllBytes方法:
Path file = ...; byte[] fileArray; fileArray = Files.readAllBytes(file);
您可以使用写入方法之一将字节或行写入文件。
write(Path, byte[], OpenOption...)write(Path, Iterable< extends CharSequence>, Charset, OpenOption...)以下代码片段显示了如何使用write方法。
Path file = ...; byte[] buf = ...; Files.write(file, buf);
java.nio.file包支持通道I/O,该方法使用缓冲区移动数据,绕过了可能导致流I/O瓶颈的一些层次。
newBufferedReader(Path, Charset)方法打开一个文件进行读取,返回一个BufferedReader,可以高效地从文件中读取文本。
以下代码片段展示了如何使用newBufferedReader方法从文件中读取数据。文件使用"US-ASCII"编码。
Charset charset = Charset.forName("US-ASCII");
try (BufferedReader reader = Files.newBufferedReader(file, charset)) {
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException x) {
System.err.format("IOException: %s%n", x);
}
可以使用newBufferedWriter(Path, Charset, OpenOption...)方法使用BufferedWriter来写入文件。
以下代码片段展示了如何使用这种方法创建一个使用"US-ASCII"编码的文件:
Charset charset = Charset.forName("US-ASCII");
String s = ...;
try (BufferedWriter writer = Files.newBufferedWriter(file, charset)) {
writer.write(s, 0, s.length());
} catch (IOException x) {
System.err.format("IOException: %s%n", x);
}
java.io API互操作的方法要打开一个文件进行读取,可以使用newInputStream(Path, OpenOption...)方法。此方法返回一个用于读取文件字节的非缓冲输入流。
Path file = ...;
try (InputStream in = Files.newInputStream(file);
BufferedReader reader =
new BufferedReader(new InputStreamReader(in))) {
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException x) {
System.err.println(x);
}
您可以使用newOutputStream(Path, OpenOption...)方法创建文件、追加文件或写入文件。该方法打开或创建一个文件用于写入字节,并返回一个未缓冲的输出流。
该方法接受一个可选的OpenOption参数。如果没有指定打开选项,并且文件不存在,则创建一个新文件。如果文件存在,则截断文件。此选项相当于使用CREATE和TRUNCATE_EXISTING选项调用该方法。
以下示例打开一个日志文件。如果文件不存在,则创建该文件。如果文件存在,则以追加方式打开。
import static java.nio.file.StandardOpenOption.*;
import java.nio.file.*;
import java.io.*;
public class LogFileTest {
public static void main(String[] args) {
// 将字符串转换为字节数组。
String s = "Hello World! ";
byte data[] = s.getBytes();
Path p = Paths.get("./logfile.txt");
try (OutputStream out = new BufferedOutputStream(
Files.newOutputStream(p, CREATE, APPEND))) {
out.write(data, 0, data.length);
} catch (IOException x) {
System.err.println(x);
}
}
}
ByteBuffers的方法流I/O每次读取一个字符,而通道I/O每次读取一个缓冲区。接口ByteChannel提供了基本的read和write功能。一个SeekableByteChannel是一个具有在通道中保持位置并更改该位置的能力的ByteChannel。SeekableByteChannel还支持截断与通道关联的文件和查询文件的大小。
可以移动到文件中的不同位置,然后从该位置读取或写入,从而实现对文件的随机访问。有关更多信息,请参阅随机访问文件。
有两种方法用于读写通道I/O。
newByteChannel(Path, OpenOption...)newByteChannel(Path, Set<? extends OpenOption>, FileAttribute<?>...)newByteChannel方法返回一个SeekableByteChannel实例。在默认文件系统中,您可以将此可寻址字节通道强制转换为FileChannel,以获得更高级的功能,例如将文件的某个区域直接映射到内存中以加快访问速度、锁定文件的某个区域以防止其他进程访问,或者在不影响通道当前位置的情况下读取和写入字节。
newByteChannel方法都支持指定一个OpenOption选项列表。除了支持newOutputStream方法使用的相同的打开选项外,还支持一个额外的选项:READ是必需的,因为SeekableByteChannel同时支持读取和写入。
指定READ打开通道以进行读取。指定WRITE或APPEND打开通道以进行写入。如果没有指定这些选项中的任何一个,那么通道将以读取模式打开。
以下代码片段读取一个文件并将其打印到标准输出:
public static void readFile(Path path) throws IOException {
// Files.newByteChannel() 默认使用 StandardOpenOption.READ
try (SeekableByteChannel sbc = Files.newByteChannel(path)) {
final int BUFFER_CAPACITY = 10;
ByteBuffer buf = ByteBuffer.allocate(BUFFER_CAPACITY);
// 使用此平台的适当编码读取字节。如果
// 跳过这一步,您可能会看到外来的或难以辨认的
// 字符。
String encoding = System.getProperty("file.encoding");
while (sbc.read(buf) > 0) {
buf.flip();
System.out.print(Charset.forName(encoding).decode(buf));
buf.clear();
}
}
}
以下示例适用于UNIX和其他POSIX文件系统,创建具有特定文件权限集的日志文件。此代码将创建一个日志文件,如果已经存在则追加到日志文件中。该日志文件以所有者的读写权限和组的只读权限创建。
import static java.nio.file.StandardOpenOption.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.*;
import java.util.*;
public class LogFilePermissionsTest {
public static void main(String[] args) {
// 创建追加到文件的选项集合。
Set<OpenOption> options = new HashSet<OpenOption>();
options.add(APPEND);
options.add(CREATE);
// 创建自定义权限属性。
Set<PosixFilePermission> perms =
PosixFilePermissions.fromString("rw-r-----");
FileAttribute<Set<PosixFilePermission>> attr =
PosixFilePermissions.asFileAttribute(perms);
// 将字符串转换为ByteBuffer。
String s = "Hello World! ";
byte data[] = s.getBytes();
ByteBuffer bb = ByteBuffer.wrap(data);
Path file = Paths.get("./permissions.log");
try (SeekableByteChannel sbc =
Files.newByteChannel(file, options, attr)) {
sbc.write(bb);
} catch (IOException x) {
System.out.println("Exception thrown: " + x);
}
}
}
您可以使用createFile(Path, FileAttribute<?>)方法创建一个带有初始属性集的空文件。例如,在创建时,如果您想要文件具有特定的文件权限集,请使用createFile方法来实现。如果您没有指定任何属性,文件将使用默认属性创建。如果文件已经存在,则createFile会抛出一个异常。
在单个原子操作中,createFile方法检查文件是否存在,并使用指定的属性创建该文件,这使得该过程对恶意代码更加安全。
以下代码片段创建一个具有默认属性的文件:
Path file = ...;
try {
// 使用默认权限创建空文件
Files.createFile(file);
} catch (FileAlreadyExistsException x) {
System.err.format("名为%s的文件已存在%n", file);
} catch (IOException x) {
// 其他类型的失败,如权限等。
System.err.format("createFile错误:%s%n", x);
}
POSIX文件权限中有一个使用createFile(Path, FileAttribute<?>)创建带有预设权限的文件的示例。
您也可以使用newOutputStream方法创建一个新文件,详见使用流I/O创建和写入文件。如果打开一个新的输出流并立即关闭它,将创建一个空文件。
您可以使用以下createTempFile方法之一创建临时文件:
createTempFile(Path, String, String, FileAttribute<?>)createTempFile(String, String, FileAttribute<?>)第一个方法允许代码为临时文件指定一个目录,而第二个方法在默认临时文件目录中创建一个新文件。两种方法都允许您为文件名指定后缀,而第一个方法还允许您指定前缀。以下代码片段给出了第二种方法的示例:
try {
Path tempFile = Files.createTempFile(null, ".myapp");
System.out.format("临时文件已创建:%s%n", tempFile)
;
} catch (IOException x) {
System.err.format("IOException:%s%n", x);
}
运行该文件的结果可能类似于以下内容:
临时文件已创建:/tmp/509668702974537184.myapp
临时文件名的具体格式取决于操作系统。