created: 2022-09-07T17:34:33+08:00 updated: 2022-09-09T14:57:38+08:00 tags:

I-O

2. Basic I/O

本章课程覆盖了java平台类中的基本I/O类。首先我们先关注I/O Streams(流),这是一个功能很强大的概念,可以大大简化I/O操作。本章同样关注序列化,通过序列化和I/O流,我们可以将对象读出和写入程序。最后我们关注于File (文件)I/O和文件系统,包括随机访问文件。

I/O Streams章节中绝大多数类在java.io包中。File I/O章节中绝大多数类在java.nio.file包中。

2.1 I/O Streams

2.1.1 Byte Streams(字节流)

程序使用字节流执行8比特(8位二进制)字节的输入和输出。所有的字节流类都继承自俩个抽象类InputStream和OutputStream。

有相当多的字节流类。在这里为了演示字节流如何工作的,我们将使用File I/O字节流中的俩个类FileInputStream和FileOutputStream。其他种类的字节流用法大相径庭,最主要的区别就是他们的构造方式。

2.1.1.1 Using Byte Streams(使用字节流)

我们通过一个简单的程序CopyBytes来探索FileInputStream和FileOutputStream类的使用,该程序使用字节流从xanadu.txt文件中一次复制一个字节到outagain.txt文件中。

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

public class CopyBytes {

public static void main(String[] args) throws IOException {

FileInputStream in = null;

FileOutputStream out = null;

try {

in = new FileInputStream("xanadu.txt");

out = new FileOutputStream("outagain.txt");

int c;

while ((c = in.read()) != -1) {

out.write(c);

}

} finally {

if (in != null) {

in.close();

}

if (out != null) {

out.close();

}

}

}

}

CopyBytes 程序花费的大量的时间在一个简单的循环中,该循环每次从输入流读取一个字节写入到输出流。

2.1.1.2 Always Close Streams(别忘了关闭流)

**当不再需要的时候别忘了关闭流,这非常重要。**所以CopyBytes 程序在finally代码块中保证了即使程序抛出异常一样可以关闭流。这样的实践避免流资源的泄漏。

一个可能的错误是CopyBytes 程序无法打开文件。当发生这种情况,文件流相对应的值不会发生变化,依旧是初始值null。这就是CopyBytes 程序为什么在调用close之前要确保流不为空。

2.1.1.3 When Not to Use Byte Streams(什么时候不使用字节流)

CopyBytes 程序看上去是一个普通程序,实际上却代表的是一个底层I/O,你应当避免这种情况。文件xanadu.txt包含字符数据,最好的方法应是使用Character Streams(字符流),我们将在下一节讨论。

也有针对复杂数据类型的流。字节流应当被用作最原始的I/O流。

那么我们为什么要讨论字节流呢?因为字节流相对底层,其他流类型都是基于字节流。

2.1.2 Character Streams(字符流)

java平台用Unicode标准存储字符值。字符流I/O将根据本地字符集自动转换内部格式。在西方,本地字符集通常使用8位的ASCII超集。

对于绝大多数应用来说,I/O字符流并不会比I/O字节流复杂。通过流类自动的转换成本地的字符集来完成输入和输出。当项目使用字符流代替字节流可以自动适应本地字符集,并且可以国际化,而这一切都不用程序设计人员操心。

如果国际化并不是重点,你可以简单的使用字符流,并不用关系字符集的问题。如果以后,你的项目需要国际化,程序会自适应并不需要你大量重新编码。更多信息看国际化章节。

2.1.2.1 Using Character Streams(使用字符流)

所有的字符流类都是Reader和Writer的子类。和字节流一样,有专门的文件I/O字符流FileReader和FileWriter。下面CopyCharacters例类演示了文件的字符流。

import java.io.FileReader;

import java.io.FileWriter;

import java.io.IOException;

public class CopyCharacters {

public static void main(String[] args) throws IOException {

FileReader inputStream = null;

FileWriter outputStream = null;

try {

inputStream = new FileReader("xanadu.txt");

outputStream = new FileWriter("characteroutput.txt");

int c;

while ((c = inputStream.read()) != -1) {

outputStream.write(c);

}

} finally {

if (inputStream != null) {

inputStream.close();

}

if (outputStream != null) {

outputStream.close();

}

}

}

}

CopyCharacters类非常类似于CopyBytes类。最主要的不同是CopyCharacters类使用的是字符流,而CopyBytes类使用的是字节流。注意CopyCharacters类和CopyBytes类都使用了一个int局部变量用于读和写。然而,在CopyCharacters类中,int变量持有一个字符的值即它的后16位,在CopyBytes类中,int变量持有一个字节的值即它的后8位。

字符流其实是使用字节流。

字符流是字节流的一个“包装器”。字符流使用字节流去处理物理I/O,字符流处理的是字符和字节之间的转换。例如,FileReader使用FileInputStream,同样FileWriter使用FileOutputStream。

有俩个通用的字节到字符的“桥”流InputStreamReader和OutputStreamWriter。当你没有预包装好的字符流时,可以使用这俩个类创建字符流以满足你的需求。在套接字学习网络章节中演示了怎么样从提供套接字类的字节流中创建字符流。

2.1.2.2 Line-Oriented I/O(面向行的I/O)

字符流通常应用在比一个字符大的单元。通常一个单元一行:以行结尾的所有字符构成一个字符串。一行结尾可以是以一个回车和一个换行 ("\r\n"),单单一个回车("\r")或单单一个换行("\n")。支持所有可能的行终止符,使程序可以读取在任何广泛使用的操作系统上创建的文本文件。

下面来修改CopyCharacters类实现面向行的I/O。在这之前,我们要先用俩个我们之前没有使用过的类BufferedReader和PrintWriter。我们将深度学习这些类在缓冲I/O和格式化章节。现在,我们仅仅感兴趣如何支持面向行的I/O。

下面CopyLines例类调用 BufferedReader.readLine和PrintWriter.println方法一次输入和输出一行。

import java.io.FileReader;

import java.io.FileWriter;

import java.io.BufferedReader;

import java.io.PrintWriter;

import java.io.IOException;

public class CopyLines {

public static void main(String[] args) throws IOException {

BufferedReader inputStream = null;

PrintWriter outputStream = null;

try {

inputStream = new BufferedReader(new FileReader("xanadu.txt"));

outputStream = new PrintWriter(new FileWriter("characteroutput.txt"));

String l;

while ((l = inputStream.readLine()) != null) {

outputStream.println(l);

}

} finally {

if (inputStream != null) {

inputStream.close();

}

if (outputStream != null) {

outputStream.close();

}

}

}

}

掉用readLine方法返回一行文本。CopyLines类掉用println方法输出一行,并且在行尾追加当前操作系统的行结尾的标识。这有可能于输入文本的行结尾标识不同。

除了字符和行之外,还有许多方法来构造文本的输入和输出。详细信息,请看扫描和格式化章节。

2.1.3 Buffered Streams(缓冲流)

我们已经见过很多例子,但是至今使用的都是无缓冲流。这意味着每次的读写都是底层操作系统来直接处理的。这使得程序非常的低效,因为每次的请求都会触发磁盘的访问和网络的使用,还有一写其他相关的耗时操作。

为来减少这些开销,java平台实现了缓存I/O流。输入缓冲流从一个叫做缓存的内存区域读取数据,当缓冲为空时,原生的输入api被调用。类似的,输出缓冲流写入数据到缓冲,当缓冲满时,原生的输出api被调用。

程序可以使用包装,将无缓冲流转换为缓冲流,现在我们使用过几次,将无缓冲流的对象作为参数传给缓冲类的构造器。下面你可以修改CopyCharacters例类的构造器调用,然后使用缓冲I/O

inputStream = new BufferedReader(new FileReader("xanadu.txt"));

outputStream = new BufferedWriter(new FileWriter("characteroutput.txt"));

有四种缓冲流类可以用来包装无缓冲流BufferedInputStream和BufferedOutputStream用于创建缓冲字节流,BufferedReader和BufferedWriter创建缓冲字符流。

2.1.3.1 Flushing Buffered Streams(刷新缓冲流)

这通常用于在一个关键点写出,并不需要等到缓冲满,这被叫做刷新缓冲。

一些输出缓冲类支持指定一个可选的构造器参数来实现自动刷新。当自动刷新开启后,指定的事件会导致缓冲刷新。例如,自动刷新PrintWriter类的对象在每次调用println和format方法时会刷新缓冲。这些方法的更多信息请看格式化。

调用flush方法会手动刷新缓冲。这方法在输出缓冲流中的任何类中都有效。

2.1.4 Scanning and Formatting(扫描和格式化)

程序I/O经常涉及到人们喜欢的格式之间的转换。为了帮助你完成这些琐碎的事,java平台提供了俩个api。扫描api将输入分为与数据位相关联的各个令牌。格式化api将数据组装成格式良好,易于阅读的格式。

2.1.4.1 Scanning(扫描)

Scanner类的对象可用于将格式化的输入分解为令牌,并根据其数据类型转换单个令牌。

2.1.4.1.1 Breaking Input into Tokens(将输入化分成令牌)

扫描器默认使用空白分隔令牌。(空白包括空格,制表和换行。完整的信息请参考Character.isWhitespace)要查看扫描的工作方式,让我们看下面ScanXan例类,程序在文本文件xanadu.txt读取单个单词并一行一行的打印出来。

import java.io.*;

import java.util.Scanner;

public class ScanXan {

public static void main(String[] args) throws IOException {

Scanner s = null;

try {

s = new Scanner(new BufferedReader(new FileReader("xanadu.txt")));

while (s.hasNext()) {

System.out.println(s.next());

}

} finally {

if (s != null) {

s.close();

}

}

}

}

注意ScanXan例类完成扫描对象操作时调用了扫描的close方法。即使扫描器并不是流,你依然需要关闭它以表面底层流操作完成。

ScanXan例类的运行结果如下:

In

Xanadu

did

Kubla

Khan

A

stately

pleasure-dome

...

如果要使用不同令牌的分隔器,你可以调用useDelimiter()方法,并指定一个正则表达式。假设您希望令牌分隔器是逗号,并且后面可以选择跟上空格。你可以这样调用:

s.useDelimiter(",\\s*");

2.1.4.1.2 Translating Individual Tokens(翻译单个令牌)

ScanXan例类对待所有的输入令牌都当做一个简单的字符串值。扫描器还支持所有java基本类型的令牌(除了char),以及BigInteger和BigDecimal。另外,数值可以使用千位分隔符。所以,在美国地区可正确的读出字符串"32,767"代表的是一个整数值。

我们必须提到语言环境,因为数千个分隔符和十进制符号是特定于语言环境的。因此,如果我们未指定扫描器应使用美国语言环境,所以以下示例在所有语言环境中不一定正常工作。通常这不必担心,因为输入数据通常来自与您使用相同语言环境的源。但是此示例是Java教程的一部分,并在全世界范围内分发。

ScanSum示例读取一个双精度值列表并将其加起来,下面是源码:

import java.io.FileReader;

import java.io.BufferedReader;

import java.io.IOException;

import java.util.Scanner;

import java.util.Locale;

public class ScanSum {

public static void main(String[] args) throws IOException {

Scanner s = null;

double sum = 0;

try {

s = new Scanner(new BufferedReader(new FileReader("usnumbers.txt")));

s.useLocale(Locale.US);

while (s.hasNext()) {

if (s.hasNextDouble()) {

sum += s.nextDouble();

} else {

s.next();

}

}

} finally {

s.close();

}

System.out.println(sum);

}

}

下面是示例输入文件usnumbers.txt

8.5

32,767

3.14159

1,000,000.1

输出字符串为“ 1032778.74159”。在某些地区,点号将是不同的字符,因为System.out是一个PrintStream对象,并且该类没有提供覆盖默认语言环境的方法。我们可以覆盖整个程序的语言环境,也可以仅使用格式化,如下一章节“格式化”中所述。

2.1.4.2 Formatting(格式化)

实现格式化的流对象是字符流类PrintWriter或字节流类PrintStream的实例。

注意:您可能唯一需要的PrintStream对象是System.out和System.err。(有关这些对象的更多信息,请参见命令行中的I/O。)当需要创建格式化的输出流时,请实例化PrintWriter而不是PrintStream。

与所有字节和字符流对象一样,PrintStream和PrintWriter的实例实现一组标准的write方法,用于简单的字节和字符输出。此外,PrintStream和PrintWriter都实现了将内部数据转换为格式化输出的相同方法集。提供了两种格式设置:

print和println以标准方式格式化单个值。 format会根据格式字符串来格式化几乎任何数量的值,并提供许多用于精确格式化的选项。

2.1.4.2.1 The print and println Methods

使用适当的toString方法转换值后,调用print或println将输出一个值。我们可以看下面Root例类:

public class Root {

public static void main(String[] args) {

int i = 2;

double r = Math.sqrt(i);

System.out.print("The square root of ");

System.out.print(i);

System.out.print(" is ");

System.out.print(r);

System.out.println(".");

i = 5;

r = Math.sqrt(i);

System.out.println("The square root of " + i + " is " + r + ".");

}

}

这是Root的输出:

The square root of 2 is 1.4142135623730951.

The square root of 5 is 2.23606797749979.

i和r变量被格式化两次:第一次使用以及加载的print,第二次使用Java编译器自动生成的转换代码,该代码也使用toString。您可以通过这种方式设置任何值的格式,但是您对结果没有太多控制权。

2.1.4.2.2 The format Method

example1

public class Root2 {

public static void main(String[] args) {

int i = 2;

double r = Math.sqrt(i);

System.out.format("The square root of %d is %f.%n", i, r);

}

}

result

The square root of 2 is 1.414214.

example2

public class Format {

public static void main(String[] args) {

System.out.format("%f, %1$+020.10f %n", Math.PI);

}

}

result

3.141593, +00000003.1415926536

附加元素都是可选的。下图显示了较长的说明符如何分解为元素。

元素必须按显示的顺序出现。从右边开始,可选元素是:

Precision Width Flags Argument Index

2.1.5 I/O from the Command Line

程序通常从命令行运行并在命令行环境中与用户交互。 Java 平台以两种方式支持这种交互:通过标准流和通过控制台。

2.1.5.1 Standard Streams

Java 平台支持三种标准流:标准输入,通过 System.in 访问;标准输出,通过 System.out 访问;和标准错误,通过 System.err 访问。

您可能期望标准流是字符流,但由于历史原因,它们是字节流。 System.out 和 System.err 被定义为 PrintStream 对象。虽然它在技术上是字节流,但 PrintStream 使用内部字符流对象来模拟字符流的许多特性。

相比之下,System.in 是一个没有字符流特性的字节流。要将标准输入用作字符流,请将 System.in 包装在 InputStreamReader 中。

InputStreamReader cin = new InputStreamReader(System.in);

2.1.5.2 The Console

标准流的更高级替代方案是控制台。这是一个单例的、预定义的 Console 类型的对象,它具有标准流提供的大部分功能,除此之外还有其他功能。控制台对于安全密码输入特别有用。 Console 对象还通过它的 reader 和 writer 方法提供了真正的字符流的输入和输出流。

在程序可以使用控制台之前,它必须尝试通过调用 System.console() 来检索控制台对象。如果 Console 对象可用,则此方法返回它。如果 System.console 返回 NULL,则不允许控制台操作,因为操作系统不支持它们或因为程序是在非交互式环境中启动的。

Console 对象通过其 readPassword 方法支持安全密码输入。此方法通过两种方式帮助保护密码输入。首先,它禁止回显,因此密码在用户屏幕上不可见。其次,readPassword 返回一个字符数组,而不是字符串,因此可以覆盖密码,一旦不再需要它就将其从内存中删除。

example

public class Password {

public static void main (String args[]) throws IOException {

Console c = System.console();

if (c == null) {

System.err.println("No console.");

System.exit(1);

}

String login = c.readLine("Enter your login: ");

char [] oldPassword = c.readPassword("Enter your old password: ");

if (verify(login, oldPassword)) {

boolean noMatch;

do {

char [] newPassword1 = c.readPassword("Enter your new password: ");

char [] newPassword2 = c.readPassword("Enter new password again: ");

noMatch = ! Arrays.equals(newPassword1, newPassword2);

if (noMatch) {

c.format("Passwords don't match. Try again.%n");

} else {

change(login, newPassword1);

c.format("Password for %s changed.%n", login);

}

Arrays.fill(newPassword1, ' ');

Arrays.fill(newPassword2, ' ');

} while (noMatch);

}

Arrays.fill(oldPassword, ' ');

}

// Dummy change method.

static boolean verify(String login, char[] password) {

// This method always returns

// true in this example.

// Modify this method to verify

// password according to your rules.

return true;

}

// Dummy change method.

static void change(String login, char[] password) {

// Modify this method to change

// password according to your rules.

}

}

2.1.6 Data Streams

数据流支持原始数据类型值(boolean、char、byte、short、int、long、float 和 double)以及 String 值的二进制 I/O。所有数据流都实现 DataInput 接口或 DataOutput 接口。本节重点介绍这些接口的最广泛使用的实现,DataInputStream 和 DataOutputStream。

example

public class DataStreams {

static final String dataFile = "invoicedata";

static final double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };

static final int[] units = { 12, 8, 13, 29, 50 };

static final String[] descs = { "Java T-shirt",

"Java Mug",

"Duke Juggling Dolls",

"Java Pin",

"Java Key Chain" };

public static void main(String[] args) throws IOException {

DataOutputStream out = null;

try {

out = new DataOutputStream(new

BufferedOutputStream(new FileOutputStream(dataFile)));

for (int i = 0; i < prices.length; i ++) {

out.writeDouble(prices[i]);

out.writeInt(units[i]);

out.writeUTF(descs[i]);

}

} finally {

out.close();

}

DataInputStream in = null;

double total = 0.0;

try {

in = new DataInputStream(new

BufferedInputStream(new FileInputStream(dataFile)));

double price;

int unit;

String desc;

try {

while (true) {

price = in.readDouble();

unit = in.readInt();

desc = in.readUTF();

System.out.format("You ordered %d units of %s at $%.2f%n",

unit, desc, price);

total += unit * price;

}

} catch (EOFException e) { }

System.out.format("For a TOTAL of: $%.2f%n", total);

}

finally {

in.close();

}

}

}

2.1.7 Object Streams

正如数据流支持原始数据类型的 I/O 一样,对象流支持对象的 I/O。大多数(但不是全部)标准类支持其对象的序列化。那些确实实现了标记接口 Serializable 的。

对象流类是 ObjectInputStream 和 ObjectOutputStream。这些类实现 ObjectInput 和 ObjectOutput,它们是 DataInput 和 DataOutput 的子接口。这意味着数据流中涵盖的所有原始数据 I/O 方法也在对象流中实现。因此,对象流可以包含原始值和对象值的混合。

example

