JavaRush /Java 博客 /Random-ZH /Java 文件、路径

Java 文件、路径

已在 Random-ZH 群组中发布
你好!今天我们将讨论如何使用文件和目录。您已经知道如何管理文件的内容:我们有很多专门用于此目的的类:)我认为您可以轻松记住实现这些目的所需的几个类。在今天的讲座中,我们将具体讨论文件管理——创建、重命名等。在 Java 7 之前,所有此类操作都是使用File. 您可以在这里阅读有关他的作品。但在 Java 7 中,该语言的创建者决定改变他们处理文件和目录的方式。这是因为该课程File有许多缺点。例如,它没有一种方法copy()可以让您将文件从一个位置复制到另一个位置(看似显然需要的功能)。此外,该类File还有很多返回boolean值的方法。如果发生错误,此类方法将返回false而不是抛出异常,这使得诊断错误并确定其原因变得非常困难。出现的不是单个类,而是File多达 3 个类:PathsPathFiles。嗯,准确地说,Path这是一个接口,而不是一个类。让我们弄清楚它们之间有何不同以及为什么需要它们。让我们从最简单的事情开始—— Paths

路径

Paths是一个非常简单的类,只有一个静态方法get()。它的创建只是为了从传递的 string 或 URI 中获取类型的对象Path。它没有其他功能。这是他的工作示例:
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {

   public static void main(String[] args) {

       Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
   }
}
不是最难的课程,对吧?:) 好吧,既然我们得到了一个类型为 的对象Path,那么让我们弄清楚它是什么Path以及为什么需要它:)

小路

Path总的来说,是重新设计的File. 与 一起工作比与 一起工作要容易得多File首先,许多实用(静态)方法被从中删除并移至类中Files其次Path方法的返回值是有序的。在类中,File方法返回了这个String、那个boolean、那个File——这并不容易弄清楚。例如,有一个方法getParent()以字符串形式返回当前文件的父路径。但同时有一个方法getParentFile()返回相同的东西,但以对象的形式File!这显然是多余的。因此,在接口中,Path该方法getParent()和其他处理文件的方法只是返回一个对象Path。没有太多的选择——一切都很简单。 它有哪些有用的方法呢Path 以下是其中的一些以及他们的工作示例:
  • getFileName()— 从路径返回文件名;

  • getParent()— 返回相对于当前路径的“父”目录(即目录树中较高的目录);

  • getRoot()— 返回“根”目录;即位于目录树顶部的那个;

  • startsWith(), endsWith()— 检查路径是否以传递的路径开始/结束:

    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
    
           Path fileName = testFilePath.getFileName();
           System.out.println(fileName);
    
           Path parent = testFilePath.getParent();
           System.out.println(parent);
    
           Path root = testFilePath.getRoot();
           System.out.println(root);
    
           boolean endWithTxt = testFilePath.endsWith("Desktop\\testFile.txt");
           System.out.println(endWithTxt);
    
           boolean startsWithLalala = testFilePath.startsWith("lalalala");
           System.out.println(startsWithLalala);
       }
    }

    控制台输出:

    testFile.txt
    C:\Users\用户名\桌面
    C:\
    true
    false

    注意该方法是如何运作的endsWith()。它检查当前路径是否以传递的路径结束。它位于路径上,而不是字符集上

    比较这两个调用的结果:

    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
    
           System.out.println(testFilePath.endsWith("estFile.txt"));
           System.out.println(testFilePath.endsWith("Desktop\\testFile.txt"));
       }
    }

    控制台输出:


    您需要将完整路径传递给方法endsWith(),而不仅仅是一组字符:否则结果将始终为false,即使当前路径实际上以这样的字符序列结尾(如“estFile.txt”的情况) ”在上面的例子中)。

    此外,还有Path一组方法可以简化绝对(完整)和相对路径的使用

