Java> 文件操作(Files, Path, File)


概要

Files: 操作文件的工具類,Java7加入,封裝了用戶機器上處理文件系統所需所有功能。包含了文件創建、復制、寫入、讀出、刪除,獲取文件信息,快捷訪問、遍歷目錄等功能。使用較File更方便,由於結合了Path/Stream 等類,很擅長批量處理文件。
Path: 表示文件路徑,Java7加入,常用Paths創建,配合Files使用。
File: 傳統文件類,Java 1.0加入,功能強大,但使用繁瑣。

Path類

Path通過表示一個目錄名序列,后面還可以跟着一個文件名,來表示路徑。

創建方式

  • 通過指定路徑字符串 Paths.get()
    通過Paths.get() 拼接多個字符串,組成路徑。
    包含2類:1)絕對路徑;2)相對路徑。
    路徑不必是一個存在的文件,僅僅只是一個抽象的名字序列。只有當要創建文件的時候,才會調用方法根據路徑創建文件。
Path absolute = Paths.get("C:\\Users", "test.txt"); // 絕對路徑, 使用Windows風格路徑分隔符  
Path relative = Paths.get("config", "properties", "user.properties"); // 相對路徑
  • 通過已有Path + 字符串組合 Path.resolve()和Path.resolveSibling()
// resolve path
Path basePath = Paths.get("rss"); // 通過字符串獲取路徑
Path resolvePath = basePath.resolve("resolvePath"); // 組合basePath和"resolvePath"得到新路徑
Path resolveSibling = basePath.resolveSibling("resolveSibling"); // 得到basePath兄弟路徑"resolveSibling"
// 打印轉換path
System.out.println("basePath = " + basePath.toAbsolutePath());
System.out.println("resolvePath = " + resolvePath.toAbsolutePath());
System.out.println("resolveSibling = " + resolveSibling.toAbsolutePath());
  • 產生相對路徑relativize
    relativize是resolve逆操作。
    p.resolve(r)結果產生路徑q = "p/r";p.relative(q)產生r,即r="../q"。簡單來說,就是resolve是利用母路徑path+字符串(作為子路徑),組合成新路徑;relativize是通過母路徑 - 組合的新路徑,得到相對路徑。
// relative path
System.out.println(basePath.relativize(resolvePath));
  • 打印轉換path和relative path運行結果
basePath = F:\workspace\IDEA\Java_Core2\rss
resolvePath = F:\workspace\IDEA\Java_Core2\rss\resolvePath
resolveSibling = F:\workspace\IDEA\Java_Core2\resolveSibling
resolvePath
  • 其他常用操作
    normalize 移除所有冗余.和..部件
    toAbsolutePath 產生給定路徑的絕對路徑
    getParent 獲取父路徑
    getFileName 獲取文件名
    getRoot 獲取根目錄,Unix是 / , Windows是所在盤符根目錄
    toFile 轉換成File類對象

通過Path構建Scanner對象

Scanner in = new Scanner(Paths.get("C:\\Users\test.txt"));

Files類

創建文件

  • 創建目錄
    如果目錄已經存在會拋出異常FileAlreadyExistsException. 創建目錄是原子性的
Path path = Paths.get("dir");
Files.createDirectory(path); // 創建以path為路徑的目錄
  • 創建文件
    如果文件已經存在會拋出異常FileAlreadyExistsException. 創建文件是原子性的
Path path = Paths.get("file");
Files.createDirectory(pat); // 創建以path為路徑的文件, 文件可以與目錄路徑及同名 
  • 在給定位置或者系統指定位置,創建臨時文件/目錄
Path newPath = Files.createTempFile(dir, prefix, suffix); // dir路徑下, 創建以prefix為前綴, suffix為后綴的名稱的文件
Path newPath = Files.createTempFile(prefix, suffix); // 系統默認臨時目錄路徑下, 創建以prefix為前綴, suffix為后綴的名稱的文件
Path newPath = Files.createTempDirectory(dir, prefix); // dir路徑下, 創建以prefix為前綴, suffix為后綴的名稱的目錄
Path newPath = Files.createTempDirecotry(prefix); // 系統默認臨時目錄路徑下, 創建以prefix為前綴, suffix為后綴的名稱的目錄

dir是一個Path對象,給定創建臨時文件路徑;
prefix,suffix可以為null字符串,分別指定文件名前綴、后綴;
系統默認臨時文件夾路徑,Win10x64:C:\Users\Martin\AppData\Local\Temp

讀寫文件

  • 讀取/寫中小文件
/* 一次讀取所有文件內容 */
// 一次按二進制讀取所有文件內容
byte[] bytes = Files.readAllBytes(path); // 文件路徑Path -> 二進制數組byte[]
// 將bytes轉換成字符串
String content = new String(bytes, charset); // charset指定字符編碼, 如StandardCharsets.UTF_8

// 一次按行讀取文件所有內容
List<String> lines = Files.readAllLines(path);

/* 一次寫所有文件內容 */
// 寫一個字符串到文件
Files.write(path, content.getBytes(charset)); 
// 追加字符串到文件
Files.write(path, content.getBytes(charset),StandardOpenOption.APPEND);
// 寫一個行的集合到文件
Files.write(path, lines);
  • 大文件
    要處理大文件和二進制文件,需要用到輸入流/輸出流,或者使用讀入器/寫入器。
InputStream in = Files.newInputStream(path);
OutputStream out = Files.newOutputStream();
Reader reader = Files.newBufferedReader(path, charset);
Writer writer = Writer.newBufferedWriter(path, charset);

上面這些方法較單純使用FileInputStream, FileOutputStream, BufferedReader, BufferedWriter更為簡便。
例如,如果使用FileInputStream和FileOutputStream,需要這樣使用

// read data from stream
try(DataInputStream in = new DataInputStream(new FileInputStream("filename"))) {
      ...
}

// write data to stream
try(DataOutputStream out = new DataOutputStream(new FileOutputStream("filename"))) {
      ...
}

簡單來說,就是少了專門new的語句;對於Reader/Writer,還少了包裝的語句。

復制文件

從一個位置復制到另外一個位置

Files.copy(fromPath, toPath); // fromPath和toPath都是Path對象, 如果目標路徑已存在文件, 復制失敗
Files.copy(fromPath, toPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); // 選項REPLACE_EXISTING表示想覆蓋原有目標路徑, COPY_ATTRIBUTES表示復制所有文件屬性
Files.copy(inputStream, toPath); // 從輸入流復制到目標路徑
Files.copy(fromPath, outputStream); // 從源路徑復制到輸出流

移動文件

從一個位置移動到另外一個位置

Files.move(fromPath, toPath); // fromPath和toPath都是Path對象
Files.move(fromPath, toPath, StandardCopyOption.ATOMIC_MOVE); // ATOMIC_MOVE表示該操作是原子性的(要么成功移動到目標路徑, 要么失敗文件還在原來的位置)
// move操作無法從輸入流到目標路徑, 或者從源路徑到輸出流

刪除文件

刪除指定路徑文件

Files.delete(path); // 如果指定路徑不存在, 報異常NoSuchFileException
boolean deleted = Files.deleteIfExist(path); // 如果文件存在, 才會刪除, 不會報異常. 可以用來刪除空目錄

獲取文件信息

常用操作

boolean exists(path) // 文件存在?
boolean isHidden(path) // 文件隱藏?
boolean isReadable(path) // 文件可讀?
boolean isWritable(path) // 文件可寫?
boolean isExecutable(path) // 可執行?
boolean isRegularFile(path) // 是普通文件? 等價於!isSymbolicLink() && !isDirectory() && !isOther()
boolean isDirectory(path) // 是目錄?
boolean isSymbolicLink(path) // 是符號鏈接? 

long fileSize = Files.size(path); // 獲取文件字節數

獲取基本文件屬性集
基本文件屬性集主要包括:

  1. 創建文件、最后一次訪問以及最后一次修改時間;
  2. 文件是常規文件、目錄,還是符號鏈接;
  3. 文件尺寸;
  4. 文件主鍵,具體所屬類與文件系統相關,有可能是文件唯一標識符,有可能不是;
BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);

PosixiFileAttributes posixAttributes = Files.readAttributes(path, PosixiFileAttributes.class); // 如果文件系統兼容POSIX, 才能獲取到PosixiFileAttributes 實例

訪問目錄各項

  • 遍歷指定目錄下各項
    Files.list會返回Stream ,而且是惰性讀取,處理目錄具有大量項時高效。不過,list不會進入子目錄,進入子目錄使用walk。使用示例
try(Stream<Path> entries = Files.list(dirPath)) { // 讀取目錄涉及需要關閉系統資源, 使用try塊. 不進入子目錄
      entries.forEach(System.out::println); // 打印每個entries項, 也就是打印每個path
}

try(Stream<Path> entries = Files.walk(dirPath)) { // 會進入子目錄
      entries.forEach(System.out.println);
}
  • 遍歷並刪除指定目錄下各項
    使用Files.walk遍歷 得到Stream ,再利用Stream的forEach方法對各項進行處理
/* 示例將當前目錄rss下所有文件(包括目錄)及子文件, 都復制到目錄rss2下 */
Path source = Paths.get("rss"); // 根據實際情況設置字節的source路徑
Path target = Paths.get("rss2");

try(Stream<Path> entries = Files.walk(source)) {
      entries.forEach( p-> {
            try{
                  Path q = target.resolve(source.relative(p)); // 取得p相對於source的相對路徑后, 再拼接到target路徑下. 相當於是說, 將每個文件相對路徑都由source轉移到target下
                  if(!Files.exists(q)) {
                        if(Files.isDirectory(q)) Files.createDirectory(q); // 如果是目錄, 在target路徑下, 根據相對路徑創建對應目錄
                        else Files.copy(p, q);  // 如果是文件, 從source路徑復制到target下
                  }
            } catch(IOException e) {
                  e.printStackTrace();
            }
      });
}

目錄流

使用Files.walk有一個缺陷:無法方便地刪除目錄,因為要刪除父目錄,必須先刪除子目錄。否則,會拋出異常。
使用File.newDirectoryStream對象,產生一個DirectoryStream,對遍歷過程可以進行更細粒度控制。DirectoryStream不是Stream,而是專門用於目錄遍歷的接口。它是Iterable的子接口,可以用Iterable的迭代和增強forEach方法。
還可以搭配glob模式來過濾文件,示例是過濾出dir目錄下 后綴名為 .java的文件:

try(DirectoryStream<Path> entries = Files.newDirectoryStream(dir, "*.java")){
      for (Path entry: entries) {
            Process entry
      }
}

glob模式

模式 描述 示例
* 匹配路徑組成部分中0個或多個字符串 *.java 匹配當前目錄中的所有java文件
** 匹配跨目錄邊界的0個或多個字符串 **.java 匹配在所有子目錄中的java文件
? 匹配一個字符 ????.java 匹配所有4個字符的java文件(不含擴展名)
[...] 匹配一個字符集合, 可以使用連線字符[0-9]和取反字符[!0-9] Test[0-9A-F].java 匹配Testx.java, 其中x是一個十六進制數
{...} 匹配由逗號隔開的多個可選項之一 *.{java,class} 匹配所有的java文件和類class文件
\ 轉義任意模式中的字符以及\字符 *\** 匹配所有文件名中包含*的文件

注意:如果使用Windows,必須對glob的反斜杠轉義兩次:一次是glob語法轉義,另外一次是java字符串轉義:Files.newDirectoryStream(dir, "C:\\\\") // 相當於C:\\

訪問目錄所有子孫

如果想要訪問某個目錄下所有子孫,可以使用walkFileTree(),並向其傳遞一個FileVisitor對象。這個方法並非簡單遍歷,而是在遇到文件或目錄時,目錄被處理前后,訪問文件錯誤時,FileVisitor會收到通知,然后指定執行方式:跳過該文件、跳過目錄、跳過兄弟文件、終止訪問。

// walkFileTree得到的通知:
FileVisitResult visitFile()  // 遇到文件或目錄時
FileVisitResult preVisitDirectory() // 一個目錄被處理前
FileVisitResult postVisitDirectory() // 一個目錄被處理后
FileVisitResult visitFileFailed() // 試圖訪問文件失敗, 或目錄發生錯誤時

// 收到通知后, 可以設置指定的操作
FileVisitResult.CONTINURE // 繼續訪問下一個文件
FileVisitResult.SKIP_SUBTREE // 繼續訪問, 但不再訪問這個目錄下任何文件
FileVisitResult.SKIP_SIBLINGS // 繼續訪問, 但不再訪問這個文件的兄弟文件(同一個目錄下的文件)
FileVisitResult.TERMINATE // 終止訪問

便捷類SimpleFileVisitor + Files.walkFileTree()可以實現對目錄的細粒度訪問,並在在收到相關通知時,有機會進行相應處理。默認SimpleFileVisitor類實現FileVisitor接口,除visitFileFailed() 外(拋出異常並終止訪問),其余方法都是直接繼續訪問,而不做任何處理。
注意:preVisitDirectory()和postVisitDirectory()通常需要覆蓋,否則,訪問時遇到不允許打開的目錄或者不允許訪問的文件時立即失敗,進而直接跳轉到visitFileFailed()

示例,展示如何打印給定目錄下的所有子目錄:

Files.walkFileTree(Paths.get("F:\\test"), new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        System.out.println(dir);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        System.out.println("postVisitDirectory " + dir);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
        return FileVisitResult.SKIP_SUBTREE;
    }
});

執行結果:

F:\test
F:\test\dir1
F:\test\dir1\subdir1
postVisitDirectory F:\test\dir1\subdir1
postVisitDirectory F:\test\dir1
F:\test\dir2
postVisitDirectory F:\test\dir2
F:\test\dir3
postVisitDirectory F:\test\dir3
postVisitDirectory F:\test

示例2,刪除目錄樹(包括其中的文件)
利用walkFileTree訪問到對應路徑目錄時,利用便捷類SimpleFileVisitor 的preVisitDirectory先刪除當前目錄下的所有文件,然后訪問完畢后,在postVisitDirectory刪除當前訪問完畢的目錄。

Files.walkFileTree(Paths.get("F:\\test"), new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        System.out.println(dir);

        // 刪除dir路徑下所有文件(不包含子目錄)
        Files.list(dir).forEach(p->{
            try {
                if (!Files.isDirectory(p))
                    Files.delete(p);

            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        return FileVisitResult.CONTINUE;
    }

    // 刪除目錄
    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        System.out.println("postVisitDirectory " + dir);
        if (null != exc) throw exc;
        Files.delete(dir);
        return FileVisitResult.CONTINUE;
    }
} );

File類

創建文件

通過路徑字符串創建

File file = new File("filePath");  // 在當前路徑創建名為"filePath"的文件, 此時磁盤上還沒有創建對應文件或目錄

// 磁盤上創建文件或目錄
File tempFile = File.createTempFile(prefix, suffix, directory);  // 在指定目錄或當前目錄(directory缺省),創建以prefix/suffix為前綴/后綴的臨時文件
file.createNewFile(); // 以file所代表路徑, 創建新文件
file.mkdir(); // 以file所代表路徑, 創建新目錄, 注意mkdir()和createNewFile() 不能同時用同一個file所代表路徑創建同名文件/目錄對象, 否則后者無法創建

讀寫文件

單純File類是沒有辦法直接讀寫文件的,這點跟Files是一個明顯區別,File類需要借助輸入流/輸出流(FileInputStream/FileOutputStream),或者讀入器/寫出器(Reader/Writer)來實現讀寫功能。
示例是用DataInputStream和DataOutoutStream進行文件讀寫包裝,支持字符本文以及二進制數據。使用Reader和Writer的方式,這里省略。感興趣可參考系統學習 Java IO (十三)----字符讀寫 Reader/Writer 及其常用子類

// 寫數據到文件file
try(DataOutputStream out = new DataOutputStream(new FileOutputStream((file)))) {
    out.writeInt(1);
    out.writeChar('a');
}

//從文件file讀數據
try(DataInputStream in = new DataInputStream(new FileInputStream(file))) {
    // read數據類型的個數和順序一定要和write一致
    int a = in.readInt();
    char c = in.readChar();

    System.out.println(a);
    System.out.println(c);
}

復制和移動

復制和移動文件,File類沒有專門的API,需要自行實現。這里只舉一個復制的例子,移動可以看成是 復制+刪除源文件。

/**
* 利用File類復制文件(包括目錄)
* @note 要求文件只能是普通文件或者目錄
*/
public static File copyFile(String dest, String src) throws IOException {
    File destFile = new File(dest);
    File srcFile = new File(src);
    
    // 源文件不存在, 無法復制
    if (!srcFile.exists()) {
        System.out.println("源文件不存在, 路徑: " + srcFile);
        return null;
    }
    
    // 根據源文件類型, 在目標路徑新建文件或目錄
    if (srcFile.isDirectory()) {
        destFile.mkdir();
        return destFile;
    }
    else srcFile.createNewFile();

    // 處理源文件為文件(非目錄)的情形
    try(Scanner scanner = new Scanner(new FileInputStream(srcFile))) {

        try(PrintWriter writer = new PrintWriter(new FileOutputStream(destFile))){
            while (scanner.hasNext()) {
                String line = scanner.nextLine();
                writer.println(line);
            }
        }
    }

    return destFile;
}

刪除文件

刪除文件包括刪除一般文件,還有目錄。文件比較容易,直接調用file.delete()即可,目錄的情況較為復雜,因為涉及到目錄不為空的情況。與Files.delete()刪除目錄類似,必須確保帶刪除目錄為空目錄,也就是說需要遍歷目錄及其子目錄,先刪除目錄下的內容,才能刪除對應目錄,這里不再遨述,后續有機會再補充完整這塊示例。

獲取文件信息

直接調用File對象接口,即可以查詢到文件對應信息,主要包括

String getName() // 獲取文件名稱
String getParent() // 獲取所在目錄名稱
File getParentFile() // 獲取文件路徑
boolean canRead() // 獲取文件是否可讀
boolean canWrite() // 獲取文件是否可寫
boolean exists() // 獲取文件是否存在
boolean isDirectory() // 表示是否是一個目錄
boolean isFile() // 表示是否是一個標注文件
long lastModified() // 最近一次修改
long length() // 文件長度
String[] list() // 路徑名所表示的目錄中的文件名列表
String[] list(FilenameFilter filter) // // 路徑名所表示的目錄中的文件名列表, 文件名經由filter過濾
String[] listFiles() // 路徑名所表示的目錄中的文件路列表
boolean setReadOnly() // 設置文件為只讀

Java File類|菜鳥教程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM