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
相关文章
|
6天前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
55 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
14天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
27天前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
86 34
|
18天前
|
算法 Java 调度
java并发编程中Monitor里的waitSet和EntryList都是做什么的
在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。 - **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。 - **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。
52 12
|
14天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
97 2
|
1月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
1月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
51 3
|
Java
java中I/O流之字节流和字符流学习总结(下)
java中I/O流之字节流和字符流学习总结(下)
124 0
java中I/O流之字节流和字符流学习总结(下)
|
移动开发 Java 数据库
java中I/O流之字节流和字符流学习总结(上)
java中I/O流之字节流和字符流学习总结(上)
140 0
java中I/O流之字节流和字符流学习总结(上)
|
移动开发 Java
Java I/O学习(附实例和详解)
版权声明:本文为博主原创文章,转载注明出处http://blog.csdn.net/u013142781 目录(?)[+] 一、Java I/O类结构以及流的基本概念 在阅读Java I/O的实例之前我们必须清楚一些概念,我们先看看Java I/O的类结构图: Java I/O主要以流的形式进行读写数据。
2546 0