表示一個文件的 File 類型

從本篇文章開始,咱們將開啓對 Java IO 系統的學習,本質上就是對文件的讀寫操做,聽上去簡單,其實並不容易。Java 的 IO 系統一直在完善和改進,設計了大量的類,也只有理解了這些類型被設計出來的意義以及各自的應用場景,才能提高文件 IO 的理解。java

那麼,第一步就是要解決如何表示一個文件的問題,Java 世界中「萬物皆對象」,如何將一個實際磁盤文件或目錄對應到一個 Java 對象則是咱們首要的問題。git

Java 中使用 File 來抽象一個文件,不管是普通文件或是目錄,均可對應於一個 File 對象。我以爲你們對於 File 這個類型的定位必定要準確:它只是抽象的表明了磁盤上的某個文件或目錄,內部其實是依賴一個平臺無關的本地文件系統類,而且 File 沒法對其所表示文件內容進行任何讀寫操做(那是流作的事情)github

構建一個 File 實例

在實際介紹 File 實例構造方法以前,咱們得先看看它的幾種重要的屬性成員。bash

private static final FileSystem fs = DefaultFileSystem.getFileSystem();
複製代碼

這是 File 類中最核心的成員,它表示爲當前系統的文件系統 API,全部向磁盤發出的操做都是基於這個屬性的。微信

private final String path;
複製代碼

path 表明了當前實例的完整路徑名稱,若是當前的 File 實例表示的是目錄的話,那麼 path 的值就是這個完整的目錄名稱,若是表示的是純文件的話,那麼這個 path 的值等於該文件的完整路徑 + 文件名稱。ide

public static final char separatorChar = fs.getSeparator();
public static final char pathSeparatorChar = fs.getPathSeparator();
複製代碼

separatorChar 表示的是目錄間的分隔符,pathSeparatorChar 表示的是不一樣路徑下的分隔符,這兩個值在不一樣的系統平臺下不盡相同。例如 Windows 下這二者的值分別爲:「\」 和 「;」,其中封號用於分隔多個不一樣路徑。學習

File 類提供了四種不一樣的構造器用於實例化一個 File 對象,但較爲經常使用的只有三個,咱們也着重學習前三個構造器。ui

public File(String pathname)
複製代碼

這是最廣泛的實例化一個 File 對象的方法,pathname 的值能夠是一個目錄,也能夠是一個純文件的名稱。例如:this

File file = new File("C:\\Users\\yanga\\Desktop");

File file1 = new File("C:\\Users\\yanga\\Desktop\\a.txt");

File file2 = new File("a.txt");
複製代碼

固然也能夠顯式指定一個父路徑:spa

public File(String parent, String child)
複製代碼

在構造器的內部,程序會爲咱們拼接出一個完整的文件路徑,例如:

File file = new File("C:\\Users\\yanga\\Desktop","a.txt");

File file1 = new File("C:\\Users\\yanga\\Desktop","java");
複製代碼

第三種構造器其實本質上和第二種是同樣的,只不過增長了一個父類 File 實例的封裝過程:

public File(File parent, String child)
複製代碼

相似的狀況,再也不舉例說明了。咱們這裏並無深究這些構造器的內部具體實現狀況,並非說它簡單,而是 File 過分依賴本地文件系統,不少方法的實現狀況都不得直接看到,因此對於 File 的學習,定位爲熟練掌握便可,具體實現暫時無法深刻學習。

文件名稱或路徑相關信息獲取

getName 方法能夠用於獲取文件名稱:

public String getName() {
    int index = path.lastIndexOf(separatorChar);
    if (index < prefixLength) return path.substring(prefixLength);
    return path.substring(index + 1);
}
複製代碼

還記得咱們的 separatorChar 表示的是什麼了嗎?

它表示爲路徑分隔符,Windows 中爲符號「\」,path 屬性存儲的當前 File 實例的完整路徑名稱,因此最後一次出現的位置後面全部的字符必然是咱們的文件名稱。

固然你必定發現了,對於純文件來講,該方法可以返回文件的簡單名稱,而對於一個目錄而言,返回值將會是最近的目錄名。例如:

File file = new File("C:\\Users\\yanga\\Desktop\\a.txt");
System.out.println(file.getName());

File file1 = new File("C:\\Users\\yanga\\Desktop");
System.out.println(file1.getName());
複製代碼

輸出結果不會出乎你的意料:

a.txt
Desktop
複製代碼

getParent 方法用於返回當前文件的父級目錄,不管你是純文件或是目錄,你終有你的父目錄(固然,虛擬機生成的臨時文件天然不是)。

public String getParent() {
    int index = path.lastIndexOf(separatorChar);
    if (index < prefixLength) {
        if ((prefixLength > 0) && (path.length() > prefixLength))
            return path.substring(0, prefixLength);
        return null;
    }
    return path.substring(0, index);
}
複製代碼

方法的實現很簡單,再也不贅述。

getPath 方法能夠返回當前 File 實例的完整文件名稱:

public String getPath() {
    return path;
}
複製代碼

如下是一些有關目錄的相關操做,實現比較簡單,此處簡單羅列了:

  • public boolean isAbsolute():是否爲絕對路徑
  • public String getAbsolutePath():獲取當前 File 實例的絕對路徑
  • public String getCanonicalPath():返回當前 File 實例的標準路徑

這裏咱們須要對 getCanonicalPath 作一點解釋,什麼叫標準路徑,和絕對路徑有區別嗎?

通常而言,「../」表示源文件所在目錄的上一級目錄,「../../」表示源文件所在目錄的上上級目錄,並以此類推。getAbsolutePath 方法不會作這種轉換的操做,而 getCanonicalPath 方法則會將這些特殊字符進行識別並取合適的語義。

例如:

File file = new File("..\\a.txt");
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
複製代碼

輸出結果:

C:\Users\yanga\Desktop\Java\workspace2017\TestFile\..\a.txt
C:\Users\yanga\Desktop\Java\workspace2017\a.txt
複製代碼

前者會將「..\a.txt」做爲文件路徑名稱的一部分,然後者卻可以識別「..\a.txt」表示的是「a.txt」位於當前目錄的上級目錄中。這就是二者最大的不一樣之處,適合不一樣的情境。

文件的屬性信息獲取

這部分的文件操做其實很簡單,無非是一些文件權限的問題,是否可讀,是否可寫,是否爲隱藏文件等。下面咱們具體看看這些方法:

  • public boolean canRead():該抽象的 File 實例對應的文件是否可讀
  • public boolean canWrite():該抽象的 File 實例對應的文件是否可寫
  • public boolean exists():該抽象的 File 實例對應的文件是否實際存在
  • public boolean isDirectory():該抽象的 File 實例對應的文件是不是一個目錄
  • public boolean isFile():該抽象的 File 實例對應的文件是不是一個純文件
  • public boolean isHidden():該抽象的 File 實例對應的文件是不是一個隱藏文件
  • public long length():文件內容所佔的字節數

須要說明一點的是,length 方法對於純文件來講,能夠正確返回該文件的字節總數,可是對於一個目錄而言,返回值將會是一個「unspecified」的數值,既不是目錄下全部文件的總字節數,也不是零,只是一個未被說明的數值,沒有意義。

文件的操做

文件的操做無外乎「增刪改查」,下面咱們一塊兒來看看。

  • public boolean createNewFile():根據抽象的 File 對象建立一個實際存在的磁盤文件
  • public boolean delete():刪除該 File 對象對應的磁盤文件,刪除失敗會返回 false

固然,處理上述兩個簡單的新建和刪除操做,File 類還提供了所謂「查詢」操做,這個咱們要好好學習一下。例如:

public String[] list() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkRead(path);
    }
    if (isInvalid()) {
        return null;
    }
    return fs.list(this);
}
複製代碼

這個方法會檢索出當前實例所表明的目錄下全部的「純文件」和「目錄」簡單名稱集合。例如:

File file = new File("C:\\Users\\yanga\\Desktop");
String[] list = file.list();
for (String str : list){
    System.out.println(str);
}
複製代碼

程序的輸出結果會打印我電腦桌面目錄下全部的文件的簡單名稱,就不給你們看了。

須要注意一點,若是咱們的 File 實例對應的不是一個目錄,而是一個純文件,那麼 list 將返回 null。

接着,咱們再看一個檢索目錄文件的方法:

public String[] list(FilenameFilter filter) {
    String names[] = list();
    if ((names == null) || (filter == null)) {
        return names;
    }
    List<String> v = new ArrayList<>();
    for (int i = 0 ; i < names.length ; i++) {
        if (filter.accept(this, names[i])) {
            v.add(names[i]);
        }
    }
    return v.toArray(new String[v.size()]);
}
複製代碼

這個方法實際上是 list 的重載版本,它容許傳入一個過濾器用於檢索目錄時只篩選咱們須要的文件及目錄。

而這個 FilenameFilter 接口的定義倒是如此簡單:

public interface FilenameFilter {
    boolean accept(File dir, String name);
}
複製代碼

只須要重寫這個 accept 方法便可,list 的 for 循環每獲取一個文件或目錄就會嘗試着先調用這個過濾方法,若是經過篩選,纔會將當前文件的簡單名稱添加進返回集合中。

因此這個 accept 方法的重寫就決定着哪些文件可以經過篩選,哪些則不能。咱們看個例子:

個人桌面上 test 文件夾下文件狀況以下:

image

File file = new File("C:\\Users\\yanga\\Desktop\\test");
    String[] list = file.list(
        new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                // dir 表明的當前 File 對象
                //name 是當前遍歷的文件項的簡單名稱
                if (!name.endsWith(".txt"))
                    return false;
                else
                    return true;
            }
        }
    );
    for (String str : list){
        System.out.println(str);
    }
複製代碼

這裏呢,咱們使用匿名內部類建立一個 FilenameFilter 的子類實例,而後實現了它的 accept 方法,具體的實現很簡單,過濾掉全部的目錄並取出全部純文件的簡單名稱。

最後輸出結果以下:

3.txt
4.txt
複製代碼

固然,File 類中還提供了兩個「變種」list 方法,例如:

  • public File[] listFiles()
  • public File[] listFiles(FilenameFilter filter)

它們再也不返回目標目錄下的「純文件」和「目錄」的簡單名稱,而返回它們所對應的 File 對象,其實也沒什麼,目標目錄 + 簡單名稱 便可構建出這些 File 實例了。

因此,本質上說,list 方法並不會遍歷出目標目錄下的全部文件,即目標目錄的子目錄中的文件並不會被訪問遍歷。

因此你應當思考如何完成目標目錄下全部文件的遍歷,包含一級子目錄下的深層次文件的遍歷。文末將給出答案。

接下來的兩個方法和文件夾的建立有關:

  • public boolean mkdir()
  • public boolean mkdirs()

二者都是依據的當前 File 實例建立文件夾,關於它們的不一樣點,咱們先看一段代碼:

File file = new File("C:\\Users\\yanga\\Desktop\\test2");
System.out.println(file.mkdir());

File file2 = new File("C:\\Users\\yanga\\Desktop\\test3\\hello");
System.out.println(file2.mkdir());
複製代碼

其中,test2 和 test3 在程序執行以前都不存在。

輸出結果以下:

true
false
複製代碼

爲何後者建立失敗了?

這源於 mkdir 方法一次只能建立一個文件夾,假若給定的目錄的父級或更上層目錄存在未被建立的目錄,那麼將致使建立失敗。

而 mkdirs 方法就是用於解決這種情境的,它會建立目標路徑上全部未建立的目錄,看代碼:

File file3 = new File("C:\\Users\\yanga\\Desktop\\test3\\hello\\231");
System.out.println(file3.mkdirs());
複製代碼

即使咱們 test3 文件夾就不存在,程序運行以後,test三、hello、231 這三個文件夾都會被建立出來。

除此以外,File 還有一類建立臨時文件的方法,所謂臨時文件即:運行期存在,虛擬機關閉時銷燬。你們能夠自行研究,使用上仍是比較簡單的,這裏再也不贅述了。

至此,有關 File 這個文件類型,咱們大體學習了一下,想必你們都會或多或少的感受到將純文件和目錄使用同一個類型進行表示的設計彷佛有些混亂不合理。知道 jdk1.7 sun 推出了 Files 和 Path 分離了文件和目錄,咱們後續文章會詳細學習一下。


文章中的全部代碼、圖片、文件都雲存儲在個人 GitHub 上:

(https://github.com/SingleYam/overview_java)

歡迎關注微信公衆號:撲在代碼上的高爾基,全部文章都將同步在公衆號上。

image
相關文章
相關標籤/搜索