让我们看看这些方法:
  • boolean isAbsolute()—如果当前路径是绝对路径,则返回true :

    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
    
           System.out.println(testFilePath.isAbsolute());
       }
    }

    控制台输出:

    真的

  • Path normalize()—“规范化”当前路径,从中删除不必要的元素。您可能知道,流行的操作系统在表示路径时经常使用“.”字符。(“当前目录”)和“..”(父目录)。例如:“ ./Pictures/dog.jpg ”表示我们现在所在的目录下有一个Pictures文件夹,里面有一个文件“dog.jpg”

    所以就是这样。如果您的程序有一个使用“.”的路径 或“..”,该方法normalize()将删除它们并获取不包含它们的路径:

    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
    
           Path path5 = Paths.get("C:\\Users\\Java\\.\\examples");
    
           System.out.println(path5.normalize());
    
           Path path6 = Paths.get("C:\\Users\\Java\\..\\examples");
           System.out.println(path6.normalize());
       }
    }

    控制台输出:

    C:\Users\Java\examples
    C:\Users\examples

  • Path relativize()— 计算当前路径和传递路径之间的相对路径。

    例如:

    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath1 = Paths.get("C:\\Users\\Users\\Users\\Users");
           Path testFilePath2 = Paths.get("C:\\Users\\Users\\Users\\Users\\Username\\Desktop\\testFile.txt");
    
           System.out.println(testFilePath1.relativize(testFilePath2));
       }
    }

    控制台输出:

    用户名\桌面\testFile.txt

完整的方法列表Path相当大。您可以在Oracle 文档中找到它们。我们将继续进行审查Files

文件

Files- 这是一个实用程序类,其中移动了类中的静态方法File。- 这与orFiles大致相同,只是它适用于文件,而不适用于数组和集合:) 它专注于管理文件和目录。使用静态方法,我们可以创建、删除和移动文件和目录。对于这些操作,使用方法(对于目录 - )和。以下是如何使用它们: ArraysCollectionsFilescreateFile()createDirectory()move()delete()
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class Main {

   public static void main(String[] args) throws IOException {

       //file creation
       Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
       System.out.println("Was the file created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       // create directory
       Path testDirectory = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory"));
       System.out.println("Was the directory successfully created?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory")));

       //move file from desktop to testDirectory. You need to move with the name of the file in the folder!
       testFile1 = Files.move(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("Is our file left on the desktop?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println("Has our file been moved to testDirectory?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));

       //remove file
       Files.delete(testFile1);
       System.out.println("Does the file still exist?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));
   }
}
Files.createFile()这里我们首先在桌面上 创建一个文件(方法),然后在那里创建一个文件夹(方法Files.createDirectory())。之后,我们将文件(method Files.move())从桌面移动到这个新文件夹,最后我们删除该文件(method Files.delete())。 控制台输出: 文件创建成功吗?true 目录创建成功吗?true 我们的文件还在桌面上吗?false 我们的文件是否已移至 testDirectory?true 文件是否仍然存在?错误的 注意:就像接口方法一样Path许多方法Files返回一个对象Path。大多数类方法Files也接受Path. 这里有一个方法将成为你忠实的助手Paths.get()——积极使用它。还有什么有趣的地方Files?旧类真正缺少的是!File方法。copy()我们在讲座一开始就谈到了他,现在是时候认识他了!
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class Main {

   public static void main(String[] args) throws IOException {

       //file creation
       Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
       System.out.println("Was the file created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       // create directory
       Path testDirectory2 = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2"));
       System.out.println("Was the directory successfully created?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2")));

       //copy the file from the desktop to the directory testDirectory2.
       testFile1 = Files.copy(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("Is our file left on the desktop?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println("Has our file been copied to testDirectory?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt")));
   }
}
控制台输出: 文件创建成功吗?true 目录创建成功吗?true 我们的文件还在桌面上吗?true 我们的文件是否已复制到 testDirectory ?true 现在您可以通过编程方式复制文件!:) 但是该类Files不仅允许您管理文件本身,还允许您使用其内容。要将数据写入文件,它有一个方法write(),并且有一个方法用于读取 - 多达 3 个:read()readAllBytes()我们readAllLines() 将详细讨论后者。为什么要上它?因为它有一个非常有趣的返回类型 - List<String>! 也就是说,它返回文件中的行列表。当然,这使得处理内容变得非常方便,因为整个文件可以逐行输出到控制台,例如定期循环for
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;

public class Main {

   public static void main(String[] args) throws IOException {

       List<String> lines = Files.readAllLines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"), UTF_8);

       for (String s: lines) {
           System.out.println(s);
       }
   }
}
控制台输出: 我记得一个美妙的时刻:你出现在我面前,像转瞬即逝的幻象,像一个纯粹美丽的天才。 很舒服!:)这个特性在Java 7中出现。在Java 8中,出现了Stream API,它为Java添加了一些函数式编程的元素。包括更丰富的文件管理功能。想象一下,我们有一个任务:找到文件中以“How”一词开头的所有行,将它们转换为大写并将它们输出到控制台。在 Java 7 中使用类的解决方案是什么Files样的?像这样的东西:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;

public class Main {

   public static void main(String[] args) throws IOException {

       List<String> lines = Files.readAllLines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"), UTF_8);

       List<String> result = new ArrayList<>();

       for (String s: lines) {
           if (s.startsWith("How")) {
               String upper = s.toUpperCase();
               result.add(upper);
           }
       }

       for (String s: result) {
           System.out.println(s);
       }
   }
}
控制台输出: LIKE A FASTING VISION, LIKE A GENIUS OF PURE BEAUTY。 我们似乎已经做到了,但是您不认为对于这样一个简单的任务,我们的代码有点……冗长吗?使用 Java 8 Stream API,解决方案看起来更加优雅:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

   public static void main(String[] args) throws IOException {

       Stream<String> stream = Files.lines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"));

       List<String> result  = stream
               .filter(line -> line.startsWith("How"))
               .map(String::toUpperCase)
               .collect(Collectors.toList());
       result.forEach(System.out::println);
   }
}
我们取得了相同的结果,但代码少得多!而且,也不能说我们输在了“可读性”上。我认为即使您不熟悉 Stream API,您也可以轻松评论这段代码的作用。但简而言之,Stream是一个元素序列,您可以在其中执行不同的功能。我们从方法中获取 Stream 对象Files.lines(),然后对其应用 3 个函数:
  1. 使用该方法,filter()我们仅从文件中选择以“How”开头的那些行。

  2. 我们使用该方法遍历所有选定的行map(),并将它们中的每一行都变为大写。

  3. 我们将所有生成的行组合起来List使用collect().

在出口处,我们得到了相同的结果: 就像一个快速的愿景,就像一个纯粹美丽的天才。 如果您有兴趣了解有关该库的功能的更多信息,我们建议您阅读这篇文章。我们将回到我们的羊,即文件:)我们今天要考虑的最后一种可能性是遍历文件树。现代操作系统中的文件结构通常采用树的形式:它有一个根,还有可以与其他分支分开的分支,等等。目录起着根和分支的作用。例如,目录“ C:// ”可以充当根目录。从它分支出两个分支:“ C://Downloads ”和“ C://Users ”。每个分支还有 2 个分支:“ C://Downloads/Pictures ”、“ C://Downloads/Video ”、“ C://Users/JohnSmith ”、“ C://Users/Pudge2005 ” 。其他分支从这些分支中分支出来,等等。- 这就是一棵树的样子。在 Linux 中看起来差不多,只是目录充当根目录/ 文件、路径 - 2现在想象我们有一个任务:知道根目录,我们必须遍历它,查看所有级别的文件夹并找到其中包含内容的文件我们需要。我们将查找包含“这是我们需要的文件!”这一行的文件。我们的根目录将是位于桌面上的“testFolder”文件夹。里面有以下内容: 文件、路径 - 3在文件夹 level1-a 和 level1-b 里面还有文件夹: 文件、路径 - 4文件、路径 - 5在这些“二级文件夹”里面没有更多的文件夹,只有单独的文件: 文件、路径 - 6文件、路径 - 7我们将专门指定 3 个文件,其中包含我们的内容需要有明确的名称 - FileWeNeed1.txt 、 FileWeNeed2.txt 、 FileWeNeed3.txt 这些是我们需要使用 Java 按内容查找的内容。我们应该怎么做?一种非常强大的遍历文件树的方法可以解决问题 - Files.walkFileTree(). 这就是我们需要做的。首先,我们需要FileVisitor. FileVisitor是一个特殊的接口,描述了遍历文件树的所有方法。具体来说,我们将在那里放置逻辑来读取文件的内容并检查它是否包含我们需要的文本。这就是我们的样子FileVisitor
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;

public class MyFileVisitor extends SimpleFileVisitor<Path> {

   @Override
   public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

       List<String> lines = Files.readAllLines(file);
       for (String s: lines) {
           if (s.contains("This is the file we need")) {
               System.out.println("Required file found!");
               System.out.println(file.toAbsolutePath());
               break;
           }
       }

       return FileVisitResult.CONTINUE;
   }
}
在这种情况下,我们的类继承自SimpleFileVisitor. 这是一个实现 的类FileVisitor,您只需重写其中一个方法:visitFile()。这里我们描述了每个目录中的每个文件需要做什么。如果您需要更复杂的遍历逻辑,您应该编写自己的实现FileVisitor。在那里你需要实现另外 3 个方法:
  • preVisitDirectory()— 进入文件夹之前必须执行的逻辑;

  • visitFileFailed()— 如果无法输入文件(无法访问或其他原因)该怎么办;

  • postVisitDirectory()——进入文件夹后必须执行的逻辑。

我们没有这样的逻辑,所以对我们来说就足够了SimpleFileVisitor。该方法内部的逻辑visitFile()非常简单:从文件中读取所有行,检查它们是否包含我们需要的内容,如果包含,则将绝对路径打印到控制台。唯一可能给你带来麻烦的一行是这一行:
return FileVisitResult.CONTINUE;
其实,一切都很简单。这里我们简单描述一下文件输入并完成所有必要的操作后程序应该做什么。在我们的例子中,我们需要继续遍历树,所以我们选择选项CONTINUE。但是,例如,我们可能有另一项任务:不是找到所有包含“这是我们需要的文件”的文件,而是只找到一个这样的文件。此后,必须终止该程序。在这种情况下,我们的代码看起来完全相同,但不是中断;会:
return FileVisitResult.TERMINATE;
好吧,让我们运行我们的代码,看看它是否有效。
import java.io.IOException;
import java.nio.file.*;

public class Main {

   public static void main(String[] args) throws IOException {

       Files.walkFileTree(Paths.get("C:\\Users\\Username\\Desktop\\testFolder"), new MyFileVisitor());
   }
}
控制台输出: 已找到所需文件!C:\Users\用户名\Desktop\testFolder\FileWeNeed1.txt 找到所需文件!C:\Users\用户名\Desktop\testFolder\level1-a\level2-aa\FileWeNeed2.txt 找到所需文件!C:\Users\用户名\Desktop\testFolder\level1-b\level2-bb\FileWeNeed3.txt 太好了,我们做到了!:) 如果你想了解更多walkFileTree(),我推荐你这篇文章。您还可以完成一项小任务 - 将其替换SimpleFileVisitor为常规任务FileVisitor,实现所有 4 种方法并为该程序提出一个目的。例如,您可以编写一个程序来记录您的所有操作:在输入文件或文件夹之前/之后在控制台中显示文件或文件夹的名称。就这样 - 稍后见!:)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION