Java NIO总结(三):Path和Files工具类

4 篇文章 0 订阅

Path相关接口JDK7加入NIO的,在java.nio.file包中。JDK7的NIO更新主要包含两个方面:

  1. 新增Path接口,Paths工具类,Files工具类。 这些接口和工具类对NIO中的功能进行了高度封装,大大简化了文件系统的IO编程。
  2. 基于异步Channel的IO,新增了多个Aysnchronous开头的channel接口和类。

在NIO基础上改进的IO也被称为NIO.2

1.Path接口

在java.io及java.nio中,是通过File类来访问文件系统(文件和目录都用File类表示),但是File类不能利用特定文件系统的特性,且性能不高,抛出的异常也太抽象,因此在NIO.2中引入了Path接口。

Path接口表示的是一个与平台无关的路径(文件和目录都用Path表示)。

Path既可以是绝对路径也可以是相对路径
绝对路径包含文件系统根目录到指向具体文件或目录的完整路径;
相对路径包含一个文件或目录到其他位置的路径。

Path接口在很多方面和java.io.File类很想,但还是有一些细微的差异,在很多场景下是可以用Path替换File使用的。

1.1.创建Path

在使用Path之前需要创建Path对象,可以使用Paths工具类的静态方法进行创建

Path path = Paths.get("C:\\DATA\\test.txt");

Paths.get()是一个创建Path的工厂方法,需要传入一个绝对路径作为参数。
上边我们传入的是Windows系统的文件路径格式,在Unix系统(Linux,MacOS…)中我们也可以传入下边的路径

Path path = Paths.get("/home/data/test.txt");
根据相对路径创建Path

相对路径从一个基础路径指向其他文件或者目录,这个文件或者目录的路径由基础路径和相对路径共同组成。相对路径可以通过下边方法创建

// 目录相对路径
Path projects = Paths.get("d:\\data", "projects");
// 文件相对路径
Path file = Paths.get("d:\\data", "projects\\file.txt");

相对路径中特殊符号

  • . 表示当前目录
  • .. 表示上级目录
Path currentDir = Paths.get(".");
System.out.print(currentDir.toAbsolutePath());

Path parentDir = Paths.get("..");
System.out.print(parentDir.toAbsolutePath());

1.2.Path.normalize()

normalize()方法可以标准化路径,它会处理路径中的相对路径,去除“.”“..”

Path path = Paths.get("c:/Z_DATA/./test.txt");
System.out.println("path = " + path);

path = path.normalize();
System.out.println("path = " + path);

// 输出
path = c:\Z_DATA\.\test.txt
path = c:\Z_DATA\test.txt

这里需要注意 “path = path.normalize();”normalize()并不会改变原Path实例,它会返回一个新的Path对象,这里直接将返回值赋值给path变量了。

2.Files

Java NIO2中增加了Files工具类封装提供了一些操作文件系统文件的工具方法。下面介绍一些常用的方法的使用;Files类是和Path一起使用的,在使用Files方法前需要先理解Path接口。

2.1. Files.exists()

检查一个Path在文件系统中是否存在,你可能创建了一个文件系统中不存在的Path对象。例如你希望新建一个目录,你需要先创建一个对应的Path对象,然后创建目录。Path指向的文件或目录可能存在也可能不存在,所以你需要Files.exists()进行判断。

boolean exists = Files.exists(path, new LinkOption[]{LinkOption.NOFOLLOW_LINKS});

方法的第二个参数是一个包含LinkOption.NOFOLLOW_LINKS的数组,它的意思是Files.exists()方法不需要跟踪文件系统中的象征性连接判断文件是否存在。

2.2. Files.createDirctory()

创建目录

Path newDir = Paths.get("c:/Z_DATA/newDir");
try {
    if(!Files.exists(newDir)) {
        Files.createDirectory(newDir);
    }
} catch (IOException e) {
    e.printStackTrace();
}

在调用创建方法前最好先检查是否存在,如果已经存在会抛出 FileAlreadyExistsException异常。

2.3. Files.copy()

copy()方法从一个路径拷贝文件到另一个路径

Path sourcePath = Paths.get(classPath,"nio-data.txt");
Path targetPath = Paths.get(classPath,"nio-data-copy.txt");
try {
    Files.copy(sourcePath, targetPath);
} catch (IOException e) {
    e.printStackTrace();
}

copy方法只能复制到不存在的路径,如果复制的目标文件已存在则会抛出异常。强制覆盖已存在文件也是可以的,需要增加相应参数:

Path sourcePath = Paths.get(classPath,"nio-data.txt");
Path targetPath = Paths.get(classPath,"nio-data-copy.txt");
try {
    Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); // 复制并覆盖已有文件
} catch (IOException e) {
    e.printStackTrace();
}

2.4 Files.move()

Java NIO Files类同样提供了移动文件的方法。

Path sourcePath = Paths.get(classPath,"nio-data.txt");
Path targetPath = Paths.get(classPath,"nio-data-copy.txt");
try {
    Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); // 移动并覆盖已有文件
} catch (IOException e) {
    e.printStackTrace();
}

2.5 Files.delete()

删除文件或目录

Files.delete(targetPath);

如果删除的文件或目录不存在会抛出IOException异常。

2.6 Files.walkFileTree() 遍历文件

walkFileTree()包含用于递归遍历目录树的方法,方法参数接收一个Path对象和一个FileVisitor对象,Path对象指向需要遍历的目录,FileVisitor在便利的时候调用。FileVistor接口定义如下:

public interface FileVisitor {

    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException;

    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException;

    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException;

    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {

}

使用的时候需要实现FileVisitor的接口

try {
    Files.walkFileTree(start, new FileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            System.out.println("pre visit dir:" + dir);
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            System.out.println("visit file:" + file);
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {

            System.out.println("visit file failed:" + file);
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {

            System.out.println("post visit dir:" + dir);
            return FileVisitResult.CONTINUE;
        }
    });
} catch (IOException e) {
    e.printStackTrace();
}

输出:

pre visit dir:C:\Z_WORK\develop\1_GitHub\MyJava\nio-tutorial\nio-exam\target\classes\data
visit file:C:\Z_WORK\develop\1_GitHub\MyJava\nio-tutorial\nio-exam\target\classes\data\nio-data-to.txt
visit file:C:\Z_WORK\develop\1_GitHub\MyJava\nio-tutorial\nio-exam\target\classes\data\nio-data.txt
post visit dir:C:\Z_WORK\develop\1_GitHub\MyJava\nio-tutorial\nio-exam\target\classes\data

FileVisitor接口中的方法会在不同的时间被调用:

  • preVisitDirectory():在访问任意目录前调用;
  • postVisitDirectory():在访问任意目录完成后调用;
  • visitFile():在访问到每个文件时调用;
  • visitFileFaild():在访问文件失败是调用

这四个方法都会返回一个FileVistResult枚举类型的实例,FileVisitResult枚举包含四个选项:

  • CONTINUE:继续遍历;
  • TERMINATE:立即终止遍历;
  • SKIP_SIBLINGS:跳过当前目录下的兄弟文件继续遍历;
  • SKIP_SUBTREE:跳过当前目录继续遍历,这个只能在preVisitDirectory()方法中返回,在其他方法中返回和CONTINUE效果一样。
使用walkFileTree实现文件检索

下面展示一个使用walkFileTree和继承SimpleFileVistor查找名为nio-data.txt 文件的例子

try {
    Files.walkFileTree(start, new SimpleFileVisitor<Path>(){
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

            String filePath = file.toAbsolutePath().toString();

            if(filePath.endsWith("nio-data.txt")){
                System.out.println("file found at path:" + filePath);
                return FileVisitResult.TERMINATE;
            }

            return super.visitFile(file, attrs);
        }
    });
} catch (IOException e) {
    e.printStackTrace();
}
使用walkFileTree递归删除目录

walkFileTree还可以用来递归删除目录及其目录中所有文件,Files.delete()只能删除空目录或者文件,当目中包含文件的时候无法直接删除,只能删除目录下所有文件后才能删除目录,walkFileTreee可以在visitFile()方法中删除文件,然后在postVisitDirectory()删除目录实现递归删除目录:

try {
    Files.walkFileTree(start, new SimpleFileVisitor<Path>(){
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            System.out.println("delete file:" + file);
            Files.delete(file);
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            System.out.println("delete dir:" + dir);
            Files.delete(dir);
            return FileVisitResult.CONTINUE;
        }
    });
} catch (IOException e) {
    e.printStackTrace();
}

3.AsynchronousFileChannel

AsynchronousFileChannel也是jdk7加入NIO的,它可以异步的读写文件数据。下面介绍如何使用AsynchronousFileChannel.

3.1.创建AsynchronousFileChannel

AsynchronousFileChannel asyncFileChannel = AsynchronousFileChannel.open(filePath, StandardOpenOption.READ);

AsynchronousFileChannel是一个抽象类,只能通过它的静态方法open()创建实例,需要传入文件路径参数和打开模式。

3.2读数据

AsynchronousFileChannel读取数据有两种方式,每种方式都会调用一种read()方法:

  • 通过Future读取数据

这一种读方法返回一个Future对象

ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> operation = asyncFileChannel.read(buffer, 0);

这种read()方法的重载ByteBuffer作为第一个参数,数据从channel中读取到buffer中,第二个参数是读取的起始位置。
read()方法会立即返回,即使读操作还没有完成。我们可以使用read()方法返回的Future对象的isDone()方法检查操作是否完成。
下边是使用这种读方法的完整例子

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;
Future<Integer> operation = asyncFileChannel.read(buffer, position);
while (!operation.isDone()){}
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data));
buffer.clear();
  • 通过CompletionHandler读取数据

第二种read()方法使用一个ComplationHandler作为参数:

asyncFileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    @Override
    public void completed(Integer result, ByteBuffer attachment) {

        System.out.println(result);
        attachment.flip();
        byte[] data = new byte[attachment.limit()];
        attachment.get(data);
        System.out.println(new String(data));
        attachment.clear();
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {

    }
});

一旦读操作完成,ConpletionHandler的completed()方法就会被调用,completed()方法的第一个参数是一个整数告诉我们有多少个字节被读取了,第二个参数“attachment”是read()方法的第三个参数,是已经读入数据的buffer。当操作失败的时候回调用failed()方法。

3.3. 写数据

和读数据一样,写数据也有两种方法

  • 基于Future写数据
// 写模式打开 AsynchronousFileChannel
AsynchronousFileChannel writeFileChannel = AsynchronousFileChannel.open(filePath, StandardOpenOption.WRITE);
position = 12;
buffer.put("test data".getBytes());
buffer.flip();
Future<Integer> writeOperation = writeFileChannel.write(buffer, position);
buffer.clear();
while (!writeOperation.isDone()){}
System.out.println("write done");
  • 基于CompletionHandler写数据
writeFileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("bytes written:" + result);
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        System.out.println("bytes written failed");
    }
});

写操作完成,ConpletionHandler的completed()方法就会被调用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值