自从 java 7 以来,引入了 FIles 类和 Path 接口。他们两封装了用户对文件的所有可能的操作,相比于 java 1 的 File 类来说,使用起来方便很多。但是其实一些本质的操作还是很类似的。主要需要知道的是,Path 表示路径可以使文件的路径也可以是目录的路径,Files 中所有成员都是静态方法,通过路径实现了对文件的基本操作。下面我们首先看看 Path 接口。
一、Path 接口
和 Path 接口相关的还有一个类 Paths,这个类非常简单,只有两个方法加一个私有构造方法。
- public final class Paths {
- private Paths() {}
- public static Path get(String first, String...more) {
- return FileSystems.getDefault().getPath(first, more);
- }
- public static Path get(URI uri) {
- /*这个方法我们暂时不研究*/
- }
- }
大家可以看到,通过 Paths 的 get 静态方法,我们可以获得一个 Path 对象,而实际上我们通常都是通过 Paths 的这个 get 方法来获取 Path 对象。(至于怎么调用这个方法返回的对象,主要是通过调用文件系统的抽象方法来实现的),获得了 Path 对象,我们就可以调用其内部封装的所有的方法,由于方法比较多,我们一点一点看。
- boolean isAbsolute();
- Path getFileName();
- Path getParent();
- int getNameCount();
- Path getName(int index);
- Path subpath(int beginIndex, int endIndex);
- Path resolve(Path other);
- Path resolveSibling(Path other);
- Path relativize(Path other);
- Path toAbsolutePath();
- File toFile();
主要的方法就这么多,其中有些方法的功能乃至具体的实现代码和上篇介绍的 File 类类似,这里就不在说明了。getFileName 这个方法类似于 File 类的 getName 方法,返回路径的文件名称(目录名或者文件名),两者实现原理基本一样。
- Path p = Paths.get("a", "b", "c", "d", "e");
- System.out.println(p.getNameCount());
- /*执行代码可以看到输出结果:5*/
- Path p = Paths.get("a/t", "b", "c", "d", "e");
- System.out.println(p.getNameCount());
- /*执行代码输出结果:6*/
可以看到,getNameCount 方法并不是直接数构建 Path 对象时传入了几个字符串。当我们调用 Path.get 方法传入可变字符串作为路径时,它将每个独立的字符串当成一个目录名,使用默认路径分隔符连接这些路径名形成 Path 路径,而调用 getNameCount 方法是根据默认路径分隔符的个数来统计返回的。
getName 方法要求传入一个 int 型索引,在构建路径时,从根路径开始每一层都被编号了,根目录为 0,子目录依次加一所以 getName 方法可以获取任意层次目录的名称。
- Path p = Paths.get("a", "b", "c", "d", "e");
- System.out.println(p.getName(1));
- /*输出:b*/
subpath 方法和我们 String 中的 substring 类似,给定开始和结束位置的索引值,获取他们之间的路径字符串。
- Path p = Paths.get("a", "b", "c", "d", "e");
- System.out.println(p.subpath(1, 3));
- /*输出结果:b/c*/
需要注意一个细节,截取范围 [startIndex,endIndex),也就是 endIndex 位置的值是不会被截取的。
resolve 方法是一个很有意思的方法,按照我的理解,这个方法实现的是对路径的组合的操作。p.resolve(q),如果 q 是绝对路径,返回结果为 q,如果 q 是相对路径返回结果是 p+q,实现了拼接组合。
- Path p = Paths.get("a", "b", "c", "d", "e");
- Path q = Paths.get("c:/users");
- System.out.println(p.resolve(q));
- /*输出:c:/users*/
- Path p = Paths.get("a", "b", "c", "d", "e");
- Path q = Paths.get("users");
- System.out.println(p.resolve(q));
- /*输出结果:a\b\c\d\e\users*/
resolveSibling 方法是通过解析当前路径的父目录,产生兄弟路径。
- Path p = Paths.get("a", "b", "c", "d", "e");
- Path q = Paths.get("users");
- System.out.println(p.resolveSibling(q));
- /*输出结果:a\b\c\d\users*/
替换了 e 为 users,因为 e 作为当前目录,而此方法就是在当前目录下生成一个和他同级的兄弟目录。通常可以用来修改当前目录的目录名。(在生成磁盘文件之前)
接下来说说这个 relativize 方法,它是一个用来生成一个相对路径的方法。需要额外传入一个 Path 对象。
- Path p = Paths.get("a", "b", "c", "d", "e");
- Path q = Paths.get("users");
- System.out.println(p.relativize(q));
- /*输出结果:..\..\..\..\..\users*/
- Path p = Paths.get("a", "b", "c", "d", "e");
- Path q = Paths.get("a", "b", "c", "d", "e", "f");
- System.out.println(p.relativize(q));
- /*输出结果:f*/
通过比较可以发现,所谓生成相对路径,实际上就是,q 相对于 p。从第二中情况我们可以看出来,整个 p 可以作为 q 的父目录,于是相对路径就是当前目录 f。对于第一种情况,因为没有找到公共目录,所以整个 p 作为 q 的父目录,当然相对于 q 来说,相对路径就是这样了。
最后还想啰嗦一句,在 Path 中有一个方法 toFile,这个方法对应于 FIle 中的 toPath,为什么要实现这么两个方法,其实还是为了兼容旧的 File 类,方便一些旧的系统成功的跨度到新的 java 标准中来。
二、Files 类
上面说过,整个 FIles 类中,都是静态方法,没有一个实例域。(足以见得,这个类就是为了实现对文件的各种操作)首先看看对文件的读写操作。
- public static InputStream newInputStream(Path path, OpenOption... options)
- public static OutputStream newOutputStream(Path path, OpenOption... options)
- public static BufferedReader newBufferedReader(Path path, Charset cs)
- public static BufferedReader newBufferedReader(Path path)
- public static BufferedWriter newBufferedWriter
- public static byte[] readAllBytes(Path path)
- public static List<String> readAllLines(Path path)
- public static Path write(Path path, byte[] bytes, OpenOption... options)
- public static long copy(InputStream in, Path target, CopyOption... options)
- public static long copy(Path source, OutputStream out)
方法很多,我们慢慢看,首先有两个方法可以根据 Path 路径返回 InputStream/OutputStream 字节流对象,这两个方法为我们下面的一些方法提供了一定的便利,也有两个方法通过 Path 对象返回 BufferedReader/BufferedWriter 对象。这些都是对于我们其他的方法是有帮助的。
readAllBytes 内部通过创建 InputStream 对象来读取所有的字节到给定的字节数组中并返回。readAllLines 内部通过创建 List 数组,使用 BufferedReader 创建字符缓冲流,一行一行的读取。最后返回 List 集合。写的操作基本都是读的逆操作,这里不再赘述。
copy 这个方法有多个重载,分别是:
- private static long copy(InputStream source, OutputStream sink)
- public static long copy(InputStream in, Path target, CopyOption... options)
- public static long copy(Path source, OutputStream out)
- public static Path copy(Path source, Path target, CopyOption... options)
第一个重载是一个私有的方法,是被被人调用的工具方法。主要的功能是:从一个 source 流中读取所有的字节并写入 sink 流中,返回实际读入或写入的字节数。第二个重载是选择将 Path 对象通过方法 newOutputStream,构建了一个 OutputStream 对象,然后调用第一个重载方法实现 copy。完成的功能是:从一个 InputStream 流中读取所有的字节并写入一个指定的文件中。第三个重载方法主要是:从一个 Path 文件中读取所有的字节并写入一个 OutputStream 对象流中。操作流程类似,不在赘述。最后一个重载方法实现的是从一个 Path 对象复制到另一个 Path 对象。
- //根目录下只有hello.txt文件,没有world文件
- Path p = Paths.get("hello.txt");
- Path q = Paths.get("world.txt");
- Files.copy(p, q);
- /*world文件被创建并且hello中的内容被复制到此*/
对于这个操作,需要注意的几点:如果 q 在磁盘为位置的文件已经存在将不能完成复制操作,如果 p 在磁盘位置上没有对应文件此操作依然失败,如果 p 是一个目录文件,结果会复制一个名为 world 的目录文件,如果 q 是一个目录文件则会创建一个无类型的文件(hello 中的内容已经被复制进去)。
说完了有关文件的读写操作,下面说说文件或目录的创建和获取文件的基本信息。
- public static Path createFile(Path path, FileAttribute<?>... attrs)
- public static Path createDirectory(Path dir, FileAttribute<?>... attrs)
- public static Path createDirectories(Path dir, FileAttribute<?>... attrs)
- public static Path createTempFile
因为 Path 路径中存放的可以是文件类型,也可以是目录类型。那么在创建的时候就需要进行区分了。createFile 根据指定路径创建一个指定类型的文件,createDirectory 和 createDirectories 的区别在,如果 Path 路径上存在着没有被创建的目录,后者会将他们全部都创建。对于创建临时文件,由于用的不多,就不说了。
对于文件信息的获取主要有以下些方法:
- public static boolean isSameFile(Path path, Path path2)
- public static boolean isHidden(Path path)
- public static String probeContentType(Path path)
- public static boolean isDirectory(Path path, LinkOption... options)
- public static boolean isRegularFile(Path path, LinkOption... options)
- public static long size(Path path)
- public static boolean exists(Path path, LinkOption... options)
- public static boolean isReadable(Path path)
- public static boolean isWritable(Path path)
- /*这些方法名字就是注释,相信大家一眼就能识别他们各自的功能*/
最后谈谈迭代和过滤器,在上篇文章的最后,我们说了 FIle 的过滤和迭代,因为在 File 类中,通常都是一次性返回一个 File 数组或者 String 数组,这往往是低效的。在 Files 类中,设计了一个方法 newDirectoryStream,返回了一个目录流,可以显著提高效率。
- public static DirectoryStream<Path> newDirectoryStream(Path dir)
- public static DirectoryStream<Path> newDirectoryStream(Path dir, String glob)
- public static DirectoryStream<Path> newDirectoryStream(Path dir,
- DirectoryStream.Filter<? super Path> filter)
这是三个目录流的重载方法,第一个只需要提供一个 Path 路径即可,第二个方法提供了一个 Path 对象和一个 glob 字符串。glob 模式:
第三个方法还外部指定了一个过滤器。具体怎么使用,下面看代码。
- DirectoryStream<Path> d = Files.newDirectoryStream(Paths.get("f:/360"));
- for (Path p : d){
- System.out.println(p.getFileName());
- }
- //输出结果:
- 360defender
- 360sdSetup.exe
- 360zip
- //这是我f盘360文件下的所有文件
- DirectoryStream<Path> d = Files.newDirectoryStream(Paths.get("f:/360"),"*.exe");
- for (Path p : d){
- System.out.println(p.getFileName());
- }
- //输出结果:
- 360sdSetup.exe
- DirectoryStream<Path> d = Files.newDirectoryStream(Paths.get("f:/360"),new DirectoryStream.Filter<Path>(){
- @Override
- public boolean accept(Path entry) {
- return Files.isDirectory(entry)?true:false;
- }
- });
- for (Path p : d){
- System.out.println(p.getFileName());
- }
- //输出结果:
- 360defender
- 360zip
第三种方法通过显式传入一个过滤器来实现获取子目录中所有的目录。
如果本文有错误,欢迎大家指出!
来源: http://www.cnblogs.com/yangming1996/p/6606761.html