public class ObjectStreams {

static final String dataFile = "invoicedata";

static final BigDecimal[] prices = {

new BigDecimal("19.99"),

new BigDecimal("9.99"),

new BigDecimal("15.99"),

new BigDecimal("3.99"),

new BigDecimal("4.99") };

static final int[] units = { 12, 8, 13, 29, 50 };

static final String[] descs = { "Java T-shirt",

"Java Mug",

"Duke Juggling Dolls",

"Java Pin",

"Java Key Chain" };

public static void main(String[] args)

throws IOException, ClassNotFoundException {

ObjectOutputStream out = null;

try {

out = new ObjectOutputStream(new

BufferedOutputStream(new FileOutputStream(dataFile)));

out.writeObject(Calendar.getInstance());

for (int i = 0; i < prices.length; i ++) {

out.writeObject(prices[i]);

out.writeInt(units[i]);

out.writeUTF(descs[i]);

}

} finally {

out.close();

}

ObjectInputStream in = null;

try {

in = new ObjectInputStream(new

BufferedInputStream(new FileInputStream(dataFile)));

Calendar date = null;

BigDecimal price;

int unit;

String desc;

BigDecimal total = new BigDecimal(0);

date = (Calendar) in.readObject();

System.out.format ("On %tA, %

try {

while (true) {

price = (BigDecimal) in.readObject();

unit = in.readInt();

desc = in.readUTF();

System.out.format("You ordered %d units of %s at $%.2f%n",

unit, desc, price);

total = total.add(price.multiply(new BigDecimal(unit)));

}

} catch (EOFException e) {}

System.out.format("For a TOTAL of: $%.2f%n", total);

} finally {

in.close();

}

}

}

2.1.7.1 Output and Input of Complex Objects

writeObject 和 readObject 方法使用简单,但它们包含一些非常复杂的对象管理逻辑。这对于像 Calendar 这样只封装原始值的类来说并不重要。但是许多对象包含对其他对象的引用。如果 readObject 要从流中重构对象,它必须能够重构原始对象引用的所有对象。这些附加对象可能有它们自己的引用,等等。在这种情况下,writeObject 会遍历整个对象引用网络,并将该网络中的所有对象写入流中。因此,单次调用 writeObject 会导致将大量对象写入流。

这在下图中进行了演示,其中调用 writeObject 来写入名为 a 的单个对象。该对象包含对对象 b 和 c 的引用,而 b 包含对 d 和 e 的引用。调用 writeobject(a) 不仅写入 a,还写入重构 a 所需的所有对象,因此该网络中的其他四个对象也被写入。当 a 被 readObject 读回时,其他四个对象也被读回,并且保留了所有原始对象引用。

2.2 File I/O (Featuring NIO.2)

java.nio.file 包及其相关包 java.nio.file.attribute 为文件 I/O 和访问默认文件系统提供全面的支持。尽管 API 有很多类,但您只需要关注几个入口点。您会看到这个 API 非常直观且易于使用。

2.2.1 What Is a Path? (And Other File System Facts)

文件系统在某种形式的媒体(通常是一个或多个硬盘驱动器)上存储和组织文件,以便可以轻松检索它们。当今使用的大多数文件系统将文件存储在树(或分层)结构中。树的顶部是一个(或多个)根节点。在根节点下,有文件和目录。每个目录都可以包含文件和子目录,而这些文件和子目录又可以包含文件和子目录等等,可能具有几乎无限的深度。

2.2.1.1 What Is a Path?

下图显示了一个包含单个根节点的示例目录树。 Microsoft Windows 支持多个根节点。每个根节点都映射到一个卷,例如 C:\ 或 D:\。 Solaris OS 支持单个根节点,它由斜杠字符 / 表示。

文件通过文件系统的路径标识,从根节点开始。例如,上图中的 statusReport 文件在 Solaris OS 中由以下符号描述:

/home/sally/statusReport

在 Microsoft Windows 中,statusReport 由以下符号描述:

C:\home\sally\statusReport

用于分隔目录名称的字符(也称为分隔符)特定于文件系统:Solaris OS 使用正斜杠 (/),Microsoft Windows 使用反斜杠 (\)。

2.2.1.2 Relative or Absolute?

路径是相对的或绝对的。绝对路径始终包含根元素和定位文件所需的完整目录列表。例如,/home/sally/statusReport 是绝对路径。定位文件所需的所有信息都包含在路径字符串中。

相对路径需要与另一个路径组合才能访问文件。例如,joe/foo 是一个相对路径。如果没有更多信息,程序就无法可靠地定位文件系统中的 joe/foo 目录。

2.2.1.3 Symbolic Links

文件系统对象通常是目录或文件。每个人都熟悉这些对象。但是一些文件系统也支持符号链接的概念。符号链接也称为符号链接或软链接。

符号链接是一个特殊文件,用作对另一个文件的引用。在大多数情况下,符号链接对应用程序是透明的,对符号链接的操作会自动重定向到链接的目标。(指向的文件或目录称为链接的目标。)例外情况是符号链接被删除或重命名,在这种情况下链接本身被删除,或重命名而不是链接的目标。

在下图中,logFile 对用户来说似乎是一个常规文件,但实际上它是指向 dir/logs/HomeLogFile 的符号链接。 HomeLogFile 是链接的目标。

符号链接通常对用户是透明的。读取或写入符号链接与读取或写入任何其他文件或目录相同。

解析链接的短语意味着用文件系统中的实际位置替换符号链接。在示例中,解析 logFile 会产生 dir/logs/HomeLogFile。

在现实世界的场景中,大多数文件系统都大量使用符号链接。有时,不小心创建的符号链接会导致循环引用。当链接的目标指向原始链接时,就会发生循环引用。循环引用可能是间接的:目录 a 指向目录 b,目录 b 指向目录 c,其中包含指向目录 a 的子目录。当程序递归遍历目录结构时,循环引用可能会造成严重破坏。但是,这种情况已被考虑在内,不会导致您的程序无限循环。

2.2.2 The Path Class

Java SE 7 版本中引入的 Path 类是 java.nio.file 包的主要入口点之一。如果您的应用程序使用文件 I/O,您将​​需要了解此类的强大功能。

顾名思义,Path 类是文件系统中路径的编程表示。 Path 对象包含用于构造路径的文件名和目录列表,用于检查、定位和操作文件。

Path 实例反映了底层平台。在 Solaris OS 中,Path 使用 Solaris 语法 (/home/joe/foo),而在 Microsoft Windows 中,Path 使用 Windows 语法 (C:\home\joe\foo)。路径不是系统独立的。您不能比较 Solaris 文件系统中的路径并期望它与 Windows 文件系统中的路径相匹配,即使目录结构相同并且两个实例都找到相同的相关文件。

Path 对应的文件或目录可能不存在。您可以创建一个 Path 实例并以各种方式对其进行操作:您可以附加到它,提取它的片段,将它与另一个路径进行比较。在适当的时候,您可以使用 Files 类中的方法来检查 Path 对应的文件是否存在,创建文件、打开文件、删除文件、更改其权限等。

2.2.2.1 Path Operations

Path 类包括各种方法,可用于获取有关路径的信息、访问路径的元素、将路径转换为其他形式或提取路径的一部分。还有一些匹配路径字符串的方法和删除路径中冗余的方法。本课介绍这些 Path 方法,有时称为句法操作,因为它们在路径本身上操作并且不访问文件系统。

2.2.2.1.1 Creating a Path

Path 实例包含用于指定文件或目录位置的信息。在定义路径时,会为路径提供一系列一个或多个名称。可能包含根元素或文件名,但两者都不是必需的。路径可能只包含一个目录或文件名。

您可以使用 Paths(注意复数)辅助类中的以下 get 方法之一轻松创建 Path 对象:

Path p1 = Paths.get("/tmp/foo");

Path p2 = Paths.get(args[0]);

Path p3 = Paths.get(URI.create("file:///Users/joe/FileTest.java"));

Paths.get 方法是以下代码的简写:

Path p4 = FileSystems.getDefault().getPath("/users/sally");

以下示例创建 /u/joe/logs/foo.log 假设您的主目录是 /u/joe,如果您在 Windows 上,则为 C:\joe\logs\foo.log。

Path p5 = Paths.get(System.getProperty("user.home"),"logs", "foo.log");

2.2.2.1.2 Retrieving Information about a Path

您可以将 Path 视为将这些名称元素存储为一个序列。目录结构中的最高元素将位于索引 0。目录结构中的最低元素将位于索引 [n-1],其中 n 是路径中名称元素的数量。方法可用于使用这些索引检索单个元素或路径的子序列。

本课中的示例使用以下目录结构。

以下代码片段定义了一个 Path 实例,然后调用几个方法来获取有关路径的信息:

// None of these methods requires that the file corresponding

// to the Path exists.

// Microsoft Windows syntax

Path path = Paths.get("C:\\home\\joe\\foo");

// Solaris syntax

Path path = Paths.get("/home/joe/foo");

System.out.format("toString: %s%n", path.toString());

System.out.format("getFileName: %s%n", path.getFileName());

System.out.format("getName(0): %s%n", path.getName(0));

System.out.format("getNameCount: %d%n", path.getNameCount());

System.out.format("subpath(0,2): %s%n", path.subpath(0,2));

System.out.format("getParent: %s%n", path.getParent());

System.out.format("getRoot: %s%n", path.getRoot());

result

Method InvokedReturns in the Solaris OSReturns in Microsoft WindowsCommenttoString/home/joe/fooC:\home\joe\fooReturns the string representation of the Path. If the path was created using Filesystems.getDefault().getPath(String) or Paths.get (the latter is a convenience method for getPath), the method performs minor syntactic cleanup. For example, in a UNIX operating system, it will correct the input string //home/joe/foo to /home/joe/foo.getFileNamefoofooReturns the file name or the last element of the sequence of name elements.getName(0)homehomeReturns the path element corresponding to the specified index. The 0th element is the path element closest to the root.getNameCount33Returns the number of elements in the path.subpath(0,2)home/joehome\joeReturns the subsequence of the Path (not including a root element) as specified by the beginning and ending indexes.getParent/home/joe\home\joeReturns the path of the parent directory.getRoot/C:\Returns the root of the path.

前面的示例显示了绝对路径的输出。在以下示例中,指定了相对路径

// Solaris syntax

Path path = Paths.get("sally/bar");

or

// Microsoft Windows syntax

Path path = Paths.get("sally\\bar");

result

Method InvokedReturns in the Solaris OSReturns in Microsoft WindowstoStringsally/barsally\bargetFileNamebarbargetName(0)sallysallygetNameCount22subpath(0,1)sallysallygetParentsallysallygetRootnullnull

2.2.2.1.3 Removing Redundancies From a Path

许多文件系统使用“.”符号表示当前目录,“…”表示父目录。您可能会遇到路径包含冗余目录信息的情况。也许服务器配置为将其日志文件保存在“/dir/logs/.”目录中,并且您要删除尾随的“/.”来自路径的符号。

以下示例都包括冗余:

/home/./joe/foo

/home/sally/../joe/foo

normalize 方法删除任何冗余元素,其中包括任何“.”。或“directory/…”出现。前面的两个示例都归一化为 /home/joe/foo。

需要注意的是,normalize 在清理路径时不会检查文件系统。这是一个纯粹的句法操作。在第二个示例中,如果 sally 是一个符号链接,删除 sally/… 可能会导致 Path 不再定位预期的文件。

要在确保结果找到正确文件的同时清理路径,可以使用 toRealPath 方法。

2.2.2.1.4 Converting a Path

您可以使用三种方法来转换路径。如果需要将路径转换为可以从浏览器打开的字符串,可以使用 toUri。例如:

Path p1 = Paths.get("/home/logfile");

// Result is file:///home/logfile

System.out.format("%s%n", p1.toUri());

toAbsolutePath 方法将路径转换为绝对路径。如果传入的路径已经是绝对路径,则返回相同的 Path 对象。 toAbsolutePath 方法在处理用户输入的文件名时非常有用。例如:

public class FileTest {

public static void main(String[] args) {

if (args.length < 1) {

System.out.println("usage: FileTest file");

System.exit(-1);

}

// Converts the input string to a Path object.

Path inputPath = Paths.get(args[0]);

// Converts the input Path

// to an absolute path.

// Generally, this means prepending

// the current working

// directory. If this example

// were called like this:

// java FileTest foo

// the getRoot and getParent methods

// would return null

// on the original "inputPath"

// instance. Invoking getRoot and

// getParent on the "fullPath"

// instance returns expected values.

Path fullPath = inputPath.toAbsolutePath();

}

}

toAbsolutePath 方法转换用户输入并返回一个 Path,该 Path 在查询时返回有用的值。该文件不需要存在此方法即可工作。

toRealPath 方法返回现有文件的真实路径。此方法合而为一地执行多项操作:

如果将 true 传递给此方法并且文件系统支持符号链接,则此方法将解析路径中的任何符号链接。(jdk8不用传参数,或者传 LinkOption.NOFOLLOW_LINKS ) 如果 Path 是相对的,则返回绝对路径。 如果 Path 包含任何冗余元素,它会返回删除了这些元素的路径。

如果文件不存在或无法访问,此方法将引发异常。当您想要处理任何这些情况时,您可以捕获异常。例如:

try {

Path fp = path.toRealPath();

} catch (NoSuchFileException x) {

System.err.format("%s: no such" + " file or directory%n", path);

// Logic for case when file doesn't exist.

} catch (IOException x) {

System.err.format("%s%n", x);

// Logic for other sort of file error.

}

2.2.2.1.5 Joining Two Paths

您可以使用 resolve 方法组合路径。您传入一个部分路径,这是一个不包含根元素的路径,并且该部分路径被附加到原始路径。

例如,考虑以下代码片段:

// Solaris

Path p1 = Paths.get("/home/joe/foo");

// Result is /home/joe/foo/bar

System.out.format("%s%n", p1.resolve("bar"));

or

// Microsoft Windows

Path p1 = Paths.get("C:\\home\\joe\\foo");

// Result is C:\home\joe\foo\bar

System.out.format("%s%n", p1.resolve("bar"));

将绝对路径传递给 resolve 方法会返回传入的路径:

// Result is /home/joe

Paths.get("foo").resolve("/home/joe");

2.2.2.1.6 Creating a Path Between Two Paths

编写文件 I/O 代码时的一个常见要求是能够构建从文件系统中的一个位置到另一个位置的路径。您可以使用 relativize 方法来满足这一点。此方法构造一个路径,该路径源自原始路径,并在传入路径指定的位置结束。新路径相对于原始路径。

例如,考虑定义为 joe 和 sally 的两个相对路径:

Path p1 = Paths.get("joe");

Path p2 = Paths.get("sally");

在没有任何其他信息的情况下,假设 joe 和 sally 是兄弟姐妹,这意味着节点位于树结构中的同一级别。要从 joe 导航到 sally,您需要先向上导航到父节点,然后再向下导航到 sally:

// Result is ../sally

Path p1_to_p2 = p1.relativize(p2);

// Result is ../joe

Path p2_to_p1 = p2.relativize(p1);

考虑一个稍微复杂一点的例子:

Path p1 = Paths.get("home");

Path p3 = Paths.get("home/sally/bar");

// Result is sally/bar

Path p1_to_p3 = p1.relativize(p3);

// Result is ../..

Path p3_to_p1 = p3.relativize(p1);

2.2.2.1.7 Comparing Two Paths

Path 类支持 equals ,使您能够测试两条路径是否相等。 startsWith 和 endsWith 方法使您能够测试路径是否以特定字符串开头或结尾。这些方法很容易使用。例如:

Path path = ...;

Path otherPath = ...;

Path beginning = Paths.get("/home");

Path ending = Paths.get("foo");

if (path.equals(otherPath)) {

// equality logic here

} else if (path.startsWith(beginning)) {

// path begins with "/home"

} else if (path.endsWith(ending)) {

// path ends with "foo"

}

Path 类实现了 Iterable 接口。迭代器方法返回一个对象,使您能够迭代路径中的名称元素。返回的第一个元素是目录树中最接近根的元素。以下代码片段遍历路径,打印每个名称元素:

Path path = ...;

for (Path name: path) {

System.out.println(name);

}

Path 类还实现了 Comparable 接口。您可以使用 compareTo 比较 Path 对象,这对于排序很有用。

2.2.3 File Operations

Files 类是 java.nio.file 包的另一个主要入口点。此类提供了一组丰富的静态方法,用于读取、写入和操作文件和目录。 Files 方法适用于 Path 对象的实例。

2.2.3.1 Releasing System Resources

此 API 中使用的许多资源(例如流或通道)都实现或扩展了 java.io.Closeable 接口。 Closeable 资源的要求是当不再需要时必须调用 close 方法来释放资源。忽略关闭资源会对应用程序的性能产生负面影响。下一节中描述的 try-with-resources 语句将为您处理此步骤。

2.2.3.2 Catching Exceptions

对于文件 I/O,意外情况是不争的事实:文件按预期存在(或不存在),程序无权访问文件系统,默认文件系统实现不支持特定功能, 等等。可能会遇到许多错误。

所有访问文件系统的方法都可能抛出 IOException。最佳实践是通过将这些方法嵌入到 Java SE 7 版本中引入的 try-with-resources 语句中来捕获这些异常。 try-with-resources 语句的优点是编译器会在不再需要时自动生成关闭资源的代码。以下代码显示了它的外观:

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);

}

或者,您可以将文件 I/O 方法嵌入到 try 块中,然后在 catch 块中捕获任何异常。如果您的代码打开了任何流或通道,您应该在 finally 块中关闭它们。前面的示例使用 try-catch-finally 方法看起来类似于以下内容:

Charset charset = Charset.forName("US-ASCII");

String s = ...;

BufferedWriter writer = null;

try {

writer = Files.newBufferedWriter(file, charset);

writer.write(s, 0, s.length());

} catch (IOException x) {

System.err.format("IOException: %s%n", x);

} finally {

if (writer != null) writer.close();

}

除了 IOException,许多特定的异常都扩展了 FileSystemException。这个类有一些有用的方法,它们返回所涉及的文件(getFile)、详细的消息字符串(getMessage)、文件系统操作失败的原因(getReason)以及所涉及的“其他”文件,如果有的话(getOtherFile)。

以下代码片段显示了如何使用 getFile 方法:

try (...) {

...

} catch (NoSuchFileException x) {

System.err.format("%s does not exist\n", x.getFile());

}

2.2.3.3 Varargs

当指定标志时,几个 Files 方法接受任意数量的参数。例如,在以下方法签名中,CopyOption 参数后面的省略号表示该方法接受可变数量的参数或可变参数,因为它们通常被称为:

Path Files.move(Path, Path, CopyOption...)

当方法接受 varargs 参数时,您可以将逗号分隔的值列表或值的数组 (CopyOption[]) 传递给它。

在 move 示例中,可以按如下方式调用该方法:

import static java.nio.file.StandardCopyOption.*;

Path source = ...;

Path target = ...;

Files.move(source,

target,

REPLACE_EXISTING,

ATOMIC_MOVE);

2.2.3.4 Atomic Operations

一些 Files 方法,例如 move,可以在某些文件系统中原子地执行某些操作。

原子文件操作是不能被中断或“部分”执行的操作。要么执行整个操作,要么操作失败。当您有多个进程在文件系统的同一区域上运行时,这一点很重要,并且您需要保证每个进程都访问一个完整的文件。

2.2.3.5 Method Chaining

许多文件 I/O 方法都支持方法链的概念。

您首先调用一个返回对象的方法。然后,您立即在该对象上调用一个方法,该方法返回另一个对象,依此类推。许多 I/O 示例使用以下技术:

String value = Charset.defaultCharset().decode(buf).toString();

UserPrincipal group =

file.getFileSystem().getUserPrincipalLookupService().

lookupPrincipalByName("me");

这种技术可以生成紧凑的代码,并使您能够避免声明不需要的临时变量。

2.2.3.6 What Is a Glob?

有关 glob 语法的更多信息,请参阅 FileSystem 类中 getPathMatcher 方法的 API 规范。

2.2.3.7 Link Awareness

Files 类是“链接感知的”。每个 Files 方法要么检测遇到符号链接时要做什么,要么提供一个选项,使您能够配置遇到符号链接时的行为。

2.2.4 Checking a File or Directory

您有一个表示文件或目录的 Path 实例,但该文件是否存在于文件系统中?它可读吗?可写?可执行吗?

2.2.4.1 Verifying the Existence of a File or Directory

请注意,!Files.exists(path) 不等同于 Files.notExists(path)。当您测试文件的存在时,可能会出现三种结果:

The file is verified to exist. The file is verified to not exist. The file’s status is unknown. This result can occur when the program does not have access to the file.

如果exists和notExists都返回false,则无法验证文件是否存在。

2.2.4.2 Checking File Accessibility

要验证程序是否可以根据需要访问文件,可以使用 isReadable(Path)、isWritable(Path) 和 isExecutable(Path) 方法。

以下代码片段验证特定文件是否存在以及程序是否能够执行该文件。

Path file = ...;

boolean isRegularExecutableFile = Files.isRegularFile(file) &

Files.isReadable(file) & Files.isExecutable(file);

2.2.4.3 Checking Whether Two Paths Locate the Same File

当您有一个使用符号链接的文件系统时,可能有两个不同的路径来定位同一个文件。 isSameFile(Path, Path) 方法比较两个路径以确定它们是否在文件系统上定位相同的文件。例如:

Path p1 = ...;

Path p2 = ...;

if (Files.isSameFile(p1, p2)) {

// Logic when the paths locate the same file

}

2.2.5 Deleting a File or Directory

您可以删除文件、目录或链接。对于符号链接,链接被删除,而不是链接的目标。对于目录,目录必须为空,否则删除失败。

Files 类提供了两种删除方法。

delete(Path) 方法删除文件,如果删除失败则抛出异常。例如,如果文件不存在,则会引发 NoSuchFileException。您可以捕获异常以确定删除失败的原因,如下所示:

try {

Files.delete(path);

} catch (NoSuchFileException x) {

System.err.format("%s: no such" + " file or directory%n", path);

} catch (DirectoryNotEmptyException x) {

System.err.format("%s not empty%n", path);

} catch (IOException x) {

// File permission problems are caught here.

System.err.println(x);

}

deleteIfExists(Path) 方法也会删除文件,但如果文件不存在,则不会抛出异常。当您有多个线程删除文件并且您不想仅仅因为一个线程首先这样做而引发异常时,静默失败非常有用。

2.2.6 Copying a File or Directory

您可以使用 copy(Path, Path, CopyOption...) 方法复制文件或目录。如果目标文件存在,则复制失败,除非指定了 REPLACE_EXISTING 选项。

可以复制目录。但是,不会复制目录中的文件,因此即使原始目录包含文件,新目录也是空的。

复制符号链接时,复制链接的目标。如果要复制链接本身,而不是链接的内容,请指定 NOFOLLOW_LINKS 或 REPLACE_EXISTING 选项。

此方法采用 varargs 参数。支持以下 StandardCopyOption 和 LinkOption 枚举:

下面展示了如何使用复制方法:

import static java.nio.file.StandardCopyOption.*;

...

Files.copy(source, target, REPLACE_EXISTING);

除了文件复制之外,Files 类还定义了可用于在文件和流之间进行复制的方法。 copy(InputStream, Path, CopyOptions...) 方法可用于将输入流中的所有字节复制到文件中。 copy(Path, OutputStream) 方法可用于将文件中的所有字节复制到输出流。

example

import java.nio.file.*;

import static java.nio.file.StandardCopyOption.*;

import java.nio.file.attribute.*;

import static java.nio.file.FileVisitResult.*;

import java.io.IOException;

import java.util.*;

/**

* Sample code that copies files in a similar manner to the cp(1) program.

*/

public class Copy {

/**

* Returns {@code true} if okay to overwrite a file ("cp -i")

*/

static boolean okayToOverwrite(Path file) {

String answer = System.console().readLine("overwrite %s (yes/no)? ", file);

return (answer.equalsIgnoreCase("y") || answer.equalsIgnoreCase("yes"));

}

/**

* Copy source file to target location. If {@code prompt} is true then

* prompt user to overwrite target if it exists. The {@code preserve}

* parameter determines if file attributes should be copied/preserved.

*/

static void copyFile(Path source, Path target, boolean prompt, boolean preserve) {

CopyOption[] options = (preserve) ?

new CopyOption[] { COPY_ATTRIBUTES, REPLACE_EXISTING } :

new CopyOption[] { REPLACE_EXISTING };

if (!prompt || Files.notExists(target) || okayToOverwrite(target)) {

try {

Files.copy(source, target, options);

} catch (IOException x) {

System.err.format("Unable to copy: %s: %s%n", source, x);

}

}

}

/**

* A {@code FileVisitor} that copies a file-tree ("cp -r")

*/

static class TreeCopier implements FileVisitor {

private final Path source;

private final Path target;

private final boolean prompt;

private final boolean preserve;

TreeCopier(Path source, Path target, boolean prompt, boolean preserve) {

this.source = source;

this.target = target;

this.prompt = prompt;

this.preserve = preserve;

}

@Override

public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {

// before visiting entries in a directory we copy the directory

// (okay if directory already exists).

CopyOption[] options = (preserve) ?

new CopyOption[] { COPY_ATTRIBUTES } : new CopyOption[0];

Path newdir = target.resolve(source.relativize(dir));

try {

Files.copy(dir, newdir, options);

} catch (FileAlreadyExistsException x) {

// ignore

} catch (IOException x) {

System.err.format("Unable to create: %s: %s%n", newdir, x);

return SKIP_SUBTREE;

}

return CONTINUE;

}

@Override

public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

copyFile(file, target.resolve(source.relativize(file)),

prompt, preserve);

return CONTINUE;

}

@Override

public FileVisitResult postVisitDirectory(Path dir, IOException exc) {

// fix up modification time of directory when done

if (exc == null && preserve) {

Path newdir = target.resolve(source.relativize(dir));

try {

FileTime time = Files.getLastModifiedTime(dir);

Files.setLastModifiedTime(newdir, time);

} catch (IOException x) {

System.err.format("Unable to copy all attributes to: %s: %s%n", newdir, x);

}

}

return CONTINUE;

}

@Override

public FileVisitResult visitFileFailed(Path file, IOException exc) {

if (exc instanceof FileSystemLoopException) {

System.err.println("cycle detected: " + file);

} else {

System.err.format("Unable to copy: %s: %s%n", file, exc);

}

return CONTINUE;

}

}

static void usage() {

System.err.println("java Copy [-ip] source... target");

System.err.println("java Copy -r [-ip] source-dir... target");

System.exit(-1);

}

public static void main(String[] args) throws IOException {

boolean recursive = false;

boolean prompt = false;

boolean preserve = false;

// process options

int argi = 0;

while (argi < args.length) {

String arg = args[argi];

if (!arg.startsWith("-"))

break;

if (arg.length() < 2)

usage();

for (int i=1; i

char c = arg.charAt(i);

switch (c) {

case 'r' : recursive = true; break;

case 'i' : prompt = true; break;

case 'p' : preserve = true; break;

default : usage();

}

}

argi++;

}

// remaining arguments are the source files(s) and the target location

int remaining = args.length - argi;

if (remaining < 2)

usage();

Path[] source = new Path[remaining-1];

int i=0;

while (remaining > 1) {

source[i++] = Paths.get(args[argi++]);

remaining--;

}

Path target = Paths.get(args[argi]);

// check if target is a directory

boolean isDir = Files.isDirectory(target);

// copy each source file/directory to target

for (i=0; i

Path dest = (isDir) ? target.resolve(source[i].getFileName()) : target;

if (recursive) {

// follow links when copying files

EnumSet opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);

TreeCopier tc = new TreeCopier(source[i], dest, prompt, preserve);

Files.walkFileTree(source[i], opts, Integer.MAX_VALUE, tc);

} else {

// not recursive so source must not be a directory

if (Files.isDirectory(source[i])) {

System.err.format("%s: is a directory%n", source[i]);

continue;

}

copyFile(source[i], dest, prompt, preserve);

}

}

}

}

2.2.7 Moving a File or Directory

您可以使用 move(Path, Path, CopyOption...) 方法移动文件或目录。如果目标文件存在,则移动失败,除非指定了 REPLACE_EXISTING 选项。

可以移动空目录。如果目录不为空,则当目录可以移动而不移动该目录的内容时,允许移动。在 UNIX 系统上,在同一分区内移动目录通常包括重命名目录。在这种情况下,即使目录包含文件,此方法也有效。

import static java.nio.file.StandardCopyOption.*;

...

Files.move(source, target, REPLACE_EXISTING);

尽管您可以如图所示在单个目录上实现 move 方法,但该方法最常与文件树递归机制一起使用。

2.2.8 Managing Metadata (File and File Store Attributes)

元数据的定义是“关于其他数据的数据”。对于文件系统,数据包含在其文件和目录中,元数据跟踪有关每个对象的信息:它是常规文件、目录还是链接?它的大小、创建日期、上次修改日期、文件所有者、组所有者和访问权限是多少?

文件系统的元数据通常称为其文件属性。 Files 类包括可用于获取文件的单个属性或设置属性的方法。

MethodsCommentsize(Path)Returns the size of the specified file in bytes.isDirectory(Path, LinkOption)Returns true if the specified Path locates a file that is a directory.isRegularFile(Path, LinkOption...)Returns true if the specified Path locates a file that is a regular file.isSymbolicLink(Path)Returns true if the specified Path locates a file that is a symbolic link.isHidden(Path)Returns true if the specified Path locates a file that is considered hidden by the file system.getLastModifiedTime(Path, LinkOption...)setLastModifiedTime(Path, FileTime)Returns or sets the specified file’s last modified time.getOwner(Path, LinkOption...)setOwner(Path, UserPrincipal)Returns or sets the owner of the file.getPosixFilePermissions(Path, LinkOption...)setPosixFilePermissions(Path, Set)Returns or sets a file’s POSIX file permissions.getAttribute(Path, String, LinkOption...)setAttribute(Path, String, Object, LinkOption...)Returns or sets the value of a file attribute.

如果程序几乎同时需要多个文件属性,则使用检索单个属性的方法可能效率低下。重复访问文件系统以检索单个属性会对性能产生不利影响。出于这个原因,Files 类提供了两个 readAttributes 方法来在一个批量操作中获取文件的属性。

MethodCommentreadAttributes(Path, String, LinkOption...)Reads a file’s attributes as a bulk operation. The String parameter identifies the attributes to be read.readAttributes(Path, Class, LinkOption...)Reads a file’s attributes as a bulk operation. The Class parameter is the type of attributes requested and the method returns an object of that class.

2.2.8.1 Basic File Attributes

example

Path file = ...;

BasicFileAttributes attr = Files.readAttributes(file, BasicFileAttributes.class);

System.out.println("creationTime: " + attr.creationTime());

System.out.println("lastAccessTime: " + attr.lastAccessTime());

System.out.println("lastModifiedTime: " + attr.lastModifiedTime());

System.out.println("isDirectory: " + attr.isDirectory());

System.out.println("isOther: " + attr.isOther());

System.out.println("isRegularFile: " + attr.isRegularFile());

System.out.println("isSymbolicLink: " + attr.isSymbolicLink());

System.out.println("size: " + attr.size());

2.2.8.2 Setting Time Stamps

example

Path file = ...;

BasicFileAttributes attr =

Files.readAttributes(file, BasicFileAttributes.class);

long currentTime = System.currentTimeMillis();

FileTime ft = FileTime.fromMillis(currentTime);

Files.setLastModifiedTime(file, ft);

}

2.2.8.3 DOS File Attributes

example1

Path file = ...;

try {

DosFileAttributes attr =

Files.readAttributes(file, DosFileAttributes.class);

System.out.println("isReadOnly is " + attr.isReadOnly());

System.out.println("isHidden is " + attr.isHidden());

System.out.println("isArchive is " + attr.isArchive());

System.out.println("isSystem is " + attr.isSystem());

} catch (UnsupportedOperationException x) {

System.err.println("DOS file" +

" attributes not supported:" + x);

}

example2

Path file = ...;

Files.setAttribute(file, "dos:hidden", true);

2.2.8.4 POSIX File Permissions

example1

Path file = ...;

PosixFileAttributes attr =

Files.readAttributes(file, PosixFileAttributes.class);

System.out.format("%s %s %s%n",

attr.owner().getName(),

attr.group().getName(),

PosixFilePermissions.toString(attr.permissions()));

example2

Path sourceFile = ...;

Path newFile = ...;

PosixFileAttributes attrs =

Files.readAttributes(sourceFile, PosixFileAttributes.class);

FileAttribute> attr =

PosixFilePermissions.asFileAttribute(attrs.permissions());

Files.createFile(file, attr);

example3

Path file = ...;

Set perms =

PosixFilePermissions.fromString("rw-------");

FileAttribute> attr =

PosixFilePermissions.asFileAttribute(perms);

Files.setPosixFilePermissions(file, perms);

example4

import java.nio.file.*;

import java.nio.file.attribute.*;

import static java.nio.file.attribute.PosixFilePermission.*;

import static java.nio.file.FileVisitResult.*;

import java.io.IOException;

import java.util.*;

/**

* Sample code that changes the permissions of files in a similar manner to the

* chmod(1) program.

*/

public class Chmod {

/**

* Compiles a list of one or more symbolic mode expressions that

* may be used to change a set of file permissions. This method is

* intended for use where file permissions are required to be changed in

* a manner similar to the UNIX chmod program.

*

*

The {@code exprs} parameter is a comma separated list of expressions

* where each takes the form:

*

* who operator [permissions]

*

* where who is one or more of the characters {@code 'u'}, {@code 'g'},

* {@code 'o'}, or {@code 'a'} meaning the owner (user), group, others, or

* all (owner, group, and others) respectively.

*

*

operator is the character {@code '+'}, {@code '-'}, or {@code

* '='} signifying how permissions are to be changed. {@code '+'} means the

* permissions are added, {@code '-'} means the permissions are removed, and

* {@code '='} means the permissions are assigned absolutely.

*

*

permissions is a sequence of zero or more of the following:

* {@code 'r'} for read permission, {@code 'w'} for write permission, and

* {@code 'x'} for execute permission. If permissions is omitted

* when assigned absolutely, then the permissions are cleared for

* the owner, group, or others as identified by who. When omitted

* when adding or removing then the expression is ignored.

*

*

The following examples demonstrate possible values for the {@code

* exprs} parameter:

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

{@code u=rw} Sets the owner permissions to be read and write.
{@code ug+w} Sets the owner write and group write permissions.
{@code u+w,o-rwx} Sets the owner write, and removes the others read, others write

* and others execute permissions.

{@code o=} Sets the others permission to none (others read, others write and

* others execute permissions are removed if set)

*

* @param exprs

* List of one or more symbolic mode expressions

*

* @return A {@code Changer} that may be used to changer a set of

* file permissions

*

* @throws IllegalArgumentException

* If the value of the {@code exprs} parameter is invalid

*/

public static Changer compile(String exprs) {

// minimum is who and operator (u= for example)

if (exprs.length() < 2)

throw new IllegalArgumentException("Invalid mode");

// permissions that the changer will add or remove

final Set toAdd = new HashSet();

final Set toRemove = new HashSet();

// iterate over each of expression modes

for (String expr: exprs.split(",")) {

// minimum of who and operator

if (expr.length() < 2)

throw new IllegalArgumentException("Invalid mode");

int pos = 0;

// who

boolean u = false;

boolean g = false;

boolean o = false;

boolean done = false;

for (;;) {

switch (expr.charAt(pos)) {

case 'u' : u = true; break;

case 'g' : g = true; break;

case 'o' : o = true; break;

case 'a' : u = true; g = true; o = true; break;

default : done = true;

}

if (done)

break;

pos++;

}

if (!u && !g && !o)

throw new IllegalArgumentException("Invalid mode");

// get operator and permissions

char op = expr.charAt(pos++);

String mask = (expr.length() == pos) ? "" : expr.substring(pos);

// operator

boolean add = (op == '+');

boolean remove = (op == '-');

boolean assign = (op == '=');

if (!add && !remove && !assign)

throw new IllegalArgumentException("Invalid mode");

// who= means remove all

if (assign && mask.length() == 0) {

assign = false;

remove = true;

mask = "rwx";

}

// permissions

boolean r = false;

boolean w = false;

boolean x = false;

for (int i=0; i

switch (mask.charAt(i)) {

case 'r' : r = true; break;

case 'w' : w = true; break;

case 'x' : x = true; break;

default:

throw new IllegalArgumentException("Invalid mode");

}

}

// update permissions set

if (add) {

if (u) {

if (r) toAdd.add(OWNER_READ);

if (w) toAdd.add(OWNER_WRITE);

if (x) toAdd.add(OWNER_EXECUTE);

}

if (g) {

if (r) toAdd.add(GROUP_READ);

if (w) toAdd.add(GROUP_WRITE);

if (x) toAdd.add(GROUP_EXECUTE);

}

if (o) {

if (r) toAdd.add(OTHERS_READ);

if (w) toAdd.add(OTHERS_WRITE);

if (x) toAdd.add(OTHERS_EXECUTE);

}

}

if (remove) {

if (u) {

if (r) toRemove.add(OWNER_READ);

if (w) toRemove.add(OWNER_WRITE);

if (x) toRemove.add(OWNER_EXECUTE);

}

if (g) {

if (r) toRemove.add(GROUP_READ);

if (w) toRemove.add(GROUP_WRITE);

if (x) toRemove.add(GROUP_EXECUTE);

}

if (o) {

if (r) toRemove.add(OTHERS_READ);

if (w) toRemove.add(OTHERS_WRITE);

if (x) toRemove.add(OTHERS_EXECUTE);

}

}

if (assign) {

if (u) {

if (r) toAdd.add(OWNER_READ);

else toRemove.add(OWNER_READ);

if (w) toAdd.add(OWNER_WRITE);

else toRemove.add(OWNER_WRITE);

if (x) toAdd.add(OWNER_EXECUTE);

else toRemove.add(OWNER_EXECUTE);

}

if (g) {

if (r) toAdd.add(GROUP_READ);

else toRemove.add(GROUP_READ);

if (w) toAdd.add(GROUP_WRITE);

else toRemove.add(GROUP_WRITE);

if (x) toAdd.add(GROUP_EXECUTE);

else toRemove.add(GROUP_EXECUTE);

}

if (o) {

if (r) toAdd.add(OTHERS_READ);

else toRemove.add(OTHERS_READ);

if (w) toAdd.add(OTHERS_WRITE);

else toRemove.add(OTHERS_WRITE);

if (x) toAdd.add(OTHERS_EXECUTE);

else toRemove.add(OTHERS_EXECUTE);

}

}

}

// return changer

return new Changer() {

@Override

public Set change(Set perms) {

perms.addAll(toAdd);

perms.removeAll(toRemove);

return perms;

}

};

}

/**

* A task that changes a set of {@link PosixFilePermission} elements.

*/

public interface Changer {

/**

* Applies the changes to the given set of permissions.

*

* @param perms

* The set of permissions to change

*

* @return The {@code perms} parameter

*/

Set change(Set perms);

}

/**

* Changes the permissions of the file using the given Changer.

*/

static void chmod(Path file, Changer changer) {

try {

Set perms = Files.getPosixFilePermissions(file);

Files.setPosixFilePermissions(file, changer.change(perms));

} catch (IOException x) {

System.err.println(x);

}

}

/**

* Changes the permission of each file and directory visited

*/

static class TreeVisitor implements FileVisitor {

private final Changer changer;

TreeVisitor(Changer changer) {

this.changer = changer;

}

@Override

public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {

chmod(dir, changer);

return CONTINUE;

}

@Override

public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

chmod(file, changer);

return CONTINUE;

}

@Override

public FileVisitResult postVisitDirectory(Path dir, IOException exc) {

if (exc != null)

System.err.println("WARNING: " + exc);

return CONTINUE;

}

@Override

public FileVisitResult visitFileFailed(Path file, IOException exc) {

System.err.println("WARNING: " + exc);

return CONTINUE;

}

}

static void usage() {

System.err.println("java Chmod [-R] symbolic-mode-list file...");

System.exit(-1);

}

public static void main(String[] args) throws IOException {

if (args.length < 2)

usage();

int argi = 0;

int maxDepth = 0;

if (args[argi].equals("-R")) {

if (args.length < 3)

usage();

argi++;

maxDepth = Integer.MAX_VALUE;

}

// compile the symbolic mode expressions

Changer changer = compile(args[argi++]);

TreeVisitor visitor = new TreeVisitor(changer);

Set opts = Collections.emptySet();

while (argi < args.length) {

Path file = Paths.get(args[argi]);

Files.walkFileTree(file, opts, maxDepth, visitor);

argi++;

}

}

}

2.2.8.5 Setting a File or Group Owner

要将名称转换为可以存储为文件所有者或组所有者的对象,可以使用 UserPrincipalLookupService 服务。此服务将名称或组名称作为字符串查找,并返回表示该字符串的 UserPrincipal 对象。您可以使用 FileSystem.getUserPrincipalLookupService 方法获取默认文件系统的用户主体查找服务。

以下代码片段显示了如何使用 setOwner 方法设置文件所有者:

Path file = ...;

UserPrincipal owner = file.GetFileSystem().getUserPrincipalLookupService()

.lookupPrincipalByName("sally");

Files.setOwner(file, owner);

Files 类中没有用于设置组所有者的特殊方法。但是,直接执行此操作的安全方法是通过 POSIX 文件属性视图,如下所示:

Path file = ...;

GroupPrincipal group =

file.getFileSystem().getUserPrincipalLookupService()

.lookupPrincipalByGroupName("green");

Files.getFileAttributeView(file, PosixFileAttributeView.class)

.setGroup(group);

2.2.8.6 User-Defined File Attributes

如果您的文件系统实现支持的文件属性不足以满足您的需求,您可以使用 UserDefinedAttributeView 创建和跟踪您自己的文件属性。

Some implementations map this concept to features like NTFS Alternative Data Streams and extended attributes on file systems such as ext3 and ZFS. Most implementations impose restrictions on the size of the value, for example, ext3 limits the size to 4 kilobytes.

可以使用以下代码片段将文件的 MIME 类型存储为用户定义的属性:

Path file = ...;

UserDefinedFileAttributeView view = Files

.getFileAttributeView(file, UserDefinedFileAttributeView.class);

view.write("user.mimetype",

Charset.defaultCharset().encode("text/html");

要读取 MIME 类型属性,您可以使用以下代码片段:

Path file = ...;

UserDefinedFileAttributeView view = Files

.getFileAttributeView(file,UserDefinedFileAttributeView.class);

String name = "user.mimetype";

ByteBuffer buf = ByteBuffer.allocate(view.size(name));

view.read(name, buf);

buf.flip();

String value = Charset.defaultCharset().decode(buf).toString();

example

import java.nio.ByteBuffer;

import java.nio.charset.Charset;

import java.nio.file.*;

import java.nio.file.attribute.*;

import java.io.IOException;

/**

* Example code to list/set/get/delete the user-defined attributes of a file.

*/

public class Xdd {

static void usage() {

System.out.println("Usage: java Xdd ");

System.out.println(" java Xdd -set = ");

System.out.println(" java Xdd -get ");

System.out.println(" java Xdd -del ");

System.exit(-1);

}

public static void main(String[] args) throws IOException {

// one or three parameters

if (args.length != 1 && args.length != 3)

usage();

Path file = (args.length == 1) ?

Paths.get(args[0]) : Paths.get(args[2]);

// check that user defined attributes are supported by the file store

FileStore store = Files.getFileStore(file);

if (!store.supportsFileAttributeView(UserDefinedFileAttributeView.class)) {

System.err.format("UserDefinedFileAttributeView not supported on %s\n", store);

System.exit(-1);

}

UserDefinedFileAttributeView view = Files.

getFileAttributeView(file, UserDefinedFileAttributeView.class);

// list user defined attributes

if (args.length == 1) {

System.out.println(" Size Name");

System.out.println("-------- --------------------------------------");

for (String name: view.list()) {

System.out.format("%8d %s\n", view.size(name), name);

}

return;

}

// Add/replace a file's user defined attribute

if (args[0].equals("-set")) {

// name=value

String[] s = args[1].split("=");

if (s.length != 2)

usage();

String name = s[0];

String value = s[1];

view.write(name, Charset.defaultCharset().encode(value));

return;

}

// Print out the value of a file's user defined attribute

if (args[0].equals("-get")) {

String name = args[1];

int size = view.size(name);

ByteBuffer buf = ByteBuffer.allocateDirect(size);

view.read(name, buf);

buf.flip();

System.out.println(Charset.defaultCharset().decode(buf).toString());

return;

}

// Delete a file's user defined attribute

if (args[0].equals("-del")) {

view.delete(args[1]);

return;

}

// option not recognized

usage();

}

}

2.2.8.7 File Store Attributes

您可以使用 FileStore 类来了解有关文件存储的信息,例如可用空间量。 getFileStore(Path) 方法获取指定文件的文件存储。

以下代码片段打印特定文件所在的文件存储的空间使用情况:

Path file = ...;

FileStore store = Files.getFileStore(file);

long total = store.getTotalSpace() / 1024;

long used = (store.getTotalSpace() -

store.getUnallocatedSpace()) / 1024;

long avail = store.getUsableSpace() / 1024;

example

import java.nio.file.*;

import java.nio.file.attribute.*;

import java.io.IOException;

/**

* Example utility that works like the df(1M) program to print out disk space

* information

*/

public class DiskUsage {

static final long K = 1024;

static void printFileStore(FileStore store) throws IOException {

long total = store.getTotalSpace() / K;

long used = (store.getTotalSpace() - store.getUnallocatedSpace()) / K;

long avail = store.getUsableSpace() / K;

String s = store.toString();

if (s.length() > 20) {

System.out.println(s);

s = "";

}

System.out.format("%-20s %12d %12d %12d\n", s, total, used, avail);

}

public static void main(String[] args) throws IOException {

System.out.format("%-20s %12s %12s %12s\n", "Filesystem", "kbytes", "used", "avail");

if (args.length == 0) {

FileSystem fs = FileSystems.getDefault();

for (FileStore store: fs.getFileStores()) {

printFileStore(store);

}

} else {

for (String file: args) {

FileStore store = Files.getFileStore(Paths.get(file));

printFileStore(store);

}

}

}

}

2.2.9 Reading, Writing, and Creating Files

本页讨论读取、写入、创建和打开文件的详细信息。有多种文件 I/O 方法可供选择。为了帮助理解 API,下图按复杂程度排列了文件 I/O 方法。

2.2.9.1 The OpenOptions Parameter

本节中的一些方法采用可选的 OpenOptions 参数。此参数是可选的,API 会告诉您在未指定时该方法的默认行为是什么。

支持以下 StandardOpenOptions 枚举:

WRITE – Opens the file for write access.APPEND – Appends the new data to the end of the file. This option is used with the WRITE or CREATE options.TRUNCATE_EXISTING – Truncates the file to zero bytes. This option is used with the WRITE option.CREATE_NEW – Creates a new file and throws an exception if the file already exists.CREATE – Opens the file if it exists or creates a new file if it does not.DELETE_ON_CLOSE – Deletes the file when the stream is closed. This option is useful for temporary files.SPARSE – Hints that a newly created file will be sparse. This advanced option is honored on some file systems, such as NTFS, where large files with data “gaps” can be stored in a more efficient manner where those empty gaps do not consume disk space.SYNC – Keeps the file (both content and metadata) synchronized with the underlying storage device.DSYNC – Keeps the file content synchronized with the underlying storage device.

2.2.9.2 Commonly Used Methods for Small Files

2.2.9.2.1 Reading All Bytes or Lines from a File

如果你有一个很小的文件并且你想一次性读取它的全部内容,你可以使用 readAllBytes(Path) 或 readAllLines(Path, Charset) 方法。这些方法为您处理大部分工作,例如打开和关闭流,但不适用于处理大文件。以下代码显示了如何使用 readAllBytes 方法:

Path file = ...;

byte[] fileArray;

fileArray = Files.readAllBytes(file);

2.2.9.2.2 Writing All Bytes or Lines to a File

您可以使用其中一种写入方法将字节或行写入文件。

write(Path, byte[], OpenOption...)write(Path, Iterable< extends CharSequence>, Charset, OpenOption...)

以下代码片段显示了如何使用 write 方法。

Path file = ...;

byte[] buf = ...;

Files.write(file, buf);

2.2.9.3 Buffered I/O Methods for Text Files

java.nio.file 包支持通道 I/O,它在缓冲区中移动数据,绕过一些可能成为流 I/O 瓶颈的层。

2.2.9.3.1 Reading a File by Using Buffered Stream I/O

以下代码片段显示了如何使用 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);

}

2.2.9.3.2 Writing a File by Using Buffered Stream I/O

以下代码片段显示了如何使用此方法创建以“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);

}

2.2.9.4 Methods for Unbuffered Streams and Interoperable with java.io APIs

2.2.9.4.1 Reading a File by Using Stream I/O

要打开文件进行读取,可以使用 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);

}

2.2.9.4.2 reating and Writing a File by Using Stream I/O

您可以使用 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) {

// Convert the string to a

// byte array.

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);

}

}

}

2.2.9.5 Methods for Channels and ByteBuffers

2.2.9.5.1 Reading and Writing Files by Using Channel I/O

ByteChannel 接口提供基本的读写功能。 SeekableByteChannel 是一个 ByteChannel,它能够在通道中保持一个位置并改变那个位置。 SeekableByteChannel 还支持截断与通道关联的文件并查询文件的大小。

移动到文件中的不同点然后从该位置读取或写入该位置的能力使得随机访问文件成为可能。

有两种读取和写入通道 I/O 的方法。

newByteChannel(Path, OpenOption...)newByteChannel(Path, Set, FileAttribute...)

两种 newByteChannel 方法都允许您指定 OpenOption 选项列表。支持 newOutputStream 方法使用的相同打开选项,此外还有一个选项: READ 是必需的,因为 SeekableByteChannel 支持读取和写入。

指定 READ 会打开读取通道。指定 WRITE 或 APPEND 打开写入通道。如果未指定这些选项,则打开通道以供读取。

以下代码片段读取文件并将其打印到标准输出:

public static void readFile(Path path) throws IOException {

// Files.newByteChannel() defaults to StandardOpenOption.READ

try (SeekableByteChannel sbc = Files.newByteChannel(path)) {

final int BUFFER_CAPACITY = 10;

ByteBuffer buf = ByteBuffer.allocate(BUFFER_CAPACITY);

// Read the bytes with the proper encoding for this platform. If

// you skip this step, you might see foreign or illegible

// characters.

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) {

// Create the set of options for appending to the file.

Set options = new HashSet();

options.add(APPEND);

options.add(CREATE);

// Create the custom permissions attribute.

Set perms =

PosixFilePermissions.fromString("rw-r-----");

FileAttribute> attr =

PosixFilePermissions.asFileAttribute(perms);

// Convert the string to a 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);

}

}

}

2.2.9.6 Methods for Creating Regular and Temporary Files

2.2.9.6.1 Creating Files

example

Path file = ...;

try {

// Create the empty file with default permissions, etc.

Files.createFile(file);

} catch (FileAlreadyExistsException x) {

System.err.format("file named %s" +

" already exists%n", file);

} catch (IOException x) {

// Some other sort of failure, such as permissions.

System.err.format("createFile error: %s%n", x);

}

您还可以使用 newOutputStream 方法创建新文件,如使用流 I/O 创建和写入文件中所述。如果您打开一个新的输出流并立即关闭它,则会创建一个空文件。

2.2.9.6.2 Creating Temporary Files

您可以使用以下 createTempFile 方法之一创建临时文件:

createTempFile(Path, String, String, FileAttribute)createTempFile(String, String, FileAttribute)

第一种方法允许代码为临时文件指定一个目录,第二种方法在默认的临时文件目录中创建一个新文件。这两种方法都允许您为文件名指定后缀,第一种方法还允许您指定前缀。以下代码片段给出了第二种方法的示例:

try {

Path tempFile = Files.createTempFile(null, ".myapp");

System.out.format("The temporary file" +

" has been created: %s%n", tempFile)

;

} catch (IOException x) {

System.err.format("IOException: %s%n", x);

}

运行此文件的结果将类似于以下内容:

The temporary file has been created: /tmp/509668702974537184.myapp

临时文件名的具体格式是平台特定的。

2.2.10 Random Access Files

随机访问文件允许对文件内容进行非顺序或随机访问。要随机访问文件,您需要打开文件,寻找特定位置,然后读取或写入该文件。

SeekableByteChannel 接口可以实现此功能。 SeekableByteChannel 接口使用当前位置的概念扩展了通道 I/O。方法使您能够设置或查询位置,然后您可以从该位置读取数据或将数据写入该位置。 API 由几个易于使用的方法组成:

position – Returns the channel’s current positionposition(long) – Sets the channel’s positionread(ByteBuffer) – Reads bytes into the buffer from the channelwrite(ByteBuffer) – Writes bytes from the buffer to the channeltruncate(long) – Truncates the file (or other entity) connected to the channel

在默认文件系统上,您可以按原样使用该通道,也可以将其转换为 FileChannel 以访问更高级的功能,例如将文件的一个区域直接映射到内存以便更快地访问,锁定文件,或从绝对位置读取和写入字节,而不影响通道的当前位置。

example

String s = "I was here!\n";

byte data[] = s.getBytes();

ByteBuffer out = ByteBuffer.wrap(data);

ByteBuffer copy = ByteBuffer.allocate(12);

try (FileChannel fc = (FileChannel.open(file, READ, WRITE))) {

// Read the first 12

// bytes of the file.

int nread;

do {

nread = fc.read(copy);

} while (nread != -1 && copy.hasRemaining());

// Write "I was here!" at the beginning of the file.

fc.position(0);

while (out.hasRemaining())

fc.write(out);

out.rewind();

// Move to the end of the file. Copy the first 12 bytes to

// the end of the file. Then write "I was here!" again.

long length = fc.size();

fc.position(length-1);

copy.flip();

while (copy.hasRemaining())

fc.write(copy);

while (out.hasRemaining())

fc.write(out);

} catch (IOException x) {

System.out.println("I/O Exception: " + x);

}

2.2.11 Creating and Reading Directories

前面讨论过的一些方法,例如删除、处理文件、链接和目录。但是如何列出文件系统顶部的所有目录呢?如何列出目录的内容或创建目录?

2.2.11.1 Listing a File System’s Root Directories

您可以使用 FileSystem.getRootDirectories 方法列出文件系统的所有根目录。此方法返回一个 Iterable,它使您能够使用增强的 for 语句来迭代所有根目录。

以下代码片段打印默认文件系统的根目录:

Iterable dirs = FileSystems.getDefault().getRootDirectories();

for (Path name: dirs) {

System.err.println(name);

}

2.2.11.2 Creating a Directory

您可以使用 createDirectory(Path, FileAttribute) 方法创建新目录。如果您不指定任何 FileAttributes,则新目录将具有默认属性。例如:

Path dir = ...;

Files.createDirectory(path);

以下代码片段在具有特定权限的 POSIX 文件系统上创建一个新目录:

Set perms =

PosixFilePermissions.fromString("rwxr-x---");

FileAttribute> attr =

PosixFilePermissions.asFileAttribute(perms);

Files.createDirectory(file, attr);

当一个或多个父目录可能尚不存在时,要创建多个级别的目录,您可以使用方便的方法 createDirectories(Path, FileAttribute)。与 createDirectory(Path, FileAttribute) 方法一样,您可以指定一组可选的初始文件属性。以下代码片段使用默认属性:

Files.createDirectories(Paths.get("foo/bar/test"));

2.2.11.3 Creating a Temporary Directory

您可以使用 createTempDirectory 方法之一创建临时目录:

createTempDirectory(Path, String, FileAttribute...)createTempDirectory(String, FileAttribute...)

第一种方法允许代码指定临时目录的位置,第二种方法在默认的临时文件目录中创建一个新目录。

2.2.11.4 Listing a Directory’s Contents

您可以使用 newDirectoryStream(Path) 方法列出目录的所有内容。此方法返回一个实现 DirectoryStream 接口的对象。实现 DirectoryStream 接口的类也实现了 Iterable,因此您可以遍历目录流,读取所有对象。这种方法可以很好地扩展到非常大的目录。

以下代码片段显示了如何打印目录的内容:

Path dir = ...;

try (DirectoryStream stream = Files.newDirectoryStream(dir)) {

for (Path file: stream) {

System.out.println(file.getFileName());

}

} catch (IOException | DirectoryIteratorException x) {

// IOException can never be thrown by the iteration.

// In this snippet, it can only be thrown by newDirectoryStream.

System.err.println(x);

}

迭代器返回的 Path 对象是针对目录解析的条目的名称。因此,如果您要列出 /tmp 目录的内容,则会以 /tmp/a、/tmp/b 等形式返回条目。

此方法返回目录的全部内容:文件、链接、子目录和隐藏文件。如果您想对检索的内容更有选择性,可以使用其他 newDirectoryStream 方法。

请注意,如果在目录迭代期间出现异常,则以 IOException 作为原因抛出 DirectoryIteratorException。迭代器方法不能抛出异常。

2.2.11.5 Filtering a Directory Listing By Using Globbing

example

Path dir = ...;

try (DirectoryStream stream =

Files.newDirectoryStream(dir, "*.{java,class,jar}")) {

for (Path entry: stream) {

System.out.println(entry.getFileName());

}

} catch (IOException x) {

// IOException can never be thrown by the iteration.

// In this snippet, it can // only be thrown by newDirectoryStream.

System.err.println(x);

}

2.2.11.6 Writing Your Own Directory Filter

也许您想根据模式匹配以外的某些条件过滤目录的内容。您可以通过实现 DirectoryStream.Filter 接口来创建自己的过滤器。该接口包含一个 accept 方法,它确定文件是否满足搜索要求。

example

DirectoryStream.Filter filter =

newDirectoryStream.Filter() {

public boolean accept(Path file) throws IOException {

try {

return (Files.isDirectory(path));

} catch (IOException x) {

// Failed to determine if it's a directory.

System.err.println(x);

return false;

}

}

};

use case

Path dir = ...;

try (DirectoryStream

stream = Files.newDirectoryStream(dir, filter)) {

for (Path entry: stream) {

System.out.println(entry.getFileName());

}

} catch (IOException x) {

System.err.println(x);

}

2.2.12 Links, Symbolic or Otherwise

如前所述,java.nio.file 包,尤其是 Path 类,是“链接感知的”。每个 Path 方法要么检测遇到符号链接时要做什么,要么提供一个选项,使您能够配置遇到符号链接时的行为。

到目前为止的讨论都是关于符号链接或软链接,但一些文件系统也支持硬链接。硬链接比符号链接更具限制性,如下:

The target of the link must exist.Hard links are generally not allowed on directories.Hard links are not allowed to cross partitions or volumes. Therefore, they cannot exist across file systems.A hard link looks, and behaves, like a regular file, so they can be hard to find.A hard link is, for all intents and purposes, the same entity as the original file. They have the same file permissions, time stamps, and so on. All attributes are identical.

由于这些限制,硬链接不像符号链接那样经常使用,但 Path 方法可以与硬链接无缝协作。

2.2.12.1 Creating a Symbolic Link

如果您的文件系统支持它,您可以使用 createSymbolicLink(Path, Path, FileAttribute) 方法创建符号链接。第二个 Path 参数表示目标文件或目录,可能存在也可能不存在。以下代码片段创建一个具有默认权限的符号链接:

Path newLink = ...;

Path target = ...;

try {

Files.createSymbolicLink(newLink, target);

} catch (IOException x) {

System.err.println(x);

} catch (UnsupportedOperationException x) {

// Some file systems do not support symbolic links.

System.err.println(x);

}

FileAttributes 可变参数使您能够指定在创建链接时自动设置的初始文件属性。但是,此参数旨在供将来使用,目前尚未实现。

2.2.12.2 Creating a Hard Link

您可以使用 createLink(Path, Path) 方法创建指向现有文件的硬(或常规)链接。第二个 Path 参数定位现有文件,它必须存在,否则会引发 NoSuchFileException。以下代码片段显示了如何创建链接:

Path newLink = ...;

Path existingFile = ...;

try {

Files.createLink(newLink, existingFile);

} catch (IOException x) {

System.err.println(x);

} catch (UnsupportedOperationException x) {

// Some file systems do not

// support adding an existing

// file to a directory.

System.err.println(x);

}

2.2.12.3 Detecting a Symbolic Link

要确定 Path 实例是否为符号链接,可以使用 isSymbolicLink(Path) 方法。以下代码片段显示了如何:

Path file = ...;

boolean isSymbolicLink =

Files.isSymbolicLink(file);

2.2.12.4 Finding the Target of a Link

您可以使用 readSymbolicLink(Path) 方法获取符号链接的目标,如下所示:

Path link = ...;

try {

System.out.format("Target of link" +

" '%s' is '%s'%n", link,

Files.readSymbolicLink(link));

} catch (IOException x) {

System.err.println(x);

}

如果 Path 不是符号链接,则此方法将引发 NotLinkException。

2.2.13 Walking the File Tree

您是否需要创建一个将递归访问文件树中所有文件的应用程序?也许您需要删除树中的每个 .class 文件,或者查找去年未访问过的每个文件。您可以使用 FileVisitor 接口执行此操作。

2.2.13.1 The FileVisitor Interface

要遍历文件树,首先需要实现 FileVisitor。FileVisitor 指定遍历过程中关键点所需的行为:访问文件时、访问目录之前、访问目录之后或发生故障时。该接口有四种方法对应这些情况:

preVisitDirectory – Invoked before a directory’s entries are visited.postVisitDirectory – Invoked after all the entries in a directory are visited. If any errors are encountered, the specific exception is passed to the method.visitFile – Invoked on the file being visited. The file’s BasicFileAttributes is passed to the method, or you can use the file attributes package to read a specific set of attributes. For example, you can choose to read the file’s DosFileAttributeView to determine if the file has the “hidden” bit set.visitFileFailed – Invoked when the file cannot be accessed. The specific exception is passed to the method. You can choose whether to throw the exception, print it to the console or a log file, and so on.

如果您不需要实现所有四个 FileVisitor 方法,而不是实现 FileVisitor 接口,您可以扩展 SimpleFileVisitor 类。该类实现了 FileVisitor 接口,访问树中的所有文件,并在遇到错误时抛出 IOError。您可以扩展此类并仅覆盖您需要的方法。

example

import static java.nio.file.FileVisitResult.*;

public static class PrintFiles

extends SimpleFileVisitor {

// Print information about

// each type of file.

@Override

public FileVisitResult visitFile(Path file,

BasicFileAttributes attr) {

if (attr.isSymbolicLink()) {

System.out.format("Symbolic link: %s ", file);

} else if (attr.isRegularFile()) {

System.out.format("Regular file: %s ", file);

} else {

System.out.format("Other: %s ", file);

}

System.out.println("(" + attr.size() + "bytes)");

return CONTINUE;

}

// Print each directory visited.

@Override

public FileVisitResult postVisitDirectory(Path dir,

IOException exc) {

System.out.format("Directory: %s%n", dir);

return CONTINUE;

}

// If there is some error accessing

// the file, let the user know.

// If you don't override this method

// and an error occurs, an IOException

// is thrown.

@Override

public FileVisitResult visitFileFailed(Path file,

IOException exc) {

System.err.println(exc);

return CONTINUE;

}

}

2.2.13.2 Kickstarting the Process

实现 FileVisitor 后,如何启动文件遍历? Files 类中有两个 walkFileTree 方法。

walkFileTree(Path, FileVisitor)walkFileTree(Path, Set, int, FileVisitor)

第一种方法只需要一个起点和一个 FileVisitor 实例。您可以调用 PrintFiles 文件访问器,如下所示:

Path startingDir = ...;

PrintFiles pf = new PrintFiles();

Files.walkFileTree(startingDir, pf);

第二个 walkFileTree 方法使您可以另外指定访问级别的数量限制和一组 FileVisitOption 枚举。如果要确保此方法遍历整个文件树,可以指定 Integer.MAX_VALUE 作为最大深度参数。

您可以指定 FileVisitOption 枚举 FOLLOW_LINKS,它指示应遵循符号链接。

此代码片段显示了如何调用四参数方法:

import static java.nio.file.FileVisitResult.*;

Path startingDir = ...;

EnumSet opts = EnumSet.of(FOLLOW_LINKS);

Finder finder = new Finder(pattern);

Files.walkFileTree(startingDir, opts, Integer.MAX_VALUE, finder);

2.2.13.3 Considerations When Creating a FileVisitor

文件树首先遍历深度,但您不能对访问子目录的迭代顺序做出任何假设。

如果您的程序将更改文件系统,则需要仔细考虑如何实现 FileVisitor。

例如,如果您正在编写递归删除,您首先删除目录中的文件,然后再删除目录本身。在这种情况下,您删除 postVisitDirectory 中的目录。

如果您正在编写递归副本,请先在 preVisitDirectory 中创建新目录,然后再尝试将文件复制到该目录(在 visitFile 中)。如果要保留源目录的属性(类似于 UNIX cp -p 命令),则需要在文件复制后在 postVisitDirectory 中执行此操作。 Copy 示例显示了如何执行此操作。

如果您正在编写文件搜索,则在 visitFile 方法中执行比较。此方法查找与您的条件匹配的所有文件,但找不到目录。如果要查找文件和目录,还必须在 preVisitDirectory 或 postVisitDirectory 方法中执行比较。 Find 示例显示了如何执行此操作。

您需要决定是否要遵循符号链接。例如,如果您要删除文件,则可能不建议使用符号链接。如果您要复制文件树,您可能希望允许它。默认情况下,walkFileTree 不遵循符号链接。

为文件调用 visitFile 方法。如果您已指定 FOLLOW_LINKS 选项并且您的文件树具有到父目录的循环链接,则在 visitFileFailed 方法中报告循环目录并带有 FileSystemLoopException。以下代码片段显示了如何捕获循环链接,来自 Copy 示例:

@Override

public FileVisitResult

visitFileFailed(Path file,

IOException exc) {

if (exc instanceof FileSystemLoopException) {

System.err.println("cycle detected: " + file);

} else {

System.err.format("Unable to copy:" + " %s: %s%n", file, exc);

}

return CONTINUE;

}

这种情况只有在程序跟随符号链接时才会发生。

2.2.13.4 Controlling the Flow

也许您想遍历文件树以查找特定目录,并且在找到时希望终止该进程。也许您想跳过特定目录。

FileVisitor 方法返回一个 FileVisitResult 值。您可以中止文件遍历过程或控制是否通过您在 FileVisitor 方法中返回的值访问目录:

CONTINUE – Indicates that the file walking should continue. If the preVisitDirectory method returns CONTINUE, the directory is visited.TERMINATE – Immediately aborts the file walking. No further file walking methods are invoked after this value is returned.SKIP_SUBTREE – When preVisitDirectory returns this value, the specified directory and its subdirectories are skipped. This branch is “pruned out” of the tree.SKIP_SIBLINGS – When preVisitDirectory returns this value, the specified directory is not visited, postVisitDirectory is not invoked, and no further unvisited siblings are visited. If returned from the postVisitDirectorymethod, no further siblings are visited. Essentially, nothing further happens in the specified directory.

在此代码片段中,将跳过任何名为 SCCS 的目录:

import static java.nio.file.FileVisitResult.*;

public FileVisitResult

preVisitDirectory(Path dir,

BasicFileAttributes attrs) {

(if (dir.getFileName().toString().equals("SCCS")) {

return SKIP_SUBTREE;

}

return CONTINUE;

}

在此代码片段中,一旦找到特定文件,文件名就会打印到标准输出,并且文件遍历终止:

import static java.nio.file.FileVisitResult.*;

// The file we are looking for.

Path lookingFor = ...;

public FileVisitResult

visitFile(Path file,

BasicFileAttributes attr) {

if (file.getFileName().equals(lookingFor)) {

System.out.println("Located file: " + file);

return TERMINATE;

}

return CONTINUE;

}

2.2.13.5 Examples

以下示例演示了文件遍历机制:

Find – Recurses a file tree looking for files and directories that match a particular glob pattern.Chmod – Recursively changes permissions on a file tree (for POSIX systems only).Copy – Recursively copies a file tree.WatchDir – Demonstrates the mechanism that watches a directory for files that have been created, deleted or modified. Calling this program with the -r option watches an entire tree for changes.

2.2.14 Finding Files

java.nio.file 包为这个有用的特性提供了编程支持。每个文件系统实现都提供了一个 PathMatcher。您可以使用 FileSystem 类中的 getPathMatcher(String) 方法检索文件系统的 PathMatcher。以下代码片段获取默认文件系统的路径匹配器:

String pattern = ...;

PathMatcher matcher =

FileSystems.getDefault().getPathMatcher("glob:" + pattern);

传递给 getPathMatcher 的字符串参数指定要匹配的语法风格和模式。此示例指定 glob 语法。

Glob 语法易于使用且灵活,但如果您愿意,也可以使用正则表达式或正则表达式语法。一些文件系统实现可能支持其他语法。

如果您想使用其他形式的基于字符串的模式匹配,您可以创建自己的 PathMatcher 类。本页中的示例使用 glob 语法。

创建 PathMatcher 实例后,您就可以将文件与之匹配了。 PathMatcher 接口有一个matches方法,它接受一个 Path 参数并返回一个布尔值:它要么匹配模式,要么不匹配。以下代码片段查找以 .java 或 .class 结尾的文件并将这些文件打印到标准输出:

PathMatcher matcher =

FileSystems.getDefault().getPathMatcher("glob:*.{java,class}");

Path filename = ...;

if (matcher.matches(filename)) {

System.out.println(filename);

}

2.2.14.1 Recursive Pattern Matching

example

import java.io.*;

import java.nio.file.*;

import java.nio.file.attribute.*;

import static java.nio.file.FileVisitResult.*;

import static java.nio.file.FileVisitOption.*;

import java.util.*;

public class Find {

public static class Finder

extends SimpleFileVisitor {

private final PathMatcher matcher;

private int numMatches = 0;

Finder(String pattern) {

matcher = FileSystems.getDefault()

.getPathMatcher("glob:" + pattern);

}

// Compares the glob pattern against

// the file or directory name.

void find(Path file) {

Path name = file.getFileName();

if (name != null && matcher.matches(name)) {

numMatches++;

System.out.println(file);

}

}

// Prints the total number of

// matches to standard out.

void done() {

System.out.println("Matched: "

+ numMatches);

}

// Invoke the pattern matching

// method on each file.

@Override

public FileVisitResult visitFile(Path file,

BasicFileAttributes attrs) {

find(file);

return CONTINUE;

}

// Invoke the pattern matching

// method on each directory.

@Override

public FileVisitResult preVisitDirectory(Path dir,

BasicFileAttributes attrs) {

find(dir);

return CONTINUE;

}

@Override

public FileVisitResult visitFileFailed(Path file,

IOException exc) {

System.err.println(exc);

return CONTINUE;

}

}

static void usage() {

System.err.println("java Find " +

" -name \"\"");

System.exit(-1);

}

public static void main(String[] args)

throws IOException {

if (args.length < 3 || !args[1].equals("-name"))

usage();

Path startingDir = Paths.get(args[0]);

String pattern = args[2];

Finder finder = new Finder(pattern);

Files.walkFileTree(startingDir, finder);

finder.done();

}

}

Find 类似于 UNIX find 实用程序,但在功能上有所缩减。

use case

java Find . -name "*.html"

2.2.15 Watching a Directory for Changes

java.nio.file 包提供了一个文件更改通知 API,称为 Watch Service API。此 API 使您能够向监视服务注册一个(或多个目录)。注册时,您告诉服务您感兴趣的事件类型:文件创建、文件删除或文件修改。当服务检测到感兴趣的事件时,它会被转发到注册的进程。注册的进程有一个线程(或线程池)专门用于监视它注册的任何事件。当一个事件进来时,它会根据需要进行处理。

2.2.15.1 Watch Service Overview

WatchService API 相当低级,允许您对其进行自定义。您可以按原样使用它,也可以选择在此机制之上创建一个高级 API,以便它适合您的特定需求。

以下是实现监视服务所需的基本步骤:

Create a WatchService “watcher” for the file system.For each directory that you want monitored, register it with the watcher. When registering a directory, you specify the type of events for which you want notification. You receive a WatchKey instance for each directory that you register.Implement an infinite loop to wait for incoming events. When an event occurs, the key is signaled and placed into the watcher’s queue.Retrieve the key from the watcher’s queue. You can obtain the file name from the key.Retrieve each pending event for the key (there might be multiple events) and process as needed.Reset the key, and resume waiting for events.Close the service: The watch service exits when either the thread exits or when it is closed (by invoking its closed method).

WatchKeys are thread-safe and can be used with the java.nio.concurrent package.

2.2.15.2 Try It Out

example

import java.nio.file.*;

import static java.nio.file.StandardWatchEventKinds.*;

import static java.nio.file.LinkOption.*;

import java.nio.file.attribute.*;

import java.io.*;

import java.util.*;

/**

* Example to watch a directory (or tree) for changes to files.

*/

public class WatchDir {

private final WatchService watcher;

private final Map keys;

private final boolean recursive;

private boolean trace = false;

@SuppressWarnings("unchecked")

static WatchEvent cast(WatchEvent event) {

return (WatchEvent)event;

}

/**

* Register the given directory with the WatchService

*/

private void register(Path dir) throws IOException {

WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);

if (trace) {

Path prev = keys.get(key);

if (prev == null) {

System.out.format("register: %s\n", dir);

} else {

if (!dir.equals(prev)) {

System.out.format("update: %s -> %s\n", prev, dir);

}

}

}

keys.put(key, dir);

}

/**

* Register the given directory, and all its sub-directories, with the

* WatchService.

*/

private void registerAll(final Path start) throws IOException {

// register directory and sub-directories

Files.walkFileTree(start, new SimpleFileVisitor() {

@Override

public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)

throws IOException

{

register(dir);

return FileVisitResult.CONTINUE;

}

});

}

/**

* Creates a WatchService and registers the given directory

*/

WatchDir(Path dir, boolean recursive) throws IOException {

this.watcher = FileSystems.getDefault().newWatchService();

this.keys = new HashMap();

this.recursive = recursive;

if (recursive) {

System.out.format("Scanning %s ...\n", dir);

registerAll(dir);

System.out.println("Done.");

} else {

register(dir);

}

// enable trace after initial registration

this.trace = true;

}

/**

* Process all events for keys queued to the watcher

*/

void processEvents() {

for (;;) {

// wait for key to be signalled

WatchKey key;

try {

key = watcher.take();

} catch (InterruptedException x) {

return;

}

Path dir = keys.get(key);

if (dir == null) {

System.err.println("WatchKey not recognized!!");

continue;

}

for (WatchEvent event: key.pollEvents()) {

WatchEvent.Kind kind = event.kind();

// TBD - provide example of how OVERFLOW event is handled

if (kind == OVERFLOW) {

continue;

}

// Context for directory entry event is the file name of entry

WatchEvent ev = cast(event);

Path name = ev.context();

Path child = dir.resolve(name);

// print out event

System.out.format("%s: %s\n", event.kind().name(), child);

// if directory is created, and watching recursively, then

// register it and its sub-directories

if (recursive && (kind == ENTRY_CREATE)) {

try {

if (Files.isDirectory(child, NOFOLLOW_LINKS)) {

registerAll(child);

}

} catch (IOException x) {

// ignore to keep sample readbale

}

}

}

// reset key and remove from set if directory no longer accessible

boolean valid = key.reset();

if (!valid) {

keys.remove(key);

// all directories are inaccessible

if (keys.isEmpty()) {

break;

}

}

}

}

static void usage() {

System.err.println("usage: java WatchDir [-r] dir");

System.exit(-1);

}

public static void main(String[] args) throws IOException {

// parse arguments

if (args.length == 0 || args.length > 2)

usage();

boolean recursive = false;

int dirArg = 0;

if (args[0].equals("-r")) {

if (args.length < 2)

usage();

recursive = true;

dirArg++;

}

// register directory and process its events

Path dir = Paths.get(args[dirArg]);

new WatchDir(dir, recursive).processEvents();

}

}

2.2.15.3 Creating a Watch Service and Registering for Events

第一步是使用 FileSystem 类中的 newWatchService 方法创建一个新的 WatchService,如下:

WatchService watcher = FileSystems.getDefault().newWatchService();

接下来,向监视服务注册一个或多个对象。任何实现 Watchable 接口的对象都可以注册。 Path 类实现了 Watchable 接口,因此每个要监控的目录都注册为一个 Path 对象。

与任何 Watchable 一样,Path 类实现了两个注册方法。此页面使用双参数版本 register(WatchService, WatchEvent.Kind...)。 (三参数版本采用 WatchEvent.Modifier,目前未实现。)

向监视服务注册对象时,您指定要监视的事件类型。支持的 StandardWatchEventKinds 事件类型如下:

ENTRY_CREATE – A directory entry is created.ENTRY_DELETE – A directory entry is deleted.ENTRY_MODIFY – A directory entry is modified.OVERFLOW – Indicates that events might have been lost or discarded. You do not have to register for the OVERFLOW event to receive it.

以下代码片段显示了如何为所有三种事件类型注册 Path 实例:

import static java.nio.file.StandardWatchEventKinds.*;

Path dir = ...;

try {

WatchKey key = dir.register(watcher,

ENTRY_CREATE,

ENTRY_DELETE,

ENTRY_MODIFY);

} catch (IOException x) {

System.err.println(x);

}

2.2.15.4 Processing Events

WatchKey 有状态。在任何给定时间,它的状态可能是以下之一:

Ready indicates that the key is ready to accept events. When first created, a key is in the ready state.Signaled indicates that one or more events are queued. Once the key has been signaled, it is no longer in the ready state until the reset method is invoked.Invalid indicates that the key is no longer active. This state happens when one of the following events occurs:

The process explicitly cancels the key by using the cancel method.The directory becomes inaccessible.The watch service is closed.

example

import java.nio.file.*;

import static java.nio.file.StandardWatchEventKinds.*;

import static java.nio.file.LinkOption.*;

import java.nio.file.attribute.*;

import java.io.*;

import java.util.*;

/**

* Example monitors a specified directory for new files.

* If a newly added file is a plain text file, the file can

* be emailed to the appropriate alias. The emailing details

* are left to the reader. What the example actually does is

* print the details to standard out.

*/

public class Email {

private final WatchService watcher;

private final Path dir;

/**

* Creates a WatchService and registers the given directory

*/

Email(Path dir) throws IOException {

this.watcher = FileSystems.getDefault().newWatchService();

dir.register(watcher, ENTRY_CREATE);

this.dir = dir;

}

/**

* Process all events for the key queued to the watcher.

*/

void processEvents() {

for (;;) {

// wait for key to be signaled

WatchKey key;

try {

key = watcher.take();

} catch (InterruptedException x) {

return;

}

for (WatchEvent event: key.pollEvents()) {

WatchEvent.Kind kind = event.kind();

if (kind == OVERFLOW) {

continue;

}

//The filename is the context of the event.

WatchEvent ev = (WatchEvent)event;

Path filename = ev.context();

//Verify that the new file is a text file.

try {

Path child = dir.resolve(filename);

if (!Files.probeContentType(child).equals("text/plain")) {

System.err.format("New file '%s' is not a plain text file.%n", filename);

continue;

}

} catch (IOException x) {

System.err.println(x);

continue;

}

//Email the file to the specified email alias.

System.out.format("Emailing file %s%n", filename);

//Details left to reader....

}

//Reset the key -- this step is critical if you want to receive

//further watch events. If the key is no longer valid, the directory

//is inaccessible so exit the loop.

boolean valid = key.reset();

if (!valid) {

break;

}

}

}

static void usage() {

System.err.println("usage: java Email dir");

System.exit(-1);

}

public static void main(String[] args) throws IOException {

//parse arguments

if (args.length < 1)

usage();

//register directory and process its events

Path dir = Paths.get(args[0]);

new Email(dir).processEvents();

}

}

2.2.15.5 Retrieving the File Name

example

WatchEvent ev = (WatchEvent)event;

Path filename = ev.context();

2.2.15.6 When to Use and Not Use This API

Watch Service API 专为需要通知文件更改事件的应用程序而设计。它非常适合任何可能具有许多打开文件并需要确保文件与文件系统同步的应用程序,例如编辑器或 IDE。它也非常适合监视目录的应用程序服务器,可能等待 .jsp 或 .jar 文件删除,以便部署它们。

此 API 不是为索引硬盘驱动器而设计的。大多数文件系统实现都对文件更改通知提供本机支持。 Watch Service API 在可用的情况下利用此支持。但是,当文件系统不支持这种机制时,Watch Service 会轮询文件系统,等待事件。

2.2.16 Other Useful Methods

2.2.16.1 Determining MIME Type

example

try {

String type = Files.probeContentType(filename);

if (type == null) {

System.err.format("'%s' has an" + " unknown filetype.%n", filename);

} else if (!type.equals("text/plain") {

System.err.format("'%s' is not" + " a plain text file.%n", filename);

continue;

}

} catch (IOException x) {

System.err.println(x);

}

2.2.16.2 Default File System

example

PathMatcher matcher =

FileSystems.getDefault().getPathMatcher("glob:*.*");

2.2.16.3 Path String Separator

example

String separator = File.separator;

String separator = FileSystems.getDefault().getSeparator();

2.2.16.4 File System’s File Stores

一个文件系统有一个或多个文件存储来保存它的文件和目录。文件存储代表底层存储设备。在 UNIX 操作系统中,每个挂载的文件系统都由一个文件存储表示。在 Microsoft Windows 中,每个卷都由一个文件存储表示:C:、D: 等。

example1

for (FileStore store: FileSystems.getDefault().getFileStores()) {

...

}

example2

Path file = ...;

FileStore store= Files.getFileStore(file);

2.2.17 Legacy File I/O Code

2.2.17.1 Interoperability With Legacy Code

java.io.File 类提供了 toPath 方法,它将旧样式的 File 实例转换为 java.nio.file.Path 实例,如下所示:

Path input = file.toPath();

相反,Path.toFile 方法为 Path 对象构造一个 java.io.File 对象。

example1

file.delete();

example2

Path fp = file.toPath();

Files.delete(fp);

2.2.17.2 Mapping java.io.File Functionality to java.nio.file

java.io.File Functionalityjava.nio.file Functionalityjava.io.Filejava.nio.file.Pathjava.io.RandomAccessFileThe SeekableByteChannel functionality.File.canRead, canWrite, canExecuteFiles.isReadable, Files.isWritable, and Files.isExecutable.On UNIX file systems, the Managing Metadata (File and File Store Attributes) package is used to check the nine file permissions.File.isDirectory(), File.isFile(), and File.length()Files.isDirectory(Path, LinkOption...), Files.isRegularFile(Path, LinkOption...), and Files.size(Path)File.lastModified() and File.setLastModified(long)Files.getLastModifiedTime(Path, LinkOption...) and Files.setLastMOdifiedTime(Path, FileTime)The File methods that set various attributes: setExecutable, setReadable, setReadOnly, setWritableThese methods are replaced by the Files method setAttribute(Path, String, Object, LinkOption...).new File(parent, "newfile")parent.resolve("newfile")File.renameToFiles.moveFile.deleteFiles.deleteFile.createNewFileFiles.createFileFile.deleteOnExitReplaced by the DELETE_ON_CLOSE option specified in the createFile method.File.createTempFileFiles.createTempFile(Path, String, FileAttributes), Files.createTempFile(Path, String, String, FileAttributes)File.existsFiles.exists and Files.notExistsFile.compareTo and equalsPath.compareTo and equalsFile.getAbsolutePath and getAbsoluteFilePath.toAbsolutePathFile.getCanonicalPath and getCanonicalFilePath.toRealPath or normalizeFile.toURIPath.toURIFile.isHiddenFiles.isHiddenFile.list and listFilesPath.newDirectoryStreamFile.mkdir and mkdirsFiles.createDirectoryFile.listRootsFileSystem.getRootDirectoriesFile.getTotalSpace, File.getFreeSpace, File.getUsableSpaceFileStore.getTotalSpace, FileStore.getUnallocatedSpace, FileStore.getUsableSpace, FileStore.getTotalSpace

好文推荐

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: