NIO.2手册(2)

在该系列的上一篇文章中我演示了NIO.2的三个方法:文件拷贝、文件和目录的删除和文件移动。在这篇文章中,我将向大家展示路径相关的方法(如获取路径、检索路径信息)、文件和目录测试方法(如文件或目录的存在性测试)以及面向属性的方法。

获取路径

问:怎样获得一个 java.nio.file.Path 对象?

答:你可以调用 java.nio.file.Paths 类的下列任何一静态个方法,通过文件或目录在文件系统中的名称来获得一个Path对象:

  • Path get(String first, String… more):将一个路径字符串或者可以连接成路径字符串的字符序列转换成一个Path对象。如果该字符串包含无效的字符或者该路径字符串对特定文件系统是无效的,则该字符串不能转换为一个Path对象而会抛出 java.nio.file.InvalidPathException 异常。也可以通过调用 java.nio.file.FileSystem 实例的默认 Path getPath(String first, String… more) 方法来获取Path对象。
  • Path get(URI uri):将统一资源标示符(URI)转换为Path对象。当该uri不符合uri的规范时(例如:uri必须包含一个schema),此方法会抛出 java.lang.IllegalArgumentException 的异常。当uri标示符在文件系统中不存在或不能创建,或者是uri的schema没有安装,该方法将会抛出 java.nio.file.FileSystemNotFoundException 异常。如果安装的任何安全管理器不允许未知访问该文件系统,将抛出 java.lang.SecurityException。也可以遍历查找已安装的 java.nio.file.spi.FileSystemProvider 来获取的provider实例,然后调用provider的 Path getPath(URI uri) 方法来获取Path对象。

第一个 get() 方法可以通过任意数量的字符组成一个路径,例如,Path path = Paths.get(“a”, “b”); 在将字符转换为字符串的时候,该方法将使用适合特定文件系统的文件分隔符来分隔元素。例如,在Windows平台上执行 System.out.println(path); 方法,结果输出为 ab。

你也可以在元素之间硬编码一个分隔符,如Path path = Paths.get(“a”, “”, “b”);。但是,这不是一个好的想法。在Windows平台上,最终会获得ab.但是,在一个以冒号为文件分隔符的平台上,你将很有可能会观察到一个 InvalidPathException 异常,指出斜杠是一个非法的字符。

你可能想使用 File.separator 来替代硬编码一个文件分隔符。但是,这也不是一个好的想法。在NIO.2 中支持多种文件系统,但是 File.separator 指的是默认的文件系统。尝试通过一个包含 File.separator 路径来获取Path对象,如果该文件系统环境下,不能识别该常量分割符将抛出 InvalidPathException 异常。

我们应该使用 FileSystem 的 String getSeparator() 的方法来代替硬编码一个分割符或者指定File.separator。getSeparator() 方法返回一个FileSystem 实例调用关联的文件系统的文件分隔符。例如,FileSystem.getDefault().getSeparator() 返回默认文件系统的分隔符。

在该系统的第一部分我简单的演示了 get() 方法的使用。在列表1中展示了另外一个小应用的源代码,在这个应用中,我强制使用了上述的文件分隔符。

import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ObtainPath
{
   public static void main(String[] args)
   {
      Path path = Paths.get("a", "b");
      System.out.println(path);

      path = Paths.get(FileSystems.getDefault().getSeparator() + "a", "b", "c");
      System.out.println(path);

      path = Paths.get("a", ":", "b");
      System.out.println(path);
   }
}

列表1、ObtainPath.java(版本1)

在上述代码中的第二个例子中,该文件分隔符被作为根目录使用,但是存在一种更好的方式来获取该目录,我将在此文的后面演示。

编译列表1 (javac ObtainPath.java) 并运行该应用(java ObtainPath)。在Windows平台上,我看到的结果如下:

ab
abc
Exception in thread "main" java.nio.file.InvalidPathException: Illegal char <:> at index 2: a:b
    at sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182)
    at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153)
    at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77)
    at sun.nio.fs.WindowsPath.parse(WindowsPath.java:94)
    at sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:255)
    at java.nio.file.Paths.get(Paths.java:84)
    at ObtainPath.main(ObtainPath.java:15)

顺便提一下,如果将null做为路径元素传 get() 方法,如path = Paths.get(“a”, null, “b”),将会出现什么现象?

第二个 get() 方法是从一个 URI 转换为path,列表2的一个小应用程序演示了使用该方法时的两个问题。

import java.net.URI;
import java.net.URISyntaxException;

import java.nio.file.FileSystemNotFoundException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ObtainPath
{
   public static void main(String[] args) throws URISyntaxException
   {
      try
      {
         Path path = Paths.get(new URI(""));
         System.out.println(path);
      }
      catch (IllegalArgumentException iae)
      {
         System.err.println("You cannot pass a URI object initialized to the " +
                            "empty string to get().");
         iae.printStackTrace();
      }

      try
      {
         Path path = Paths.get(new URI("nntp://x"));
         System.out.println(path);
      }
      catch (FileSystemNotFoundException fsnfe)
      {
         System.err.println("No file system associated with the specified scheme.");
         fsnfe.printStackTrace();
      }
   }
}

列表2、ObtainPath.java(版本2)

编译列表2并运行该应用程序。你就能看到如下的异常输出:

You cannot pass a URI object initialized to the empty string to get().
java.lang.IllegalArgumentException: Missing scheme
    at java.nio.file.Paths.get(Paths.java:134)
    at ObtainPath.main(ObtainPath.java:15)
No file system associated with the specified scheme.
java.nio.file.FileSystemNotFoundException: Provider "nntp" not installed
    at java.nio.file.Paths.get(Paths.java:147)
    at ObtainPath.main(ObtainPath.java:27)

第一个例子演示了因缺失scheme的URI而出现的 IllegalArgumentException 异常。第二个例子演示了因为没有nntp scheme的提供者而出现的 FileSystemNotFoundException 的异常。

路径信息检索

问:我能从Path对象中检索到什么信息呢?

答:Path接口声明了几个方法。通过Path对象,可以检索到单个的名称元素和其他种类的信息,下面列出了这些方法的描述:

  • Path getFileName():返回由该文件或目录对应的名称(顶级的父元素是文件继承层级的根目录),该path对象是由该文件或目录的路径生成的。如果该path对象没有元素则返回空。
  • Path getName(int index):根据索引返回元素名称,索引的范围是从0(离跟目录最近的元素)到 getNameCount() – 1。如果index为负值或者index大于等于路径中元素的个数或者该路径没有元素,将抛出 IllegalArgumentException 异常。
  • int getNameCount():返回路径中元素的个数,如果path对象仅代表根目录,则返回0。
  • Path getParent():返回父路径,如果没有父路径,则返回空。父路径是由路径的根目录和除去自身外的其他所有元素组成。
  • Path getRoot():返回代表根目录的path对象或者如果不存在根元素的话则返回null。
  • boolean isAbsolute():如果是绝对路径(完整,不需要跟其他路径信息组合就能定位文件)返回true,否则返回false。
  • Path subpath(int beginIndex, int endIndex):返回一个由原路径的子序列名称组成的相对路径。名称元素的范围 beginIndex 需要小于endIndex。当 beginIndex 的值为负数或者大于等于元素的数量时,或者是 endIndex 小于 beginIndex 或 endIndex 大于等于元素的个数时,将抛出 IllegalArgumentException 的异常。
  • String toString():返回代表路径的字符串。分隔符使用是当前 FileSystem 对象调用 getSeparator() 方法返回的字符。

列表3展示了一个小应用程序的源码,该应用程序演示了这些路径信息检索的方法。该应用通过合适的方式增加了根目录。

import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;

public class RetrievePathInfo
{
   public static void main(String[] args)
   {
      dumpPathInfo(Paths.get("a", "b", "c"));

      FileSystem fs = FileSystems.getDefault();
      Iterable<Path> roots = fs.getRootDirectories();
      for(Path root: roots)
      {
         dumpPathInfo(Paths.get(root.toString(), "a", "b", "c"));
         break;
      }
   }

   static void dumpPathInfo(Path path)
   {
      System.out.printf("Path: %s%n", path);
      System.out.printf("Filename: %s%n", path.getFileName());
      System.out.println("Components");
      for (int i = 0; i < path.getNameCount(); i++)
         System.out.printf("   %s%n", path.getName(i));
      System.out.printf("Parent: %s%n", path.getParent());
      System.out.printf("Root: %s%n", path.getRoot());
      System.out.printf("Absolute: %b%n", path.isAbsolute());
      System.out.printf("Subpath [0, 2): %s%n%n", path.subpath(0, 2));   
   }
}

列表3、RetrievePathInfo.java

列表3创建了两个路径,一个是相对路径、一个绝对路径。然后,调用之前列出来的方法显示不同种类的信息。在第二个路径中,调用 FileSystem 的 Iterable<Path> getRootDirectories() 方法来检索文件系统默认的根目录。第一个根目录被预先添加到路径中用来显示路径信息。

编译列表3(javac RetrievePathInfo.java)然后运行该应用(java RetrievePathInfo)。在Windows平台下,我看的输出结果如下:

Path: abc
Filename: c
Components
   a
   b
   c
Parent: ab
Root: null
Absolute: false
Subpath [0, 2): ab

Path: C:abc
Filename: c
Components
   a
   b
   c
Parent: C:ab
Root: C:
Absolute: true
Subpath [0, 2): ab

注意:如果预置了根目录,isAbsolute() 将返回true。如果只添加一个文件分隔符,如 Paths.get(FileSystems.getDefault().getSeparator() + "a", "b", "c")。在Windows平台下,isAbsolute() 将返回false。在Windows平台下一个绝对路径必须包含驱动说明符,如C:.

更多关于路径的操作

问:我还能对Path对象做哪些操作?

答:Path接口提供移除冗余的路径,从另个路径中创建相对路径,连接两个路径等等操作。前三种操作可以通过下列方法实现:

Path normalize()
Path relativize(Path other)
Path resolve(Path other) (and the Path resolve(String other) variant)
  • normalize() 是一个非常有用的移除路径中冗余信息的方法。例如,reports/./2015/jan 中包含冗余信息(当前目录)元素。通过规范化,该路径变得更短:reports/2015/jan。
  • relativize() 方法从两个路径中创建一个相对路径。如当前目录jan在 reports/2015/jan 中,相对路径可以通过 ../../2016/mar 导航到 reports/2016/mar。
  • resolve() 方法与 relativize() 方法相反。该方法是连接路径的一部分(没有根目录元素的路径)到另一个路径上。如连接apr到 reports/2015上,结果是 reports/2015/apr。

列表4演示这些方法(为了方便,我在两个地方硬编码,这应该通过获取根目录来代替的)

import java.nio.file.Path;
import java.nio.file.Paths;

public class MorePathOp
{
   public static void main(String[] args)
   {
      Path path1 = Paths.get("reports", ".", "2015", "jan");
      System.out.println(path1);
      System.out.println(path1.normalize());
      path1 = Paths.get("reports", "2015", "..", "jan");
      System.out.println(path1.normalize());
      System.out.println();

      path1 = Paths.get("reports", "2015", "jan");
      System.out.println(path1);
      System.out.println(path1.relativize(Paths.get("reports", "2016", "mar")));
      try
      {
         System.out.println(path1.relativize(Paths.get("/reports", "2016", 
                                                       "mar")));
      }
      catch (IllegalArgumentException iae)
      {
         iae.printStackTrace();
      }
      System.out.println();

      path1 = Paths.get("reports", "2015");
      System.out.println(path1);
      System.out.println(path1.resolve("apr"));
      System.out.println(path1.resolve("/apr"));
   }
}

列表4、MorePathOp.java

列表4的 main() 方法首先演示了 normalize() 处理当前目录,然后演示了父目录(..)。结果是 reports/jan。

接下来,main() 演示了之前 relativize() 的例子。然后演示了不能通过绝对路径来获取相对路径,如果尝试这么做的,将抛出IllegalArgumentException的异常。

最后,main() 方法演示了先前 resolve() 的例子,然后展示通过使用一个绝对路径来对比产生的结果。

你可以使用Path对象的其他方法,例如,通过 Path toAbsolutePath() 方法来将一个路径转换为绝对路径,通过 boolean equals(Object other) 方法来比较两个路径是否相同。

测试文件的可访问性

问:怎么知道一个文件是可执行的、可读的或可写的呢?

答:File类提供下列的静态方法来获取文件的可执行性、可读性和可写性:

  • boolean isExecutable(Path path):检测路径代表的文件是否存在以及虚拟机是否拥有可执行的权限。如果文件存在并且是可执行的,则返回true。如果文件不存在,或者虚拟机没有执行的权限或不能确认访问的权限,则返回false。
  • boolean isReadable(Path path):检测路径代表的文件是否存在以及虚拟机是否拥有可读的权限。如果文件存在并且是可读的,则返回true。如果文件不存在,或者虚拟机没有可读权限或不能确认访问的权限,则返回false。
  • boolean isWritable(Path path):检测路径代表的文件是否存在以及虚拟机是否拥有可写的权限。如果文件存在并且是可修改,则返回true。如果文件不存在,或者虚拟机没有写的权限或不能确认访问的权限,则返回false。

通常在执行、读或写一个文件时,都需要调用其中的一个方法来判断。例如,在尝试写一个文件时,你可能需要先检测一个文件是否是可写的,如:

if (!Files.isWritable(Paths.get("file")))
{
   System.out.println("file is not writable");
   return;
}
byte[] data = { /* some comma-separated list of byte data items */ };
Files.write(Paths.get("file"), data);

但是,这段代码有一个问题。检测完这个文件是可写的以后,在写之前,其他程序访问了该文件,并把它设置为可读的。这样,写文件的代码将会执行失败。

检测时间和使用时间之间的竞态条件即为广为人知的 Time of check to time of use (TOCTTOU — 发音为TOCK-too) 问题,这也是为什么 javaodc中说,这些方法的检测结果会瞬间过时。

TOCTTOU对外界干扰的概率很低的很多应用来说不是什么大问题。但是,对安全敏感的应用来说,则是一个重大问题,黑客可能利用这点盗取用户的认证信息。对于这样的应用,

你可能需要避免使用 isWritable() 及它的同类方法,而使用 try/catch 包裹文件的 I/O 代码来代替。

测试文件或目录是否存在

问:我怎么知道一个文件或目录是否存在?

答:Files类提供下列静态方法来判断一个文件或目录是否存在:

  • boolean exists(Path path, LinkOption… options):测试路径所代表的的文件或目录是否存在。默认情况,符号链接会跟踪的。但是,如果传递LinkOption.NOFOLLOW_LINKS参数,符号链接就会跟踪了。如果目录或文件存在,则返回true,如果目录或文件不存在或者不知道其存在性,则返回false。
  • boolean notExists(Path path, LinkOption… options):测试路径所代表的的文件或目录是否不存在。默认情况,符号链接是会跟从的。但是,如果传递LinkOption.NOFOLLOW_LINKS参数,符号链接就不会跟从了。如果目录或文件不存在,则返回true,如果目录或文件存在或者不知道其存在性,则返回false。

在进行某种操作之前,你可能需要调用 exists() 方法来判断,以防在文件不存在时抛出异常。例如,你可能在删除文件之前测试文件的存在性:

Path path = Paths.get("file");
if (Files.exists(path))
   Files.delete(path);

注意:!exists(path) 不等于 notExists(path)(因为 !exists() 不一定是原子的,而 notExists() 是原子的)。同时,如果 exists() 和 notExists() 都返回false,则说明文件的存在性不清楚。最后,类似于访问性判断方法,这些方法的结果也是会瞬间过时的,因此,在安全性敏感的应用中应该避免至少应改谨慎使用。

更多的测试操作

问:我怎么知道还能在Path对象上进行哪种操作?

答:Files类提供以下静态方法俩判断一个路径是不是一个目录,是不是隐藏的,是不是一个正规文件,两个路径是不是指的同一个文件,以及路径是不是一个符号链接:

boolean isDirectory(Path path, LinkOption... options):如果不想让该方法跟踪符号链接,则指定LinkOption.NOFOLLOW_LINKS参数。
boolean isHidden(Path path)
boolean isRegularFile(Path path, LinkOption... options) ---如果不想让该方法跟踪符号链接,则指定LinkOption.NOFOLLOW_LINKS参数。
boolean isSameFile(Path path, Path path2)
boolean isSymbolicLink(Path path)

Consider isHidden(Path) 如果路径指向的文件是隐藏的,则返回true,列表5中应用痛过使用这个方法过滤目录中的隐藏文件,其输出结果如下:

import java.io.IOException;

import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ListHiddenFiles
{
   public static void main(String[] args) throws IOException
   {
      if (args.length != 1)
      {
         System.err.println("usage: java ListHiddenFiles directory");
         return;
      }

      Path dir = Paths.get(args[0]);
      DirectoryStream<Path> stream = Files.newDirectoryStream(dir); 
      for (Path file: stream) 
         if (Files.isHidden(file))
            System.out.println(file);
   }
}

列表5、ListHiddenFiles.java

main() 首先验证命令行参数,该唯一参数即为查询影藏文件的目录。该字符串被转换为了一个 Path对象。

这个Path对象然后被传递到Files类的 DirectoryStream<Path> newDirectoryStream(Path dir) 方法中,该方法返回一个 java.nio.file.DirectoryStream 对象,然后遍历该path对象指定的目录下的所有实体。

DirectoryStream 实现了 Iterable 接口,所以你可以使用增强的 for循环来遍历目录下实体,每次遍历出来的实体,都调用 isHidden() 方法,只有当该方法返回true的时候,才输出。

编译列表5(javac ListHiddenFiles.java)然后运行该应用,java ListHiddenFiles C:. 。在我的Windows平台下,我看的输出结果如下:

C:hiberfil.sys
C:pagefile.sys

获取和设置单个属性

问:怎么获取或者设置Path对象的属性?

答:Files提供下列几个静态方法来获取或设置Path对象的属性:

FileTime getLastModifiedTime(Path path, LinkOption... options)
UserPrincipal getOwner(Path path, LinkOption... options)
Set<PosixFilePermission> getPosixFilePermissions(Path path, LinkOption... options)
boolean isDirectory(Path path, LinkOption... options)
boolean isHidden(Path path)
boolean isRegularFile(Path path, LinkOption... options)
boolean isSymbolicLink(Path path)
Path setAttribute(Path path, String attribute, Object value, LinkOption... options)
Path setLastModifiedTime(Path path, FileTime time)
Path setOwner(Path path, UserPrincipal owner)
Path setPosixFilePermissions(Path path, Set<PosixFilePermission> perms)
long size(Path path)

在此之前,我演示的 isDirectory()、 isHidden()、isRegularFile() 和 isSymbolicLink() 都对属于Path对象。这些方法同时也返回一个Path对象的directory、hidden、regularFile 或 symbolicLink 属性值。

列表6这个应用程序展示了通过 getLastModifiedTime(Path)、getOwner(Path) 和 size(Path) 方法获取一个Path对象的最后修改事件、拥有者以及大小及这些值的输出。

import java.io.IOException;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathAttrib
{
   public static void main(String[] args) throws IOException
   {
      if (args.length != 1)
      {
         System.err.println("usage: java PathAttrib path-object");
         return;
      }

      Path path = Paths.get(args[0]);
      System.out.printf("Last modified time: %s%n", Files.getLastModifiedTime(path));
      System.out.printf("Owner: %s%n", Files.getOwner(path));
      System.out.printf("Size: %d%n", Files.size(path));
   }
}

列表6、PathAttrib.java(版本1)

编译列表6(javac PathAttrib.java)),并运行该应用,如 java PathAttrib PathAttrib.java。我看到的输出结果如下:

Last modified time: 2015-03-18T17:56:15.802635Z
Owner: Owner-PCOwner (User)
Size: 604

问:属性视图是什么?我应该怎么处理?

答:Javadoc中各种属性方法(如:getAttribute()、getPosixFilePermissions()、setAttribute() 和 setPosixFilePermissions())涉及到 java.nio.file.attribute 包中的接口类型。这些接口被不同的属性视图识别,这些属性视图组成了文件的属性。一个属性视图映射到一个文件系统的实现(例如POSIX)或一个通用的功能(如文件所有者关系)。

NIO.2 提供 AttributeView 作为属性视图继承层次中的跟接口。该接口申明一个 getName() 方法,用于返回属性视图的名称。FileAttributeView 继承自 AttributeView 接口,这是一个对文件系统的文件透明的值,是一个只读的或者可更新的视图。FileAttributeView 不提供方法,但是作为很多特定面向文件的的属性视图的超类接口存在。

  • BasicFileAttributeView:一个必须被所有文件系统支持的基本属性视图。
  • DosFileAttributeView:继承自basic属性视图,对合法DOS操作系统只读到,隐藏,文件系统和归档标签。
  • PosixFileAttributeView:扩展自basic属性视图,支持POSIX家族的标准(如UNIX).这些属性包括文件的所有者,所有者分组以及九中相关的访问许可。
  • FileOwnerAttributeView:被任何文件系统支持,任何文件系统的实现都支持文件拥有者的概念。
  • AclFileAttributeView:支持文件的读与更新一个文件的访问控制列表(ACL)。
  • UserDefinedFileAttributeView:支持用户自定义元素。

注意:一个文件系统的实现可能支持basic文件属性视图,或者支持几种后续提到的属性视图(甚至支持其他的不在上述列表中的属性视图)

Files类提供 <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption… options) 泛型方法来返回特定类型的文件属性视图。例如,AclFileAttributeView view = Files.getFileAttributeView(path, AclFileAttributeView.class);。如果属性视图是无效,则该方法返回空。

通常情况下,你需要直接处理 FileAttributeView 或它的子接口,而是通过 getAttribute() 和 setAttribute() 来代替,每个方法的参数要求一个如下语法的字符串:

[view-name:]attribute-name

view-name 标示一个FileAttributeView,FileAttributeView可以识别一个文件属性的集合。如果指定了,它必须是basic、dos、posix、owner 或者acl 之中一个。如果没有指定,默认是basic。basic是一个能识别大多数文件系统通用属性的视图。(这里没有用户自定义的文件属性视图)。attribute-name 是属性的名称,各种属性视图能识别指定名称的属性。

如果指定的属性视图不被支持,则每个方法都会抛出 java.lang.UnsupportedOperationException。为了找出什么视图是支持的,需要调用FileSystem的Set<String> supportedFileAttributeViews() 方法。

列表7演示了输出指定路径下,默认的文件系统支持的属性视图,并输出了所有的(总是支持)basic和(如果支持)acl及dos视图的属性值。

import java.io.IOException;

import java.nio.file.Files;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathAttrib
{
   public static void main(String[] args) throws IOException
   {
      boolean isACL = false;
      boolean isDOS = false;

      FileSystem defFS = FileSystems.getDefault();
      for (String fileAttrView : defFS.supportedFileAttributeViews())
      {
         System.out.printf("Default file system supports: %s%n", fileAttrView);
         if (fileAttrView.equals("acl"))
            isACL = true;
         if (fileAttrView.equals("dos"))
            isDOS = true;
      }
      System.out.println();

      if (args.length != 1)
      {
         System.err.println("usage: java PathAttrib path-object");
         return;
      }

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

      // Output basic attributes, which are always supported.

      System.out.println("Basic attributes:");
      String[] attrNames =
      {
         "lastModifiedTime",
         "lastAccessTime",
         "creationTime",
         "size",
         "isRegularFile",
         "isDirectory",
         "isSymbolicLink",
         "isOther", // something other that a regular file, directory, or 
                    // symbolic link
         "fileKey"  // an object that uniquely identifies the given file, or 
                    // null when a file key is not available.
      };
      for (String attrName: attrNames)
         System.out.printf("%s: %s%n", attrName,
                           Files.getAttribute(path, "basic:" + attrName));
      System.out.println();

      // Output ACL owner attribute when this view is supported.

      if (isACL)
      {
         System.out.println("ACL attributes:");
         System.out.printf("Owner: %s%n%n", 
                           Files.getAttribute(path, "acl:owner"));
      }

      // Output DOS attributes when this view is supported.

      if (isDOS)
      {
         System.out.println("DOS attributes:");
         attrNames = new String[] { "readonly", "hidden", "system", "archive" };
         for (String attrName: attrNames)
           System.out.printf("%s: %s%n", attrName,
                             Files.getAttribute(path, "dos:" + attrName));
         System.out.println();
      }
   }
}

列表7、PathAttrib.java(版本2)

编译列表7并运行,如 java PathAttrib PathAttrib.java。我看到的输出结果如下:

Default file system supports: acl
Default file system supports: basic
Default file system supports: owner
Default file system supports: user
Default file system supports: dos

Basic attributes:
lastModifiedTime: 2015-03-18T19:36:47.435624Z
lastAccessTime: 2015-03-18T19:21:16.681388Z
creationTime: 2015-03-18T19:21:16.681388Z
size: 2417
isRegularFile: true
isDirectory: false
isSymbolicLink: false
isOther: false
fileKey: null

ACL attributes:
Owner: Owner-PCOwner (User)

DOS attributes:
readonly: false
hidden: false
system: false
archive: true

获取和设置多个属性

问:听说有更高效的方法来批量读取文件属性,而不是单个的处理。我该怎么样批量读取文件属性?

答:Files申明了两个静态方法来批量读取文件属性。

<A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options)

第一个方法返回 java.nio.file.attribute.BasicFileAttributes 或它的子接口对象,该对象包含了所有的指定类型属性值。通过返回的Call类型的方法可以获得对象这些值。

第二个方法返回一个map,map包含了所有由属性参数识别的的名值对。这些参数的形式是 [view-name:]attribute-list;  例如, “posix:permissions,owner,size”、

列表8演示这些方法。该应用一个Path对象为参数,每个方法都去读取basic文件属性,然后输出相应的值:

import java.io.IOException;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.Map;
import java.util.TreeMap;

public class PathAttrib
{
   public static void main(String[] args) throws IOException
   {
      if (args.length != 1)
      {
         System.err.println("usage: java PathAttrib path-object");
         return;
      }

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

      BasicFileAttributes attrs;
      attrs = Files.readAttributes(path, BasicFileAttributes.class);

      System.out.printf("Basic Attributes for %s...%n%n", path);

      System.out.printf("creationTime: %s%n", attrs.creationTime());
      System.out.printf("fileKey: %s%n", attrs.fileKey());
      System.out.printf("isDirectory: %b%n", attrs.isDirectory());
      System.out.printf("isOther: %b%n", attrs.isOther());
      System.out.printf("isRegularFile: %b%n", attrs.isRegularFile());
      System.out.printf("isSymbolicLink: %b%n", attrs.isSymbolicLink());
      System.out.printf("lastAccessTime: %s%n", attrs.lastAccessTime());
      System.out.printf("lastModifiedTime: %s%n", attrs.lastModifiedTime());
      System.out.printf("size: %d%n", attrs.size());
      System.out.println();

      Map<String, Object> attrMap = new TreeMap<>(Files.readAttributes(path, 
                                                                       "*"));
      for (Map.Entry<String, Object> entry: attrMap.entrySet())
         System.out.printf("%s: %s%n", entry.getKey(), entry.getValue());
   }
}

列表8、PathAttrib.java(版本3)

在列表8中,我传入 “*” 作为第二个参数传递给第二个方法 readAttributes(),而不是指定一个具体basic中的视图名称,这就意味着basic属性视图的所有属性都需要返回。同时,我将 readAttributes() 的返回结果传递到 java.util.TreeMap 构造器中来获取到一个排序好的map,所以,输出实体的顺序是经过排序的。
编译列表8,并运行该应用。如:java PathAttrib PathAttrib.java,我观察到的输出的结果如下:

Basic Attributes for PathAttrib.java...

creationTime: 2015-03-18T20:20:43.635406Z
fileKey: null
isDirectory: false
isOther: false
isRegularFile: true
isSymbolicLink: false
lastAccessTime: 2015-03-18T20:20:43.635406Z
lastModifiedTime: 2015-03-18T20:35:20.446953Z
size: 1618

creationTime: 2015-03-18T20:20:43.635406Z
fileKey: null
isDirectory: false
isOther: false
isRegularFile: true
isSymbolicLink: false
lastAccessTime: 2015-03-18T20:20:43.635406Z
lastModifiedTime: 2015-03-18T20:35:20.446953Z
size: 1618

更多属性操作

问:我还能在一个Path对象上进行哪些属性操作?

答:你可以读取一个Path对象关联的文件的存储属性值(存储池、设备、分区、卷、具体的文件系统、其它文件存储的特定实现的内容)。可通过下列的方法完成该任务:

  • 调用Files的 FileStore getFileStore(Path path) 来获取文件的存储对象。
  • 调用 java.nio.file.FileStore 对象的 getAttribute(String attribute) 方法来获取给定存储属性的值,你必须指定参数格式为 view-name:attribute-name 的字符串。

列表9是一个小应用程序的源码,这个应用程序演示了怎样使用 getAttribute() 获取NTFS文件存储实现的属性并输出属性值:

import java.io.IOException;

import java.nio.file.Files;
import java.nio.file.FileStore;
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathAttrib
{
   public static void main(String[] args) throws IOException
   {
      if (args.length != 1)
      {
         System.err.println("usage: java PathAttrib path-object");
         return;
      }

      Path path = Paths.get(args[0]);
      FileStore fs = Files.getFileStore(path);
      if (fs.type().equals("NTFS"))
      {
         String[] attrNames = 
         {
            "totalSpace",
            "usableSpace",
            "unallocatedSpace",
            "volume:vsn",
            "volume:isRemovable",
            "volume:isCdrom"
         };
         for (String attrName: attrNames)
            System.out.println(fs.getAttribute(attrName));
      }
   }
}

列表9、PathAttrib.java(版本4)

当FileStore的 String type() 方法返回的文件存储类型为NTFS时,该文件存储的实现可以识别列表中的属性(不应该通过 getAttribute() 方法获取总空间、使用空间、未分配空间,你应该使用更方便的方法,long getTotalSpace()、long getUsableSpace() 和 long getUnallocatedSpace() 方法)。

编译列表9,并运行。如 java PathAttrib PathAttrib.java。我观察到的输出结果如下:如果文件存储类型不是NTFS,则没有任何输出:

499808989184
229907410944
229907410944
1079402743
false
false

FileStore也声明一个 <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) 方法来获取指定类型的FileStoreAttributeView(一个只读或可更新的文件存储属性视图)对象,但因为官方没有提供子接口,这些方法可能只适合于用户自定义的文件系统和API包。

接下来的内容

在第三部分,我将演示更多高级的关于目录层级的拷贝、文件的查找及目录改变的监控的方法来结束这个系列。你将学习到一些文件的访问、循环、监控的内容。

原文链接: JavaWorld 翻译: ImportNew.com - paddx
译文链接: http://www.importnew.com/16081.html
[ 转载请保留原文出处、译者和译文链接。]



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部