本篇教學文章示範如何使用Java不透過第三方套件來判斷檔案的副檔名,以及檔案類型,只需要Java 7的NIO就可以達成,再加上Java 8的Optional與Stream API來更優雅的處理。使用原生的API可以避免一些問題,例如執行時還需要第三方套件、第三方套件還包含太多用不上的API徒增檔案大小、第三方套件不更新可能有漏洞等麻煩。

取得副檔名

副檔名是檔案名稱中最後一個.後面的文字,通常是英文與數字,例如.jpg.mp3.txt等。雖然使用org.apache.commons.io.FilenameUtils可以找到副檔名,但我們目標是你複製以下程式碼就可以直接使用,不用再安裝第三方套件。

找出.的位置

這邊只透過Java的String內建的方法來尋找副檔名,效率也不錯。

// 副檔名預設為空字串
String extension = "";
// 我們要判斷的檔案名稱
String filename = "klab.tw.txt";
// 尋找最後一個.的位置,沒找到會回傳 -1
int idx = filename.lastIndexOf(".");

if(idx >= 0) {
    // 使用substring取得副檔名,會得到 txt
    extension = filename.substring(idx + 1);
}

使用Optional寫成完整版

Java 8開始增加一系列Stream API,我們重寫然後寫成一個Class,可以直接呼叫使用。

import java.util.Optional;

/**
 * Create by https://klab.tw
 */
public class FileTools {
    /**
     * 從檔案名稱判斷副檔名。
     * @param filename 檔案名稱
     * @param orElse 找不到的時候返回的預設值,可為null
     * @return 副檔名
     */
    public static String getExtension(String filename, String orElse) {
        return Optional.ofNullable(filename)
            .filter(s -> s.contains("."))
            .map(s -> s.substring(s.lastIndexOf(".") + 1))
            .orElse(orElse);
    }
}

使用方法如下。

FileTools.getExtension("klab.tw.txt", "");
// 返回:txt

取得檔案類型

在這邊我們使用Java 7開始內建的NIO API,使用Files直接透過副檔名判斷檔案類型。雖然Windows檔案總管預設會隱藏副檔名,但副檔名是人為決定的,而且很好修改,不能作為判斷檔案類型的唯一方式,但還是可以作為參考。

使用Path與Files

使用java.nio.file.Path建立Path物件,然後透過java.nio.file.Files內建的方法即可判斷。找不到的時候會回傳null,而且需要處理IOException。

String filename = "klab.tw.txt";
// Java 7~10使用 Paths.get(filename),Java 11之後可改寫為 Path.of(filename)
Path path = Paths.get(filename);
String mime = Files.probeContentType(path);

Paths.get 與 Path.of 的差別

這兩個方法功能完全一樣,都是把字串轉成Path物件,差別只在於放在哪個Class上。Paths.get()是Java 7與NIO一起推出的工具類別方法,當時Path本身是介面,沒辦法放static factory method。而Path.of()是Java 11之後加上去的,因為Java 8已經允許介面有static method,因此官方把工廠方法直接搬到Path介面本身。

從Java 11開始,Paths.get()內部其實只是呼叫Path.of(),兩個是等價的。新寫的程式建議使用Path.of(),跟List.of()Set.of()Map.of()這些Java 9之後的工廠方法風格一致,也少import一個Paths類別。但是如果還在維護Java 8的舊系統,就只能繼續使用Paths.get()

方法所在Class引入版本
Paths.get()java.nio.file.PathsJava 7
Path.of()java.nio.file.PathJava 11

跨平台的注意事項

另外要注意Files.probeContentType實際運作方式跟作業系統有關,Linux會讀取/etc/mime.types、macOS有自己的對應表,而Windows在某些JDK版本上對應較少,常會回傳null。如果需要跨平台一致的結果,建議自己維護副檔名對應表。

使用Optional寫成完整版

以下搭配Java 8的Stream API,寫可以直接呼叫使用、不用管IOException的完整版。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;

/**
 * Create by https://klab.tw
 */
public class FileTools {
    /**
     * 透過檔案副檔名判斷檔案類型。
     * @param filename 檔案名稱
     * @param orElse 找不到的時候返回的預設值,可為null
     * @return 檔案類型
     */
    public static String getContentType(String filename, String orElse) {
        return Optional.ofNullable(filename)
            .map(s -> Paths.get(s))
            .map(p -> {
                try {
                    return Files.probeContentType(p);
                } catch (IOException e) {
                    return null;
                }
            })
            .orElse(orElse);
    }
}

使用方法如下。

FileTools.getContentType("klab.tw.txt", "");
// 返回:text/plain