CodeGym /Java Blog /Toto sisi /Java 文件,路徑
John Squirrels
等級 41
San Francisco

Java 文件,路徑

在 Toto sisi 群組發布
你好!今天我們將討論使用文件和目錄。您已經知道如何管理文件內容:我們為此專門上了很多課 :) 我想您會發現很容易記住一些用於這些目的的類。在今天的課程中,我們將專門討論文件管理:創建、重命名等。在 Java 7 之前,所有這些操作都是使用 File執行的。你可以在這裡閱讀它。但是在 Java 7 中,該語言的創建者決定改變我們使用文件和目錄的方式。發生這種情況是因為File類有幾個缺點。例如,它沒有copy()方法,該方法可以讓您將文件從一個位置複製到另一個位置(看似必不可少的功能)。除此之外文件類有很多返回布爾值的方法。當出現錯誤時,此類方法返回 false。它不會拋出異常,因此很難識別錯誤並診斷其原因。在單個File類的位置,出現了 3 個類:PathsPathFiles。嗯,準確的說,Path是一個接口,而不是一個類。讓我們弄清楚它們之間有何不同以及為什麼我們需要它們中的每一個。讓我們從最簡單的開始:路徑

路徑

Paths是一個非常簡單的類,只有一個靜態方法:get()。創建它只是為了從傳遞的字符串或 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類中,方法返回StringbooleanFile。弄清楚這件事並不容易。例如,有一個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\Username\Desktop
    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"));
       }
    }
    

    控制台輸出:

    
    false
    true
    

    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());
       }
    }
    

    控制台輸出:

    
    true
    
  • 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));
       }
    }
    

    控制台輸出:

    
    Username\Desktop\testFile.txt
    

Path方法的完整列表很長。您可以在Oracle 文檔中找到它們。現在我們將繼續考慮Files

文件

Files是一個實用程序類,它包含從File類中提取的靜態方法。Files與ArraysCollections相當。不同之處在於它使用文件,而不是數組或集合:) 它專注於管理文件和目錄。使用Files類的靜態方法,我們可以創建、刪除和移動文件和目錄。這些操作是使用createFile()(對於目錄, 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 {

       // Create a file
       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 a directory
       Path testDirectory = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory"));
       System.out.println("Was the directory created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory")));

       // Move the file from the desktop to the testDirectory directory. When you move a folder, you need to indicate its name in the folder!
       testFile1 = Files.move(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("Did our file remain 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")));

       // Delete a 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()方法)。之後,我們將文件(Files.move()方法)從桌面移動到這個新文件夾,最後刪除文件(Files.delete()方法)。 控制台輸出:

Was the file created successfully? 
true 
Was the directory created successfully? 
true
Did our file remain on the desktop? 
false 
Has our file been moved to testDirectory? 
true 
Does the file still exist? 
false
筆記:與接口的方法一樣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 {

       // Create a file
       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 a directory
       Path testDirectory2 = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2"));
       System.out.println("Was the directory created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2")));

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

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

       System.out.println("Was our file copied to testDirectory?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt")));
   }
}
控制台輸出:

Was the file created successfully? 
true 
Was the directory created successfully? 
true 
Did our file remain on the desktop? 
true 
Was our file copied to 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);
       }
   }
}
控制台輸出:

I still recall the wondrous moment: 
When you appeared before my sight, 
As though a brief and fleeting omen, 
Pure phantom in enchanting light.
超級方便!:) 這種能力出現在Java 7中。Stream API出現在Java 8中。它為Java增加了一些函數式編程的元素。包括更豐富的文件處理能力。想像一下,我們有以下任務:找到所有以單詞“As”開頭的行,將它們轉換為大寫,並將它們顯示在控制台上。在 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("As")) {
               String upper = s.toUpperCase();
               result.add(upper);
           }
       }

       for (String s: result) {
           System.out.println(s);
       }
   }
}
控制台輸出:

AS THOUGH A BRIEF AND FLEETING OMEN, 
PURE PHANTOM IN ENCHANTING LIGHT.
任務已完成,但您不認為對於這樣一個簡單的任務,我們的代碼竟然有點……冗長嗎?使用 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("As"))
               .map(String::toUpperCase)
               .collect(Collectors.toList());
       result.forEach(System.out::println);
   }
}
我們獲得了相同的結果,但代碼更少!更何況,沒有人能說我們失去了“可讀性”。我認為您可以輕鬆地評論此代碼的作用,即使不熟悉 Stream API 也是如此。簡而言之,Stream是一系列元素,您可以對其執行各種操作。我們從該Files.lines()方法中獲取一個 Stream 對象,然後對其應用 3 個函數:
  1. 我們使用該filter()方法僅從文件中選擇以“As”開頭的那些行。

  2. 我們使用該方法遍歷所有選定的行map(),並將它們中的每一行都轉換為大寫。

  3. 我們使用該collect()方法將所有接收到的行收集到一個List.

我們得到相同的輸出:

AS THOUGH A BRIEF AND FLEETING OMEN, 
PURE PHANTOM IN ENCHANTING LIGHT.
現在讓我們回到我們的麵包和黃油,即文件 :) 我們今天要考慮的最後一個功能是遍歷文件樹。在現代操作系統中,文件結構通常看起來像一棵樹:它有根和分支,分支可以有其他分支,等等。根和分支是目錄。例如,目錄“ С:// ”可能是根目錄。它包括兩個分支:“ C://Downloads ”和“ C://Users ”。這些分支中的每一個都有兩個分支:“ C://Downloads/Pictures ”、“ C://Downloads/Video ”、“ C://Users/JohnSmith ”、“ C://Users/Pudge2005””。而這些分支依次有其他分支等等,這就是我們稱其為樹的原因。在 Linux 上,結構類似,但 / 目錄是根目錄現在 文件,路徑 - 2假設我們需要從根目錄開始, 遍歷其所有文件夾和子文件夾,並找到具有某些特定內容的文件。我們將搜索包含“這是我們需要的文件!”這一行的文件,我們將使用“testFolder”文件夾,該文件夾位於桌面,作為根目錄。以下是它的內容: 文件,路徑 - 3level1-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("We found a file we need!");
               System.out.println(file.toAbsolutePath());
               break;
           }
       }

       return FileVisitResult.CONTINUE;
   }
}
在這種情況下,我們的類繼承了SimpleFileVisitor. 這是一個實現 的類FileVisitor,在其中我們只需要重寫一個方法:visitFile()。在這裡,我們定義需要對每個目錄中的每個文件執行的操作。如果您需要更複雜的邏輯來遍歷文件結構,您應該編寫自己的FileVisitor. 您需要在該類中再實現 3 個方法:
  • preVisitDirectory():進入文件夾前執行的邏輯;

  • visitFileFailed():如果無法訪問文件(無法訪問或出於其他原因)時執行的邏輯;

  • postVisitDirectory():進入文件夾後執行的邏輯。

我們不需要執行任何此類邏輯,所以我們可以使用SimpleFileVisitor. 該方法內部的邏輯visitFile()非常簡單:讀取文件中的所有行,檢查它們是否包含我們需要的內容,如果包含,則在控制台打印絕對路徑。唯一可能給您帶來困難的是這一行:

return FileVisitResult.CONTINUE;
其實,這很簡單。這裡我們只是簡單地描述在文件被訪問並執行了所有必要的操作之後程序應該做什麼。在我們的例子中,我們想繼續遍歷樹,所以我們選擇了這個CONTINUE選項。 但是,或者,我們可能有一個不同的目標:不是查找所有包含“This is the file we need”的文件,而是只查找一個這樣的文件。之後,程序應該終止。在這種情況下,我們的代碼看起來完全一樣,但不是 break 而是:

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());
   }
}
控制台輸出:

We found a file we need! 
C:\Users\Username\Desktop\testFolder\FileWeNeed1.txt 
We found a file we need! 
C:\Users\Username\Desktop\testFolder\level1-a\level2-a-a\FileWeNeed2.txt 
We found a file we need! 
C:\Users\Username\Desktop\testFolder\level1-b\level2-b-b\FileWeNeed3.txt
出色的!有效!:) 您也可以接受這個小挑戰:SimpleFileVisitor用一個普通的替換FileVisitor,重寫所有 4 個方法,並提出您自己的程序目的。例如,您可以編寫一個程序來記錄其所有操作:在輸入文件或文件夾之前或之後顯示它們的名稱。目前為止就這樣了。再見!:)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION