自从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
第三种方法通过显式传入一个过滤器来实现获取子目录中所有的目录。
如果本文有错误,欢迎大家指出!