NIO 2 Paths 与 Files 使用

Paths 类与 Files 类属于 NIO 2 的一部分,在 JDK 7 中引入,并在 JDK 8 中增加了一些适应新增特性(Stream等)的方法。这两个类一般配合起来使用,其中封装了许多操作文件的方法,相比之前的 File 类,更为简洁方便,也包含更细粒度的控制功能。

关于 File 类存在的问题,可以参考这篇文章:Legacy File I/O Code


Path

Path 可以理解为定义的一个路径,对应一个文件或目录。该文件或目录如果已经存在,则可以通过此Path 进行文件的修改、删除、复制等操作,否则需要先创建该文件再进行后续操作。

创建 Path 的三种方式:

  • 通过 Paths 的 get() 方法,该方法的参数可以是 String 或 URI

Path path = Paths.get(“d:/test.txt”);
// 如果 String 过长,可以将其分成多个 String 表示
Path lPath = Paths.get(“d:”, “test.txt”);

  • 通过 FileSystem 的 getPath() 方法,FileSystems.getDefault() 方法返回默认的文件系统供 JVM 访问

Path path = FileSystems.getDefault().getPath(“d:”, “test.txt”);

  • 通过 File 转化

File file = new File(“d:”, “test.txt”);
Path path = file.toPath();


文件操作(Files)

  • 创建与删除文件/目录
//创建文件
Path path = Paths.get("d:/test.txt");
Files.createFile(path); 

//创建目录
Path directory = Paths.get("d:", "pictures");
Files.createDirectory(directory);

删除有两种方法:

public static void delete(Path path) throws IOException
public static boolean deleteIfExists(Path path) throws IOException

两者的区别在于当文件不存在时,前者会抛出 NoSuchFileException 异常,后者会返回 false。

注意:当删除目录时,无论使用哪种删除方法,都只能删除空目录,否则会抛出 DirectoryNotEmptyException 异常。

  • 文件复制

public static Path copy(Path source, Path target, CopyOption… options)

示例如下:

Path source = Paths.get("d:/test.txt");
Path target = Paths.get("e:/");
Files.copy(source, target.resolve(source.getFileName()), 
                         StandardCopyOption.REPLACE_EXISTING);

resolve() 方法用来将两个路径合在一起组成新的路径,以上例来说,它的作用是将 d 盘下的 test.txt文件复制到 e 盘下,文件名保持一致。

StandardCopyOption 有如下类型:

  • REPLACE_EXISTING:替换已存在的文件
  • COPY_ATTRIBUTES:将源文件的文件属性信息复制到目标文件中
  • ATOMIC_MOVE:原子性复制,即不存在因失败导致文件复制不完整的情况出现

注意:使用 copy 方法复制目录时只会复制空目录,目录中的文件不会复制。复制目录需要通过文件遍历来实现。

  • 文件移动/重命名

// 文件移动与重命名用到的是同一个命令
public static Path move(Path source, Path target, CopyOption… options)

示例如下:

Path source = Paths.get("d:/test.txt");
Path target = Paths.get("e:/");//注意要有"/",否则会使用当前工作目录

//移动到其它目录
Files.move(source, target.resolve(source.getFileName()), 
                          StandardCopyOption.REPLACE_EXISTING);

//重命名文件
Files.createFile(source);
Files.move(source, source.resolveSibling("test2.txt"),
                          StandardCopyOption.REPLACE_EXISTING);

resolveSibling() 方法的作用与 resolve() 方法类似,但它是以 source 的父目录与方法中的参数进行合并,例如 source.resolveSibling(“test2.txt”) 代表的路径就是 “d:/test2.txt”。

注意:当移动目录时,只能移动空目录,否则会抛出 DirectoryNotEmptyException 异常;给目录重命名时没有此限制。

  • 文件读写
在之前,如果要使用 BufferedWriter 与 BufferedReader 实现这一操作,需要写一连串冗长的代码;现在,Files类提供了实用的方法封装了这些操作。
Path path = Paths.get("d:/test.txt");
Files.createFile(path);
BufferedWriter bw = Files.newBufferedWriter(path, 
                          StandardCharsets.UTF_8, 
                       StandardOpenOption.WRITE);
bw.write("Hello");
bw.newLine();
bw.write("World!");
bw.flush();
bw.close();

在文件读取时,有以下两种方法:

//读文件方式1(jdk1.7)
BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8);
String str;
while ((str = reader.readLine()) != null) {
    System.out.println(str);
}
reader.close();

//读文件方式2(jdk1.8)
List<String> list = Files.readAllLines(path);
System.out.println(list);
  • 文件遍历
文件遍历有两种方式:非递归遍历与递归遍历。
  1. 非递归遍历的两种方法
Path dir = Paths.get("E:/Pictures");
//非递归遍历目录方式1(jdk1.7)
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(dir);
for (Path item : directoryStream)
    System.out.println(item.toAbsolutePath());
directoryStream.close();

//非递归遍历目录方式2(jdk1.8)
Stream<Path> stream = Files.list(dir);
stream.forEach(System.out::println);
stream.close();
  1. 递归遍历(深度优先遍历)的两种方法
//深度优先遍历方式1(jdk1.7)
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
    //访问文件时调用
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attr) throws IOException {
        System.out.println(file.toAbsolutePath());
        return FileVisitResult.CONTINUE;
    }
    //访问目录前调用
    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) throws IOException {
        System.out.println(dir.toAbsolutePath());
        return FileVisitResult.CONTINUE;
    }
});

//深度优先遍历方式2(jdk1.8)
Stream<Path> deepStream = Files.walk(dir);
deepStream.forEach(System.out::println);
stream.close();

可以看到,借助于 JDK 8 中引入的 Stream 特性,语法更为简洁易懂。

  • 获取与修改文件属性

public static Map<String,Object> readAttributes(Path path, String attributes, LinkOption… options)

示例如下:

Path path = Paths.get("d:/test.txt");
System.out.println(Files.readAttributes(path, "*"));

输出结果:

{lastAccessTime=2017-11-15T12:57:07.681984Z, lastModifiedTime=2017-11-15T12:57:07.681984Z, 
size=0, creationTime=2017-11-15T12:57:07.681984Z, isSymbolicLink=false, 
isRegularFile=true, fileKey=null, isOther=false, isDirectory=false}

也可以通过 setAttributes() 方法对文件属性进行修改:

public static Path setAttribute(Path path, String attribute, Object value, LinkOption… options)

示例如下:

Path path = Paths.get("d:/test.txt");

//更改文件属性方式1(Windows)
DosFileAttributeView view = Files.getFileAttributeView(path, DosFileAttributeView.class);
view.setArchive(false);

//更改文件属性方式2(Windows)
Files.setAttribute(path, "dos:archive", true);

方式1的好处是不需要记文件属性名,方式2的好处是简洁易懂,但前提是需要知道准确的文件属性名。

注意:由于各个操作系统文件属性并不相同,Windows 下使用的是 DosFileAttributeView;如果是 Linux 操作系统,使用的是 PosixFileAttributeView。

  • 寻找文件

public static Stream find(Path start, int maxDepth, BiPredicate<Path, BasicFileAttributes> matcher, FileVisitOption… options)

以寻找后缀为 “docx” 的文件为例:

Path dir = Paths.get("E:/Pictures");
Stream<Path> result = Files.find(dir, 2, (p, basicFileAttributes) -> String.valueOf(p).endsWith(".docx"));
result.map(Path::getFileName).forEach(System.out::println);

也可以在 walk() 方法返回的 Stream 上调用过滤器 filter 来实现此功能。但 find() 方法可能更有效,因为避免了重复检索 BasicFileAttributes。

  • 文件监视 WatchService

在 NIO 2 中也提供了文件监视机制,可以对文件的创建、修改和删除事件进行监视。监视服务的具体实现依赖于操作系统,在可用的情况下会利用操作系统本地的文件监视功能。 但是,当操作系统不支持文件监视机制时,监视服务将采用轮询机制。

使用文件监视的步骤如下:

  • 创建 WatchService 实例 watch
  • 将需要监控的每个目录注册到观察器,返回 WatchKey 实例 key。在注册目录时,指定要通知的事件的类型
  • 通过一个无限循环等待传入的事件。当事件发生时,key 发出信号,加入到 watch 的 queue
  • 遍历 key 的事件并根据事件类型进行处理
  • 重置 key,重新等待事件
  • 关闭监视服务
示例如下:
public class WatchServiceDemo {

    public static void main(String[] args) throws IOException, InterruptedException {
        String path = "d:/watch";
        watch(path);
    }

    public static void watch(String directory) throws IOException, InterruptedException {
        Path path = Paths.get(directory);
        WatchService watch = FileSystems.getDefault().newWatchService();
        path.register(watch, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        while (true) {
            WatchKey key = watch.take();
            for (WatchEvent event : key.pollEvents()) {
                if (event.kind() == ENTRY_CREATE) {
                    System.out.println("文件创建:" + event.context());
                }
                if (event.kind() == ENTRY_MODIFY) {
                    System.out.println("文件修改:" + event.context());
                }
                if (event.kind() == ENTRY_DELETE) {
                    System.out.println("文件删除:" + event.context());
                }
            }
            boolean reset = key.reset();
            if (!reset) {
                System.out.println("监视服务终止!");
                break;
            }
        }
        watch.close();
   }
}

注意:在创建、重命名文件以及修改文件内容时会监测到两个重复的事件。此外,上述代码只能监测到该目录下的文件事件,而无法监测到子目录中的文件事件(但能监测到子目录是否发生了变化)。

参考链接:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值