跳至主要内容

Java NIO File API使用总结

Welcome file

简介

Java I/O File API是JDK的核心API之一,在构建系统时,十分常用。我在工作实践中,见到大多数人最常使用的是java.io.File的API,对Java7之后出现的NIO的API不太熟悉。

总的来说,File能实现的功能NIO都能实现,并且NIO API更好用、更方便,同时内置了不少工具类,也解决了File API的一些缺陷。

根据实际工作中的使用和一些文档,本文总结了一些常见的Java File NIO的用法。

File的缺陷

File API比较简单好用,能完成大部分功能,但使用上有不少的问题,比如:

  • 大多数方法出错不抛出异常
    比如:删除new File("1111.txt").delete(),如果删除失败,delete方法只会返回一个false,开发者没办法知道具体原因,比如是因为没权限,还是因为文件已被删除?
  • 不支持软链接,这会导致有某些情况下没办法遍历目录
  • 文件的元数据支持较少,比如缺少文件的权限、安全属性等

等等。

File和Path

java.nio.file.Path是Java7中用于替代File的新API,File和Path的概念相似,都是表示一个文件或文件夹的抽象。实际上,也可以把Path当成新API中的File。

FIle与Path的主要区别:

  • File是实体类,Path是接口,使得Path更符合面向对象的设计原则。
  • File独立于文件系统,Path与文件系统相关,以及Path能给出更多关于文件的信息。

新写的代码最好就开始使用Path,有需要与File交互的地方,可以使用FIle与Path相互转换方法:

Path path = new File(DEMO_PATH).toPath();
File file = path.toFile();

Path的基本操作

创建Path

URI uri = new URI("file:///" + DEMO_PATH);
// method 1
Path path1 = Paths.get(DEMO_PATH);

// method 2
Path path2 = Paths.get(uri);

// method 3
Path path3 = Path.of(DEMO_PATH);

// method 4
Path path4 = Path.of(uri);

3和4是在Java11中引入的。

由于接口的默认方法是在Java8中引入的,所以NIO刚开始是使用工具类Paths来创建Path对象

从Path获取各种信息

Path path = Path.of(DEMO_PATH);

boolean exists = Files.exists(path);
System.out.println("exists = "+exists);

boolean exists2 = Files.exists(path, LinkOption.NOFOLLOW_LINKS);
System.out.println("exists2 = " + exists2);

boolean hidden = Files.isHidden(path);
System.out.println("hidden = " + hidden);

boolean directory = Files.isDirectory(path);
System.out.println("directory = " + directory);

boolean regularFile = Files.isRegularFile(path);
System.out.println("regularFile = " + regularFile);

boolean readable = Files.isReadable(path);
System.out.println("readable = " + readable);

BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("creationTime = " + attrs.creationTime());
System.out.println("size = " + attrs.size());

if (System.getProperty("os.name").toLowerCase().contains("win")) {
    // windows
    DosFileAttributes dosAttrs = Files.readAttributes(path, DosFileAttributes.class);
    System.out.println("isSystem = " + dosAttrs.isSystem());
    System.out.println("isReadOnly = " + dosAttrs.isReadOnly());
}else{
    // linux
    PosixFileAttributes posixAttrs = Files.readAttributes(path, PosixFileAttributes.class);
    System.out.println("permissions = " + posixAttrs.permissions());
}

写入文件

Path path = Path.of(DEMO_PATH);

OpenOption openOption = StandardOpenOption.APPEND;

// method 1
try(BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8, openOption);){
    writer.write("HelloWorld");
    writer.newLine();
    writer.write("HelloWorld2");
}

// method 2
Files.writeString(path, "HelloWorld3", StandardCharsets.UTF_8,openOption);

// method 3
Files.write(path, List.of("HelloWorld4"), StandardCharsets.UTF_8,openOption);

读取文件

Path path = Path.of(DEMO_PATH);

// method 1
try(BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8);){
    String line = reader.readLine();
    System.out.println(line);
}

// method 2
try( Stream<String> lines = Files.lines(path) ){
	lines.forEach(System.out::println);
}

// method 3
String string = Files.readString(path);
System.out.println(string);

创建、移动、复制文件

    public void createFile() throws IOException {
        Path path = Files.createFile(Path.of(DEMO_PATH));
        System.out.println(Files.exists(path));
    }

    public void moveFile() throws IOException {
        Path path = Files.move(Path.of(DEMO_PATH), Path.of(DEMO_MOVE_PATH), StandardCopyOption.REPLACE_EXISTING);
        boolean sameFile = Files.isSameFile(path, Path.of(DEMO_MOVE_PATH));
        System.out.println(sameFile);
    }

    public void copyFile() throws IOException {
        Path path = Files.copy(Path.of(DEMO_PATH), Path.of(DEMO_COPY_TAR_PATH), StandardCopyOption.REPLACE_EXISTING);
        boolean sameFile = Files.isSameFile(path, Path.of(DEMO_COPY_TAR_PATH));
        System.out.println(sameFile);
    }

在指定目录查找文件

Path dir = Path.of("G:/");
Path file = Path.of("demoFile2.txt");

Files.find(dir, 1, (p, attr) ->
Files.isRegularFile(p) && p.getFileName().endsWith(file)
    , FileVisitOption.FOLLOW_LINKS).findAny()
    .ifPresent(System.out::println);

遍历目录

        Path walkPath = Path.of(DEMO_WALK_PATH);

        // method 1
        AtomicLong fileCount = new AtomicLong();
        FileVisitor<Path> fileVisitor = new FileVisitor<>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                fileCount.getAndIncrement();
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                return FileVisitResult.CONTINUE;
            }
        };
        Files.walkFileTree(walkPath, fileVisitor);
        System.out.println(fileCount.get());

        // method 2
        long count2 = Files.walk(walkPath, FileVisitOption.FOLLOW_LINKS)
                .filter(Files::isRegularFile).count();
        System.out.println(count2);

参考资料

[1]Oracle对于NIO File API的介绍:https://www.oracle.com/technical-resources/articles/javase/nio.html

[2]file与path方法对照表:https://docs.oracle.com/javase/tutorial/essential/io/legacy.html

评论

此博客中的热门博文

国密SM2签名封装成PKCS7格式

在国内做金融行业,难免会有被强制使用国密算法的情况,而且一般还会指定必须使用硬件加密机之类的设备,所以我也稍微的研究了一下国密算法,使用软算法签名并封装 PKCS7 格式(文档中的一个交互)。 以下是基于 Bouncy Castle 的示例,密钥对的生成可以参考 Bouncy Castle 中 test 包下 SM2 相关代码 public static String sign ( ) throws Exception { //加载公钥 byte [ ] plainText = "hello, world" . getBytes ( ) ; FileInputStream input = new FileInputStream ( "F:\\certificate\\public.cer" ) ; CertificateFactory certificateFactory = new CertificateFactory ( ) ; X509Certificate certificate = ( X509Certificate ) certificateFactory . engineGenerateCertificate ( input ) ; input . close ( ) ; //加载私钥,private为换成实际的私钥 PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec ( "private" . getBytes ( ) ) ; //SM2算法实际上为ECC算法,并指定了一些参数值,所以这里的参数是EC KeyFactory factory = KeyFactory . getInstance ( "EC" , "BC" ) ; PrivateKey privateKey = factory . generatePrivate ( spec ) ; //以下为签名并封装成PKCS7格式 byte [ ] signedMessag

Spring Boot Actuator 2 示例

Welcome file 简介 Spring Boot Actuator为应用程序提供了各种开箱即用的运维特性,可以与应用方便的交互和监控。 使用环境:Java 11 和 Spring Boot 2.4.3.RELEASE 集成Spring Boot Actuator 在Spring Boot中集成Spring Boot Actuator与集成其他的框架类似,在 pom.xml 里引入相关的starter就可以: < dependency > < groupId > org.springframework.boot </ groupId > < artifactId > spring-boot-starter-actuator </ artifactId > </ dependency > < dependency > < groupId > org.springframework.boot </ groupId > < artifactId > spring-boot-starter-web </ artifactId > </ dependency > 由于大部分的使用场景还是web,所以这里也用Spring MVC做示例。 配置好 pom.xml 后,默认actuator仅暴露一些基本功能,实际使用中,根据需求暴露对应功能。为了简便测试,这里在 application.yml 中配置暴露全部功能: management : endpoints : web : exposure : include : "*" endpoint : health : enabled : true show-details : always probes : enabled : true shutdown : enabled : true metr

NextCloud数据目录迁移

最近服务器的环境坏了,所以迁移了NextCloud的数据目录。不过在迁移过程中有点小问题。 环境: Ubuntu 18.04 Docker 19.03.7 1.NextCloud页面不正常,Docker日志显示XX目录permission denied 参考了 这里 的做法,不过是把  /var/www/html/   整个目录的权限都修改为  chown -R www-data:www-data ,之后就不再报权限问题了。 2.数据库配置修改 因为NextCloud是在初始化时填的数据库连接信息,所以直接迁移数据目录的情况下,会导致应用连不到新的数据库环境。此时可以找到数据目录下的  config/config.php 文件,直接修改数据库连接配置。