Java 编程问题:六、Java I/O 路径、文件、缓冲区、扫描和格式化

简介: Java 编程问题:六、Java I/O 路径、文件、缓冲区、扫描和格式化

问题

为了测试您的 Java I/O 编程能力,请看下面的问题。我强烈建议您在使用解决方案和下载示例程序之前,先尝试一下每个问题:


创建文件路径:写几个创建几种文件路径的例子(如绝对路径、相对路径等)。


转换文件路径:写几个转换文件路径的例子(例如,将文件路径转换成字符串、URI、文件等)。


连接文件路径:写几个连接(组合)文件路径的例子。定义一个固定路径并向其附加其他不同的路径(或用其他路径替换其中的一部分)。


在两个位置之间构造路径:写出几个例子,在两个给定路径之间(从一条路径到另一条路径)之间构造相对路径。


比较文件路径:写几个比较给定文件路径的例子。


遍历路径:编写一个程序,访问一个目录下的所有文件,包括子目录。此外,编写一个程序,按名称搜索文件、删除目录、移动目录和复制目录。


监视路径:编写多个程序,监视某条路径上发生的变化(如创建、删除、修改)。


“流式传输文件内容”:编写一个流式传输给定文件内容的程序。


在文件树中搜索文件/文件夹:编写一个程序,在给定的文件树中搜索给定的文件/文件夹。


“高效读写文本文件”:编写几个程序,举例说明高效读写文本文件的不同方法。


高效读写二进制文件:编写几个程序,举例说明高效读写二进制文件的不同方法。


在大文件中搜索:编写一个程序,在大文件中高效地搜索给定的字符串。


将 JSON/CSV 文件作为对象读取:编写一个程序,将给定的 JSON/CSV 文件作为对象读取(POJO)。


使用临时文件/文件夹:编写几个使用临时文件/文件夹的程序。


过滤文件:为文件编写多个自定义过滤器。


发现两个文件之间的不匹配:编写一个程序,在字节级发现两个文件之间的不匹配。


循环字节缓冲区:编写一个表示循环字节缓冲区实现的程序。


分词文件:写几个代码片段来举例说明分词文件内容的不同技术。


将格式化输出直接写入文件:编写一个程序,将给定的数字(整数和双精度)格式化并输出到文件中。


使用Scanner:写几个代码片段来展示Scanner的功能。



解决方案


以下各节介绍上述问题的解决方案。记住,通常没有一个正确的方法来解决一个特定的问题。另外,请记住,这里显示的解释只包括解决问题所需的最有趣和最重要的细节。您可以从这个页面下载示例解决方案以查看更多详细信息并尝试程序。




129 创建文件路径


从 JDK7 开始,我们可以通过 NIO.2API 创建一个文件路径。更准确地说,可以通过PathPathsAPI 轻松定义文件路径。


Path类是文件系统中路径的编程表示。路径字符串包含以下信息:


   文件名


   目录列表


   依赖于操作系统的文件分隔符(例如,在 Solaris 和 Linux 上为正斜杠/,在 Microsoft Windows 上为反斜杠\


   其他允许的字符,例如,.(当前目录)和..(父目录)符号

Path类处理不同文件系统(FileSystem)中的文件,这些文件系统可以使用不同的存储位置(FileStore是底层存储)。


定义Path的一个常见解决方案是调用Paths辅助类的get()方法之一。另一种解决方案依赖于FileSystems.getDefault().getPath()方法。


Path驻留在文件系统中—文件系统存储和组织文件或某种形式的媒体,通常在一个或多个硬盘驱动器上,以便于检索。文件系统可以通过java.nio.file.FileSystems的final类获取,用于获取java.nio.file.FileSystem的实例。JVM 的默认FileSystem(俗称操作系统的默认文件系统)可以通过FileSystems().getDefault()方法获得。一旦我们知道文件系统和文件(或目录/文件夹)的位置,我们就可以为它创建一个Path对象。


另一种方法包括从统一资源标识符(URI)创建Path。Java 通过URI类包装一个URI,然后我们可以通过URI.create(String uri)方法从一个String获得一个URI。此外,Paths类提供了一个get()方法,该方法将URI对象作为参数并返回相应的Path。


从 JDK11 开始,我们可以通过两个方法创建一个Path。其中一个将URI转换为Path,而另一个将路径字符串或字符串序列转换为路径字符串。


在接下来的部分中,我们将了解创建路径的各种方法。



创建相对于文件存储根目录的路径

相对于当前文件存储根目录的路径(例如,C:/)必须以文件分隔符开头。在下面的例子中,如果当前文件存储根为C,则绝对路径为C:\learning\packt\JavaModernChallenge.pdf

Path path = Paths.get("/learning/packt/JavaModernChallenge.pdf");
Path path = Paths.get("/learning", "packt/JavaModernChallenge.pdf");
Path path = Path.of("/learning/packt/JavaModernChallenge.pdf");
Path path = Path.of("/learning", "packt/JavaModernChallenge.pdf");
Path path = FileSystems.getDefault()
  .getPath("/learning/packt", "JavaModernChallenge.pdf");
Path path = FileSystems.getDefault()
  .getPath("/learning/packt/JavaModernChallenge.pdf");
Path path = Paths.get(
  URI.create("file:///learning/packt/JavaModernChallenge.pdf"));
Path path = Path.of(
  URI.create("file:///learning/packt/JavaModernChallenge.pdf"));


创建相对于当前文件夹的路径

当我们创建一个相对于当前工作文件夹的路径时,路径应该以文件分隔符开头。如果当前文件夹名为books并且在C根目录下,那么下面代码段返回的绝对路径将是C:\books\learning\packt\JavaModernChallenge.pdf

Path path = Paths.get("learning/packt/JavaModernChallenge.pdf");
Path path = Paths.get("learning", "packt/JavaModernChallenge.pdf");
Path path = Path.of("learning/packt/JavaModernChallenge.pdf");
Path path = Path.of("learning", "packt/JavaModernChallenge.pdf");
Path path = FileSystems.getDefault()
  .getPath("learning/packt", "JavaModernChallenge.pdf");
Path path = FileSystems.getDefault()
  .getPath("learning/packt/JavaModernChallenge.pdf");



创建绝对路径

创建绝对路径可以通过显式指定根目录和包含文件或文件夹的所有其他子目录来完成,如以下示例(C:\learning\packt\JavaModernChallenge.pdf)所示:

Path path = Paths.get("C:/learning/packt", "JavaModernChallenge.pdf");
Path path = Paths.get(
  "C:", "learning/packt", "JavaModernChallenge.pdf");
Path path = Paths.get(
  "C:", "learning", "packt", "JavaModernChallenge.pdf");
Path path = Paths.get("C:/learning/packt/JavaModernChallenge.pdf");
Path path = Paths.get(
  System.getProperty("user.home"), "downloads", "chess.exe");
Path path = Path.of(
  "C:", "learning/packt", "JavaModernChallenge.pdf");
Path path = Path.of(
  System.getProperty("user.home"), "downloads", "chess.exe");
Path path = Paths.get(URI.create(
  "file:///C:/learning/packt/JavaModernChallenge.pdf"));
Path path = Path.of(URI.create(
  "file:///C:/learning/packt/JavaModernChallenge.pdf"));


使用快捷方式创建路径

我们理解快捷方式是.(当前目录)和..(父目录)符号。这种路径可以通过normalize()方法进行归一化。此方法消除了冗余,例如.directory/..

Path path = Paths.get(
  "C:/learning/packt/chapters/../JavaModernChallenge.pdf")
    .normalize();
Path path = Paths.get(
  "C:/learning/./packt/chapters/../JavaModernChallenge.pdf")
    .normalize();
Path path = FileSystems.getDefault()
  .getPath("/learning/./packt", "JavaModernChallenge.pdf")
    .normalize();
Path path = Path.of(
  "C:/learning/packt/chapters/../JavaModernChallenge.pdf")
    .normalize();
Path path = Path.of(
  "C:/learning/./packt/chapters/../JavaModernChallenge.pdf")
    .normalize();


如果不规范化,路径的冗余部分将不会被删除。


为了创建与当前操作系统 100% 兼容的路径,我们可以依赖于FileSystems.getDefault().getPath(),或者是File.separator(依赖于系统的默认名称分隔符)和File.listRoots()(可用的文件系统根)的组合。


对于相对路径,我们可以依赖以下示例:

private static final String FILE_SEPARATOR = File.separator;


或者,我们可以依赖getSeparator()

private static final String FILE_SEPARATOR
  = FileSystems.getDefault().getSeparator();
// relative to current working folder
Path path = Paths.get("learning",
  "packt", "JavaModernChallenge.pdf");
Path path = Path.of("learning",
  "packt", "JavaModernChallenge.pdf");
Path path = Paths.get(String.join(FILE_SEPARATOR, "learning",
  "packt", "JavaModernChallenge.pdf"));
Path path = Path.of(String.join(FILE_SEPARATOR, "learning",
  "packt", "JavaModernChallenge.pdf"));
// relative to the file store root
Path path = Paths.get(FILE_SEPARATOR + "learning",
  "packt", "JavaModernChallenge.pdf");
Path path = Path.of(FILE_SEPARATOR + "learning",
  "packt", "JavaModernChallenge.pdf");


我们也可以对绝对路径执行相同的操作:

Path path = Paths.get(File.listRoots()[0] + "learning",
  "packt", "JavaModernChallenge.pdf");
Path path = Path.of(File.listRoots()[0] + "learning",
  "packt", "JavaModernChallenge.pdf");

根目录列表也可以通过FileSystems获得:

FileSystems.getDefault().getRootDirectories()




130 转换文件路径

将文件路径转换为StringURIFile等是一项常见任务,可以在广泛的应用中发生。让我们考虑以下文件路径:

Path path = Paths.get("/learning/packt", "JavaModernChallenge.pdf");



现在,基于 JDK7 和 NIO.2 API,让我们看看如何将一个Path转换成一个String、一个URI、一个绝对路径、一个实路径和一个文件:

   将Path转换为String非常简单,只需(显式地或自动地)调用Path.toString()方法。注意,如果路径是通过FileSystem.getPath()方法获得的,那么toString()返回的路径字符串可能与用于创建路径的初始String不同:


// \learning\packt\JavaModernChallenge.pdf
String pathToString = path.toString();


   可以通过Path.toURI()方法将Path转换为URI(浏览器格式)。返回的URI包装了一个可在 Web 浏览器地址栏中使用的路径字符串:

// file:///D:/learning/packt/JavaModernChallenge.pdf
URI pathToURI = path.toUri();


假设我们想要将URI/URL中的文件名提取为Path(这是常见的场景)。在这种情况下,我们可以依赖以下代码片段:

// JavaModernChallenge.pdf
URI uri = URI.create(
  "https://www.learning.com/packt/JavaModernChallenge.pdf");
Path URIToPath = Paths.get(uri.getPath()).getFileName();
// JavaModernChallenge.pdf
URL url = new URL(
  "https://www.learning.com/packt/JavaModernChallenge.pdf");
Path URLToPath = Paths.get(url.getPath()).getFileName();


路径转换可按以下方式进行:

  • 可以通过Path.toAbsolutePath()方法将相对Path转换为绝对Path。如果Path已经是绝对值,则返回相同的结果:


// D:\learning\packt\JavaModernChallenge.pdf
Path pathToAbsolutePath = path.toAbsolutePath();



   通过Path.toRealPath()方法可以将Path转换为实际Path,其结果取决于实现。如果所指向的文件不存在,则此方法将抛出一个IOException。但是,根据经验,调用此方法的结果是没有冗余元素的绝对路径(标准化)。此方法获取一个参数,该参数指示应如何处理符号链接。默认情况下,如果文件系统支持符号链接,则此方法将尝试解析它们。如果您想忽略符号链接,只需将LinkOption.NOFOLLOW_LINKS常量传递给方法即可。此外,路径名元素将表示目录和文件的实际名称。


例如,让我们考虑下面的Path和调用此方法的结果(注意,我们故意添加了几个冗余元素并将PACKT文件夹大写):

Path path = Paths.get(
  "/learning/books/../PACKT/./", "JavaModernChallenge.pdf");
// D:\learning\packt\JavaModernChallenge.pdf
Path realPath = path.toRealPath(LinkOption.NOFOLLOW_LINKS);


  • 可通过Path.toFile()方法将Path转换为文件。将一个文件转换成一个Path,我们可以依赖File.toPath()方法:
File pathToFile = path.toFile();
Path fileToPath = pathToFile.toPath();



131 连接文件路径


连接(或组合)文件路径意味着定义一个固定的根路径,并附加一个部分路径或替换其中的一部分(例如,一个文件名需要替换为另一个文件名)。基本上,当我们想要创建共享公共固定部分的新路径时,这是一种方便的技术。


这可以通过 NIO.2 和Path.resolve()Path.resolveSibling()方法来实现。


让我们考虑以下固定根路径:

Path base = Paths.get("D:/learning/packt");


我们还假设我们想要得到两本不同书籍的Path

// D:\learning\packt\JBossTools3.pdf
Path path = base.resolve("JBossTools3.pdf");
// D:\learning\packt\MasteringJSF22.pdf
Path path = base.resolve("MasteringJSF22.pdf");


我们可以使用此函数循环一组文件;例如,让我们循环一String[]本书:

Path basePath = Paths.get("D:/learning/packt");
String[] books = {
  "Book1.pdf", "Book2.pdf", "Book3.pdf"
};
for (String book: books) {
  Path nextBook = basePath.resolve(book);
  System.out.println(nextBook);
}


有时,固定根路径也包含文件名:

Path base = Paths.get("D:/learning/packt/JavaModernChallenge.pdf");


这一次,我们可以通过resolveSibling()方法将文件名(JavaModernChallenge.pdf替换为另一个名称。此方法根据此路径的父路径解析给定路径,如下例所示:

// D:\learning\packt\MasteringJSF22.pdf
Path path = base.resolveSibling("MasteringJSF22.pdf");


如果我们将Path.getParent()方法引入讨论,并将resolve()resolveSibling()方法链接起来,那么我们可以创建更复杂的路径,如下例所示:

// D:\learning\publisher\MyBook.pdf
Path path = base.getParent().resolveSibling("publisher")
  .resolve("MyBook.pdf");

resolve()/resolveSibling()方法分为两种,分别是resolve(String other)/resolveSibling(String other)resolve(Path other)/resolveSibling(Path other)



132 在两个位置之间构建路径


在两个位置之间构建相对路径是Path.relativize()方法的工作。


基本上,得到的相对路径(由Path.relativize()返回)从一条路径开始,在另一条路径上结束。这是一个强大的功能,它允许我们使用相对路径在不同的位置之间导航,相对路径是根据前面的路径解析的。


让我们考虑以下两种途径:

Path path1 = Paths.get("JBossTools3.pdf");
Path path2 = Paths.get("JavaModernChallenge.pdf");


注意,JBossTools3.pdfJavaModernChallenge.pdf是兄弟姐妹。这意味着我们可以通过上一级然后下一级从一个导航到另一个。以下示例也揭示了这种导航情况:

// ..\JavaModernChallenge.pdf
Path path1ToPath2 = path1.relativize(path2);
// ..\JBossTools3.pdf
Path path2ToPath1 = path2.relativize(path1);


另一种常见情况涉及公共根元素:

Path path3 = Paths.get("/learning/packt/2003/JBossTools3.pdf");
Path path4 = Paths.get("/learning/packt/2019");


所以,path3path4共享相同的根元素/learning。从path3path4需要上两层下一层。另外,从path4path3的航行,需要上一级,下两级。查看以下代码:

// ..\..\2019
Path path3ToPath4 = path3.relativize(path4);
// ..\2003\JBossTools3.pdf
Path path4ToPath3 = path4.relativize(path3);

两个路径都必须包含根元素。完成这个需求并不能保证成功,因为相对路径的构建依赖于实现。



133 比较文件路径


根据我们如何看待两个文件路径之间的相等性,有几种解决方案。主要来说,平等性可以通过不同的方式为不同的目标进行验证。


假设我们有以下三种路径(考虑在您的计算机上复制C:\learning\packt\JavaModernChallenge.pdf

Path path1 = Paths.get("/learning/packt/JavaModernChallenge.pdf");
Path path2 = Paths.get("/LEARNING/PACKT/JavaModernChallenge.pdf");
Path path3 = Paths.get("D:/learning/packt/JavaModernChallenge.pdf");

在下面的部分中,我们将研究用于比较文件路径的不同方法。



Path.equals()


path1等于path2吗?或者,path2等于path3吗?好吧,如果我们通过Path.equals()进行这些测试,那么可能的结果将显示path1等于path2,但path2不等于path3

boolean path1EqualsPath2 = path1.equals(path2); // true
boolean path2EqualsPath3 = path2.equals(path3); // false


Path.equals()方法遵循Object.equals()规范。虽然此方法不访问文件系统,但相等性取决于文件系统实现。例如,一些文件系统实现可能会以区分大小写的方式比较路径,而其他文件系统实现可能会忽略大小写。




表示相同文件/文件夹的路径


然而,这可能不是我们想要的那种比较。如果两条路径是相同的文件或文件夹,那么说它们相等就更有意义了。这可以通过Files.isSameFile()方法来实现。此方法分为两个步骤:


首先,它调用Path.equals(),如果此方法返回true,则路径相等,无需进一步操作。

其次,如果Path.equals()返回false,则检查两个路径是否代表同一个文件/文件夹(根据实现,此操作可能需要打开/访问两个文件,因此文件必须存在,以避免出现IOException)。


//true
boolean path1IsSameFilePath2 = Files.isSameFile(path1, path2);
//true
boolean path1IsSameFilePath3 = Files.isSameFile(path1, path3);
//true
boolean path2IsSameFilePath3 = Files.isSameFile(path2, path3);




词典比较


如果我们只需要对路径进行词典比较,那么我们可以依赖于Path.compareTo()方法(这对于排序很有用)。

此方法返回以下信息:

  • 如果路径相等,则为 0
  • 如果第一条路径在词典上小于参数路径,则该值小于零
  • 如果第一条路径在词典中大于参数路径,则该值大于零:
int path1compareToPath2 = path1.compareTo(path2); // 0
int path1compareToPath3 = path1.compareTo(path3); // 24
int path2compareToPath3 = path2.compareTo(path3); // 24


请注意,您可能会获得与上一示例不同的值。此外,在您的业务逻辑中,重要的是依赖于它们的含义,而不是依赖于它们的值(例如,说if(path1compareToPath3 > 0) { ... },避免使用if(path1compareToPath3 == 24) { ... })。




部分比较


部分比较可通过Path.startsWith()Path.endsWith()方法实现。使用这些方法,我们可以测试当前路径是否以给定路径开始/结束:


boolean sw = path1.startsWith("/learning/packt");       // true
boolean ew = path1.endsWith("JavaModernChallenge.pdf"); // true
相关文章
|
1天前
|
安全 Java 开发者
Java一分钟之-文件与目录操作:Path与Files类
【5月更文挑战第13天】Java 7 引入`java.nio.file`包,`Path`和`Files`类提供文件和目录操作。`Path`表示路径,不可变。`Files`包含静态方法,支持创建、删除、读写文件和目录。常见问题包括:忽略异常处理、路径解析错误和权限问题。在使用时,注意异常处理、正确格式化路径和考虑权限,以保证代码稳定和安全。结合具体需求,这些方法将使文件操作更高效。
10 2
|
1天前
|
Java ice
【Java开发指南 | 第二十一篇】Java流之文件
【Java开发指南 | 第二十一篇】Java流之文件
8 0
|
1天前
|
数据采集 安全 Java
Java并发编程学习12-任务取消(上)
【5月更文挑战第6天】本篇介绍了取消策略、线程中断、中断策略 和 响应中断的内容
15 4
Java并发编程学习12-任务取消(上)
|
1天前
|
Java 开发者
深入理解Java并发编程:从基础到高级
【5月更文挑战第13天】本文将深入探讨Java并发编程的各个方面,从基础知识到高级概念。我们将首先介绍线程的基本概念,然后深入讨论Java中的多线程编程,包括线程的创建和控制,以及线程间的通信。接下来,我们将探讨并发编程中的关键问题,如同步、死锁和资源竞争,并展示如何使用Java的内置工具来解决这些问题。最后,我们将讨论更高级的并发编程主题,如Fork/Join框架、并发集合和并行流。无论你是Java新手还是有经验的开发者,这篇文章都将帮助你更好地理解和掌握Java并发编程。
|
1天前
|
安全 算法 Java
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第13天】 在Java开发中,并发编程是一个复杂且重要的领域。它不仅关系到程序的线程安全性,也直接影响到系统的性能表现。本文将探讨Java并发编程的核心概念,包括线程同步机制、锁优化技术以及如何平衡线程安全和性能。通过分析具体案例,我们将提供实用的编程技巧和最佳实践,帮助开发者在确保线程安全的同时,提升应用性能。
10 1
|
1天前
|
Java 编译器 开发者
Java并发编程中的锁优化策略
【5月更文挑战第13天】在Java并发编程中,锁是一种重要的同步机制,用于保证多线程环境下数据的一致性。然而,不当的使用锁可能会导致性能下降,甚至产生死锁等问题。本文将介绍Java中锁的优化策略,包括锁粗化、锁消除、锁降级等,帮助开发者提高程序的性能。
|
2天前
|
安全 Java 调度
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第12天】 在现代软件开发中,多线程编程是提升应用程序性能和响应能力的关键手段之一。特别是在Java语言中,由于其内置的跨平台线程支持,开发者可以轻松地创建和管理线程。然而,随之而来的并发问题也不容小觑。本文将探讨Java并发编程的核心概念,包括线程安全策略、锁机制以及性能优化技巧。通过实例分析与性能比较,我们旨在为读者提供一套既确保线程安全又兼顾性能的编程指导。
|
3天前
|
安全 Java
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第11天】在Java并发编程中,线程安全和性能优化是两个重要的主题。本文将深入探讨这两个方面,包括线程安全的基本概念,如何实现线程安全,以及如何在保证线程安全的同时进行性能优化。我们将通过实例和代码片段来说明这些概念和技术。
4 0
|
3天前
|
Java 调度
Java并发编程:深入理解线程池
【5月更文挑战第11天】本文将深入探讨Java中的线程池,包括其基本概念、工作原理以及如何使用。我们将通过实例来解释线程池的优点,如提高性能和资源利用率,以及如何避免常见的并发问题。我们还将讨论Java中线程池的实现,包括Executor框架和ThreadPoolExecutor类,并展示如何创建和管理线程池。最后,我们将讨论线程池的一些高级特性,如任务调度、线程优先级和异常处理。
|
4天前
|
Java 开发者
Java一分钟之-Java IO流:文件读写基础
【5月更文挑战第10天】本文介绍了Java IO流在文件读写中的应用,包括`FileInputStream`和`FileOutputStream`用于字节流操作,`BufferedReader`和`PrintWriter`用于字符流。通过代码示例展示了如何读取和写入文件,强调了常见问题如未关闭流、文件路径、编码、权限和异常处理,并提供了追加写入与读取的示例。理解这些基础知识和注意事项能帮助开发者编写更可靠的程序。
16 0