首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 文件元數據獲取完整指南:大小、時間、權限、規範路徑與日期格式化

     編輯:Java 相關知識

掌握文件元數據讀取是文件管理、同步備份、審計日誌、安全掃描等系統的基礎能力。本文基於 java.io.File 與 java.nio.file 雙 API 對比,詳細講解文件大小、最後修改時間、讀寫執行權限、隱藏屬性、規範絕對路徑的獲取方法,並提供線程安全的日期格式化工具。

核心 API 對比表

元數據java.io.File (舊)java.nio.file.Files (新,推薦)
文件大小file.length() → long (bytes)Files.size(path) → long
最後修改時間file.lastModified() → long (ms since epoch)Files.getLastModifiedTime(path) → FileTime
可讀性file.canRead()Files.isReadable(path)
可寫性file.canWrite()Files.isWritable(path)
可執行性file.canExecute() (JDK 6+)Files.isExecutable(path)
隱藏屬性file.isHidden()Files.isHidden(path) (JDK 11+)
規範路徑file.getCanonicalPath()path.toRealPath()
是否目錄file.isDirectory()Files.isDirectory(path)
是否文件file.isFile()Files.isRegularFile(path)
是否符號鏈接不支援Files.isSymbolicLink(path)
POSIX 權限不支援Files.getPosixFilePermissions(path)
ACL 權限不支援Files.getFileAttributeView(path, AclFileAttributeView.class)

生產級工具類:FileMetadata


package com.example.util;

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;

public class FileMetadata {

    private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
            .withZone(ZoneId.systemDefault());

    /** 文件信息數據類 */
    public static class Info {
        public final String name;
        public final String canonicalPath;
        public final long sizeBytes;
        public final String sizeHuman;
        public final String lastModified;
        public final boolean readable;
        public final boolean writable;
        public final boolean executable;
        public final boolean hidden;
        public final boolean isDirectory;
        public final boolean isSymbolicLink;
        public final Set posixPermissions; // null if not POSIX

        public Info(String name, String canonicalPath, long sizeBytes, String sizeHuman,
                    String lastModified, boolean readable, boolean writable, boolean executable,
                    boolean hidden, boolean isDirectory, boolean isSymbolicLink,
                    Set posixPermissions) {
            this.name = name;
            this.canonicalPath = canonicalPath;
            this.sizeBytes = sizeBytes;
            this.sizeHuman = sizeHuman;
            this.lastModified = lastModified;
            this.readable = readable;
            this.writable = writable;
            this.executable = executable;
            this.hidden = hidden;
            this.isDirectory = isDirectory;
            this.isSymbolicLink = isSymbolicLink;
            this.posixPermissions = posixPermissions;
        }

        @Override public String toString() {
            return String.format("FileInfo{name='%s', path='%s', size=%s (%d bytes), modified=%s, rwx=%s%s%s, hidden=%s, dir=%s, link=%s, posix=%s}",
                    name, canonicalPath, sizeHuman, sizeBytes, lastModified,
                    readable ? 'r' : '-', writable ? 'w' : '-', executable ? 'x' : '-',
                    hidden, isDirectory, isSymbolicLink, posixPermissions);
        }
    }

    /**
     * 獲取文件完整元數據
     * @throws IOException 文件不存在或訪問失敗
     */
    public static Info getInfo(String filePath) throws IOException {
        Path path = Paths.get(filePath);
        if (!Files.exists(path)) throw new FileNotFoundException(filePath);

        // 基礎屬性
        BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
        long size = attrs.size();
        String sizeHuman = humanReadableSize(size);
        String modified = FMT.format(Instant.ofEpochMilli(attrs.lastModifiedTime().toMillis()));

        // 權限檢查
        boolean readable = Files.isReadable(path);
        boolean writable = Files.isWritable(path);
        boolean executable = Files.isExecutable(path);
        boolean hidden = Files.isHidden(path);

        // 類型
        boolean isDir = attrs.isDirectory();
        boolean isLink = Files.isSymbolicLink(path);

        // 規範路徑
        String canonical = path.toRealPath().toString();

        // POSIX 權限(Linux/Unix)
        Set posixPerms = null;
        if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
            try {
                posixPerms = Files.getPosixFilePermissions(path);
            } catch (UnsupportedOperationException | IOException ignored) {}
        }

        return new Info(path.getFileName().toString(), canonical, size, sizeHuman, modified,
                readable, writable, executable, hidden, isDir, isLink, posixPerms);
    }

    /** 字節數人性化格式化 */
    public static String humanReadableSize(long bytes) {
        if (bytes < 1024) return bytes + " B";
        int exp = (int) (Math.log(bytes) / Math.log(1024));
        String unit = "KMGTPE".charAt(exp - 1) + "iB";
        return String.format("%.2f %s", bytes / Math.pow(1024, exp), unit);
    }

    /** 批量獲取目錄下所有文件信息 */
    public static List getDirectoryInfo(String dirPath) throws IOException {
        List list = new ArrayList<>();
        try (Stream stream = Files.list(Paths.get(dirPath))) {
            stream.forEach(p -> {
                try {
                    list.add(getInfo(p.toString()));
                } catch (IOException e) {
                    System.err.println("Failed to read: " + p + " - " + e.getMessage());
                }
            });
        }
        return list;
    }
}

日期時間處理最佳實踐

舊代碼常見問題


// 問題:SimpleDateFormat 非線程安全,Calendar 麻煩
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String s = sdf.format(new Date(file.lastModified()));

現代解決方案:java.time (JDK 8+)


// 線程安全、不可變、鏈式 API
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
        .withZone(ZoneId.systemDefault()); // 或指定 ZoneId.of("Asia/Shanghai")

String formatted = fmt.format(Instant.ofEpochMilli(file.lastModified()));
// 或 FileTime
String formatted2 = fmt.format(Files.getLastModifiedTime(path).toInstant());

格式化模式對照

模式字母含義示例
yyyy年2024
MM月(01-12)01
dd日(01-31)15
HH時(00-23)14
mm分30
ss秒05
SSS毫秒123
z時區名CST
XISO 8601 時區偏移+08:00

進階:POSIX 權限解析與修改


// 讀取 rwx 字符串
Set perms = Files.getPosixFilePermissions(path);
String rwx = PosixFilePermissions.toString(perms); // e.g. "rw-r--r--"

// 修改權限:添加組寫權限
Set newPerms = new HashSet<>(perms);
newPerms.add(PosixFilePermission.GROUP_WRITE);
Files.setPosixFilePermissions(path, newPerms);

// 從字符串解析
Set parsed = PosixFilePermissions.fromString("rwxr-xr-x");
Files.setPosixFilePermissions(path, parsed);

命令行測試工具


public class FileInfoTool {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.err.println("Usage: FileInfoTool  [file2...]");
            System.exit(1);
        }
        for (String arg : args) {
            try {
                if (Files.isDirectory(Paths.get(arg))) {
                    FileMetadata.getDirectoryInfo(arg).forEach(System.out::println);
                } else {
                    System.out.println(FileMetadata.getInfo(arg));
                }
            } catch (IOException e) {
                System.err.println(arg + ": " + e.getMessage());
            }
        }
    }
}

常見坑點避雷

  1. canRead/canWrite 對超級用戶總返回 true:Linux root 進程權限檢查失效,需結合 POSIX 權限位判斷。
  2. lastModified 精度受文件系統限制:FAT32 2秒、ext4 奈秒、NTFS 100奈秒,跨 FS 複製會丟失精度。
  3. getCanonicalPath() 拋出 IOException:舊 API 必須 try-catch,NIO toRealPath() 同理。
  4. Windows 隱藏屬性判斷:Files.isHidden() JDK 11+ 才可靠,舊版需 JNI 或命令行 attrib。
  5. 符號鏈接循環導致 walk 死循環:使用 Files.walkFileTree(..., FOLLOW_LINKS) 時需自行檢測循環或限制深度。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved