十七、文件(1)

news2025/3/13 15:42:49

本章概要

  • 文件和目录路径
    • 选取路径部分片段
    • 路径分析
    • Paths 的增减修改
  • 目录

在丑陋的 Java I/O 编程方式诞生多年以后,Java终于简化了文件读写的基本操作。

打开并读取文件对于大多数编程语言来说是非常常用的,由于 I/O 糟糕的设计以至于很少有人能够在不依赖其他参考代码的情况下完成打开文件的操作。

好像 Java 设计者终于意识到了 Java 使用者多年来的痛苦,在 Java7 中对此引入了巨大的改进。这些新元素被放在 java.nio.file 包下面,过去人们通常把 nio 中的 n 理解为 new 即新的 io,现在更应该当成是 non-blocking 非阻塞 io(io就是_input/output输入/输出_)。java.nio.file 库终于将 Java 文件操作带到与其他编程语言相同的水平。最重要的是 Java8 新增的 streams 与文件结合使得文件操作编程变得更加优雅。我们将看一下文件操作的两个基本组件:

  1. 文件或者目录的路径;
  2. 文件本身。

文件和目录路径

一个 Path 对象表示一个文件或者目录的路径,是一个跨操作系统(OS)和文件系统的抽象,目的是在构造路径时不必关注底层操作系统,代码可以在不进行修改的情况下运行在不同的操作系统上。java.nio.file.Paths 类包含一个重载方法 static get(),该方法接受一系列 String 字符串或一个_统一资源标识符_(URI)作为参数,并且进行转换返回一个 Path 对象:

import java.nio.file.*;
import java.net.URI;
import java.io.File;
import java.io.IOException;

public class PathInfo {
    static void show(String id, Object p) {
        System.out.println(id + ": " + p);
    }

    static void info(Path p) {
        show("toString", p);
        show("Exists", Files.exists(p));
        show("RegularFile", Files.isRegularFile(p));
        show("Directory", Files.isDirectory(p));
        show("Absolute", p.isAbsolute());
        show("FileName", p.getFileName());
        show("Parent", p.getParent());
        show("Root", p.getRoot());
        System.out.println("******************");
    }

    public static void main(String[] args) {
        System.out.println(System.getProperty("os.name"));
        info(Paths.get("C:", "path", "to", "nowhere", "NoFile.txt"));
        Path p = Paths.get("PathInfo.java");
        info(p);
        Path ap = p.toAbsolutePath();
        info(ap);
        info(ap.getParent());
        try {
            info(p.toRealPath());
        } catch (IOException e) {
            System.out.println(e);
        }
        URI u = p.toUri();
        System.out.println("URI: " + u);
        Path puri = Paths.get(u);
        System.out.println(Files.exists(puri));
        File f = ap.toFile(); // Don't be fooled
    }
}

在这里插入图片描述

我已经在这一章第一个程序的 main() 方法添加了第一行用于展示操作系统的名称,因此你可以看到不同操作系统之间存在哪些差异。理想情况下,差别会相对较小,并且使用 / 或者 ** 路径分隔符进行分隔。你可以看到我运行在Windows 10 上的程序输出。

toString() 方法生成完整形式的路径,你可以看到 getFileName() 方法总是返回当前文件名。

通过使用 Files 工具类(我们接下来将会更多地使用它),可以测试一个文件是否存在,测试是否是一个"普通"文件还是一个目录等等。

"Nofile.txt"这个示例展示我们描述的文件可能并不在指定的位置;这样可以允许你创建一个新的路径。“PathInfo.java"存在于当前目录中,最初它只是没有路径的文件名,但它仍然被检测为"存在”。一旦我们将其转换为绝对路径,我们将会得到一个从"C:"盘(因为我们是在Windows机器下进行测试)开始的完整路径,现在它也拥有一个父路径。

“真实”路径的定义在文档中有点模糊,因为它取决于具体的文件系统。例如,如果文件名不区分大小写,即使路径由于大小写的缘故而不是完全相同,也可能得到肯定的匹配结果。在这样的平台上,toRealPath() 将返回实际情况下的 Path,并且还会删除任何冗余元素。

这里你会看到 URI 看起来只能用于描述文件,实际上 URI 可以用于描述更多的东西;。现在我们成功地将 URI 转为一个 Path 对象。

最后,你会在 Path 中看到一些有点欺骗的东西,这就是调用 toFile() 方法会生成一个 File 对象。听起来似乎可以得到一个类似文件的东西(毕竟被称为 File ),但是这个方法的存在仅仅是为了向后兼容。虽然看上去应该被称为"路径",实际上却应该表示目录或者文件本身。这是个非常草率并且令人困惑的命名,但是由于 java.nio.file 的存在我们可以安全地忽略它的存在。

选取路径部分片段

Path 对象可以非常容易地生成路径的某一部分:

import java.nio.file.*;

public class PartsOfPaths {
    public static void main(String[] args) {
        System.out.println(System.getProperty("os.name"));
        Path p = Paths.get("PartsOfPaths.java").toAbsolutePath();
        for (int i = 0; i < p.getNameCount(); i++) {
            System.out.println(p.getName(i));
        }
        System.out.println("ends with '.java': " +
                p.endsWith(".java"));
        for (Path pp : p) {
            System.out.print(pp + ": ");
            System.out.print(p.startsWith(pp) + " : ");
            System.out.println(p.endsWith(pp));
        }
        System.out.println("Starts with " + p.getRoot() + " " + p.startsWith(p.getRoot()));
    }
}

在这里插入图片描述

可以通过 getName() 来索引 Path 的各个部分,直到达到上限 getNameCount()Path 也实现了 Iterable 接口,因此我们也可以通过增强的 for-each 进行遍历。请注意,即使路径以 .java 结尾,使用 endsWith() 方法也会返回 false。这是因为使用 endsWith() 比较的是整个路径部分,而不会包含文件路径的后缀。

通过使用 startsWith()endsWith() 也可以完成路径的遍历。但是我们可以看到,遍历 Path 对象并不包含根路径,只有使用 startsWith() 检测根路径时才会返回 true

路径分析

Files 工具类包含一系列完整的方法用于获得 Path 相关的信息。

import java.nio.file.*;
import java.io.IOException;

public class PathAnalysis {
    static void say(String id, Object result) {
        System.out.print(id + ": ");
        System.out.println(result);
    }

    public static void main(String[] args) throws IOException {
        System.out.println(System.getProperty("os.name"));
        Path p = Paths.get("D:\\onJava\\test\\src\\main\\java\\com\\example\\test\\PathAnalysis.java").toAbsolutePath();
        say("Exists", Files.exists(p));
        say("Directory", Files.isDirectory(p));
        say("Executable", Files.isExecutable(p));
        say("Readable", Files.isReadable(p));
        say("RegularFile", Files.isRegularFile(p));
        say("Writable", Files.isWritable(p));
        say("notExists", Files.notExists(p));
        say("Hidden", Files.isHidden(p));
        say("size", Files.size(p));
        say("FileStore", Files.getFileStore(p));
        say("LastModified: ", Files.getLastModifiedTime(p));
        say("Owner", Files.getOwner(p));
        say("ContentType", Files.probeContentType(p));
        say("SymbolicLink", Files.isSymbolicLink(p));
        if (Files.isSymbolicLink(p)) {
            say("SymbolicLink", Files.readSymbolicLink(p));
        }
        if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
            say("PosixFilePermissions",Files.getPosixFilePermissions(p));
        }
    }
}

在这里插入图片描述

在调用最后一个测试方法 getPosixFilePermissions() 之前我们需要确认一下当前文件系统是否支持 Posix 接口,否则会抛出运行时异常。

Paths的增减修改

我们必须能通过对 Path 对象增加或者删除一部分来构造一个新的 Path 对象。我们使用 relativize() 移除 Path 的根路径,使用 resolve() 添加 Path 的尾路径(不一定是“可发现”的名称)。

对于下面代码中的示例,我使用 relativize() 方法从所有的输出中移除根路径,部分原因是为了示范,部分原因是为了简化输出结果,这说明你可以使用该方法将绝对路径转为相对路径。
这个版本的代码中包含 id,以便于跟踪输出结果:

import java.nio.file.*;
import java.io.IOException;

public class AddAndSubtractPaths {
    static Path base = Paths.get("..", "..", "..").toAbsolutePath().normalize();

    static void show(int id, Path result) {
        if (result.isAbsolute()) {
            System.out.println("(" + id + ")r " + base.relativize(result));
        } else {
            System.out.println("(" + id + ") " + result);
        }
        try {
            System.out.println("RealPath: " + result.toRealPath());
        } catch (IOException e) {
            System.out.println(e);
        }
    }

    public static void main(String[] args) {
        System.out.println(System.getProperty("os.name"));
        System.out.println(base);
        Path p = Paths.get("AddAndSubtractPaths.java").toAbsolutePath();
        show(1, p);
        Path convoluted = p.getParent().getParent().resolve("strings").resolve("..")
                .resolve(p.getParent().getFileName());
        show(2, convoluted);
        show(3, convoluted.normalize());
        Path p2 = Paths.get("..", "..");
        show(4, p2);
        show(5, p2.normalize());
        show(6, p2.toAbsolutePath().normalize());
        Path p3 = Paths.get(".").toAbsolutePath();
        Path p4 = p3.resolve(p2);
        show(7, p4);
        show(8, p4.normalize());
        Path p5 = Paths.get("").toAbsolutePath();
        show(9, p5);
        show(10, p5.resolveSibling("strings"));
        show(11, Paths.get("nonexistent"));
    }
}

在这里插入图片描述

我还为 toRealPath() 添加了更多的测试,这是为了扩展和规则化,防止路径不存在时抛出运行时异常。

目录

Files 工具类包含大部分我们需要的目录操作和文件操作方法。出于某种原因,它们没有包含删除目录树相关的方法,因此我们将实现并将其添加到 onjava 库中。

import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;

public class RmDir {
    public static void rmdir(Path dir) throws IOException {
        Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Files.delete(file);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                Files.delete(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }
}

删除目录树的方法实现依赖于 Files.walkFileTree(),“walking” 目录树意味着遍历每个子目录和文件。Visitor 设计模式提供了一种标准机制来访问集合中的每个对象,然后你需要提供在每个对象上执行的操作。
此操作的定义取决于实现的 FileVisitor 的四个抽象方法,包括:

1.  **preVisitDirectory()**:在访问目录中条目之前在目录上运行。 
2.  **visitFile()**:运行目录中的每一个文件。  
3.  **visitFileFailed()**:调用无法访问的文件。   
4.  **postVisitDirectory()**:在访问目录中条目之后在目录上运行,包括所有的子目录。

为了简化,java.nio.file.SimpleFileVisitor 提供了所有方法的默认实现。这样,在我们的匿名内部类中,我们只需要重写非标准行为的方法:visitFile()postVisitDirectory() 实现删除文件和删除目录。两者都应该返回标志位决定是否继续访问(这样就可以继续访问,直到找到所需要的)。

作为探索目录操作的一部分,现在我们可以有条件地删除已存在的目录。在以下例子中,makeVariant() 接受基本目录测试,并通过旋转部件列表生成不同的子目录路径。这些旋转与路径分隔符 sep 使用 String.join() 贴在一起,然后返回一个 Path 对象。

import java.util.*;
import java.nio.file.*;

public class Directories {
    static Path test = Paths.get("test");
    static String sep = FileSystems.getDefault().getSeparator();
    static List<String> parts = Arrays.asList("foo", "bar", "baz", "bag");

    static Path makeVariant() {
        Collections.rotate(parts, 1);
        return Paths.get("test", String.join(sep, parts));
    }

    static void refreshTestDir() throws Exception {
        if (Files.exists(test)) {
            RmDir.rmdir(test);
        }
        if (!Files.exists(test)) {
            Files.createDirectory(test);
        }
    }

    public static void main(String[] args) throws Exception {
        refreshTestDir();
        Files.createFile(test.resolve("Hello.txt"));
        Path variant = makeVariant();
        // Throws exception (too many levels):
        try {
            Files.createDirectory(variant);
        } catch (Exception e) {
            System.out.println("Nope, that doesn't work.");
        }
        populateTestDir();
        Path tempdir = Files.createTempDirectory(test, "DIR_");
        Files.createTempFile(tempdir, "pre", ".non");
        Files.newDirectoryStream(test).forEach(System.out::println);
        System.out.println("*********");
        Files.walk(test).forEach(System.out::println);
    }

    static void populateTestDir() throws Exception {
        for (int i = 0; i < parts.size(); i++) {
            Path variant = makeVariant();
            if (!Files.exists(variant)) {
                Files.createDirectories(variant);
                Files.copy(Paths.get("D:\\onJava\\test\\src\\main\\java\\com\\example\\test\\Directories.java"),
                        variant.resolve("File.txt"));
                Files.createTempFile(variant, null, null);
            }
        }
    }
}

在这里插入图片描述

首先,refreshTestDir() 用于检测 test 目录是否已经存在。若存在,则使用我们新工具类 rmdir() 删除其整个目录。检查是否 exists 是多余的,但我想说明一点,因为如果你对于已经存在的目录调用 createDirectory() 将会抛出异常。createFile() 使用参数 Path 创建一个空文件; resolve() 将文件名添加到 test Path 的末尾。

我们尝试使用 createDirectory() 来创建多级路径,但是这样会抛出异常,因为这个方法只能创建单级路径。我已经将 populateTestDir() 作为一个单独的方法,因为它将在后面的例子中被重用。对于每一个变量 variant,我们都能使用 createDirectories() 创建完整的目录路径,然后使用此文件的副本以不同的目标名称填充该终端目录。然后我们使用 createTempFile() 生成一个临时文件。

在调用 populateTestDir() 之后,我们在 test 目录下面创建一个临时目录。请注意,createTempDirectory() 只有名称的前缀选项。与 createTempFile() 不同,我们再次使用它将临时文件放入新的临时目录中。你可以从输出中看到,如果未指定后缀,它将默认使用".tmp"作为后缀。

为了展示结果,我们首次使用看起来很有希望的 newDirectoryStream(),但事实证明这个方法只是返回 test 目录内容的 Stream 流,并没有更多的内容。要获取目录树的全部内容的流,请使用 Files.walk()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1104051.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第七章 排序

第七章 排序 概述插入排序交换排序冒泡排序快速排序 选择排序直接选择排序堆排序 归并排序有序序列合并二路归并排序 小试牛刀 概述 排序就是将一组对象按照规定的次序&#xff08;升序或降序等&#xff09;重新排列的过程&#xff0c;往往为检索服务相同键值的两个记录在排序…

索引背后的数据结构——B+树

为什么要使用B树&#xff1f; 可以进行数据查询的数据结构有二叉搜索树、哈希表等。对于前者来说&#xff0c;树的高度越高&#xff0c;进行查询比较的时候访问磁盘的次数就越多。而后者只有在数据等于key值的时候才能进行查询&#xff0c;不能进行模糊匹配。所以出现了B树来解…

SQL数据库管理工具RazorSQL mac中文版特点与功能

RazorSQL mac是一款功能强大的SQL数据库管理工具&#xff0c;它支持多种数据库&#xff0c;包括MySQL、Oracle、Microsoft SQL Server、SQLite、PostgreSQL等。 RazorSQL mac 软件特点和功能 多种数据库支持&#xff1a;RazorSQL支持多种数据库&#xff0c;用户可以通过一个工…

故障预测与健康管理(PHM)在工业领域的发展前景

故障预测与健康管理&#xff08;PHM&#xff09;作为一种关键技术&#xff0c;已经在工业领域引起了广泛的关注和应用。PHM利用传感器、数据科学和智能算法等技术手段&#xff0c;通过实时监测和分析设备和系统的状态&#xff0c;提前发现潜在故障&#xff0c;并采取适当的维修…

制作linux系统内部yum源仓库

需求说明 制作内网linux系统yum源仓库&#xff0c;比较简单的方式就是添加系统镜像&#xff0c;此种yum配置方式可参考文章 https://blog.csdn.net/d1240673769/article/details/108477661 如果无法提供系统镜像&#xff0c;那该如何创建内网的yum源仓库呢&#xff1f;本文提…

互联网Java工程师面试题·Java 总结篇·第六弹

目录 56、TreeMap 和 TreeSet 在排序时如何比较元素&#xff1f;Collections 工具类中的 sort()方法如何比较元素&#xff1f; 57、Thread 类的 sleep()方法和对象的 wait()方法都可以让线程暂停执行&#xff0c;它们有什么区别? 58、线程的 sleep()方法和 yield()方法有什…

在nodejs中实现双重身份验证机制

在nodejs中实现双重身份验证机制 双重身份验证(Two-factor authentication)是一种安全机制&#xff0c;它要求用户提供两种不同的身份验证因素来访问他们的帐户&#xff1a;密码和发送到他们的移动设备的验证码。在本文中&#xff0c;我们将一步步通过使用speakeasy在nodejs中实…

deforum + kandinsky = 视频工作流

像搭积木一样玩AI&#xff0c;随着模型种类的不断丰富&#xff0c;不同的组合会带来什么惊喜&#xff1f;今天和大家分享最近看到的一个视频工作流&#xff08;工具箱&#xff09;。 首先&#xff0c;我们先对deforum和kandinsky做一些基本的介绍&#xff1a; deforum-art/defo…

【论文解读】单目3D目标检测 CUPNet(ICCV 2021)

本文分享单目3D目标检测&#xff0c;CUPNet 模型的论文解读&#xff0c;了解它的设计思路&#xff0c;论文核心观点&#xff0c;模型结构&#xff0c;以及效果和性能。 目录 一、CUPNet简介 二、论文核心观点 三、模型框架 四、损失函数 五、核心观点——3D高度估计误差 引…

Python之并发编程(进程)

文章目录 一、操作系统的发展史二、进程基础(操作系统中的概念)1.什么是进程2.进程的调度算法3.进程的并行与并发4.进程的三状态5.同步异步6.阻塞与非阻塞7.同步异步与阻塞非阻塞综合使用 三、如何创建进程Process的几个方法如何开启多进程进程间的数据默认隔离基于TCP协议的高…

【Qt控件之QButtonGroup】概述及使用

概述 QButtonGroup 类提供了一个容器来组织一组按钮部件。 QButtonGroup 提供了一个抽象容器&#xff0c;可以将按钮部件放置其中。它不提供此容器的可视表示&#xff08;请参见 QGroupBox&#xff0c;用于容器部件&#xff09;&#xff0c;而是管理组中每个按钮的状态。 一个…

Electron webview 内网页 与 preload、 渲染进程、主进程的常规通信 以及企业级开发终极简化通信方式汇总

Electron 嵌入的页面中注入的是 preload.js 通过在标签中给 prelaod赋值&#xff0c;这里提到了 file://前缀&#xff0c;以及静态目录 static 怎么获取 实际代码&#xff0c;其中__static就是我们存放静态文件的地方&#xff0c;这个 static 是 electron 源代码根目录下的文件…

使用unordered_write调优RocksDB写性能

在使用rocksdb存储的服务中&#xff0c;我们发现QPS在4w/s就怎么调整都上不去了&#xff0c;写性能受到了某种限制。为什么呢&#xff1f;下图描述了rocksdb写入的流程。我们发现 unordered_write true可以提高写入吞吐量。 rocksdb的数据正常写入流程是&#xff0c;多个线程形…

九月 NFT 行业解读:熊市情绪仍占上风

作者: stellafootprint.network 9 月&#xff0c;著名主流媒体《滚石》&#xff08;Rolling Stone&#xff09;发表了一篇题为《你的 NFT 实际上——终于——完全不值钱了》&#xff08;Your NFTs Are Actually — Finally — Totally Worthless&#xff09;的文章&#xff0c…

【网络编程】从网络编程、TCP/IP开始到BIO、NIO入门知识(未完待续...)

目录 前言前置知识一、计算机网络体系结构二、TCP/IP协议族2.1 简介*2.2 TCP/IP网络传输中的数据2.3 地址和端口号2.4 小总结 三、TCP/UDP特性3.1 TCP特性TCP 3次握手TCP 4次挥手TCP头部结构体 3.2 UDP特性 四、总结 课程内容一、网络通信编程基础知识1.1 什么是Socket1.2 长连…

NumPy基础及取值操作

目录 第1关&#xff1a;ndarray对象 相关知识 怎样安装NumPy 什么是ndarray对象 如何实例化ndarray对象 使用array函数实例化ndarray对象 使用zeros&#xff0c;ones&#xff0c;empty函数实例化ndarray对象 代码文件 第2关&#xff1a;形状操作 相关知识 怎样改变n…

液压自动化成套设备比例阀放大器

液压电气成套设备的比例阀放大器是一种电子控制设备&#xff0c;用于控制液压动力系统中的液压比例阀1。 比例阀放大器通常采用电子信号进行控制&#xff0c;以控制比例阀的开度和流量&#xff0c;以实现液压系统的可靠控制。比例阀放大器主要由以下组成部分&#xff1a; 驱动…

tomcat 服务器

tomcat 服务器 tomcat: 是一个开源的web应用服务器。区别nginx&#xff0c;nginx主要处理静态页面&#xff0c;那么动态请求&#xff08;连接数据库&#xff0c;动态页面&#xff09;并不是nginx的长处&#xff0c;动态的请求会交给tomcat进行处理。 nginx-----转发动态请求-…

Golang学习:基础知识篇(三)—— Map(集合)

Golang学习&#xff1a;基础知识篇&#xff08;三&#xff09;—— Map集合 前言什么是Golang&#xff1f;Map集合定义 Map综合实例补充 前言 很久之前就想学Go语言了&#xff0c;但是一直有其他东西要学&#xff0c;因为我学的是Java嘛&#xff0c;所以后面学的东西一直是跟J…

Element-UI 日期选择器--禁用未来日期

在做项目的时候经常会遇到一些报表需要填写日期&#xff0c;一般是填写当日及当日以前&#xff0c;这时候我们的日期选择器就需要进行一些限制&#xff0c;比如&#xff1a; 这样之后&#xff0c;就不会误填写到明天啦&#xff0c;下面让我们看一下代码实现 html页面代码 这里…