JDK8中对文件的操作

断鸿 2019年07月13日 77次浏览

[toc]

  • Path:Path用于表示文件路径和文件,相当于File类

  • Paths:通过get()方法返回一个Path对象。

  • Files:提供了大量处理文件的方法,例如文件复制、读取、写入,获取文件属性、快捷遍历文件目录等.....

  • Files 和 Paths是Java8新增的工具类,在处理文件方面功能非常强大。

Path和Paths

				//以当前路径作为Path对象
        Path p = Paths.get(".");
        //使用传入的字符串返回一个Path对象
        Path p2 = Paths.get("D","ReviewIO","URL");
        //对应的路径
        System.out.println("p对象的对应路径:" + p.toString());
        System.out.println("p2对象的对应路径:" + p2.toString());
        //路径数量是以路径名的数量作为标准
        System.out.println("p路径数量:" + p.getNameCount());
        System.out.println("p2路径数量:" + p2.getNameCount());        
        //获取绝对路径
        System.out.println("p绝对路径:" + p.toAbsolutePath());
        System.out.println("p2绝对路径:" + p2.toAbsolutePath());
        //获取父路径
        System.out.println("p父路径:"  + p.getParent());
        System.out.println("p2父路径:" + p2.getParent());
        //获取p2对象的文件名或者文件目录名
        System.out.println(p2.getFileName());
        //通过Path对象返回一个分隔符对象
        Spliterator<Path> split = p2.spliterator();

Paths类获取文件或文件目录路径可以使用采用多个字符串形式,也可以使用Path.get(D:\ReviewIO\URL)这种形式。返回的Path对象完全可以代替File类用于文件IO操作。

Files操作文件

创建与删除文件/目录

//创建文件

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

//创建目录
Path directory = Paths.get("d:", "pictures");
Files.createDirectory(directory);
//删除有两种方法:
//两者的区别在于当文件不存在时,前者会抛出 NoSuchFileException 异常,后者会返回 false。
//注意:当删除目录时,无论使用哪种删除方法,都只能删除空目录,否则会抛出 DirectoryNotEmptyException 异常。

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

文件复制

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

//jdk8方式
Files.write(Paths.get("a.txt"), "aaaa".getBytes());
// 在文件读取时,有以下两种方法:
//读文件方式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);

文件遍历

文件遍历有两种方式:非递归遍历与递归遍历。

//非递归遍历的两种方法
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(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();

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

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

  1. 创建 WatchService 实例 watch
  2. 将需要监控的每个目录注册到观察器,返回 WatchKey 实例 key。在注册目录时,指定要通知的事件的类型
  3. 通过一个无限循环等待传入的事件。当事件发生时,key 发出信号,加入到 watch 的 queue
  4. 遍历 key 的事件并根据事件类型进行处理
  5. 重置 key,重新等待事件
  6. 关闭监视服务
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();
  }
}

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