Thao tác với tập tin và thư mục trong Java

Trong các ứng dụng, các bạn phải thao tác thường xuyên với tập tin (file) và thư mục (folder) trong hệ thống. Các thao tác này bao gồm: liệt kê danh sách tập tin, thư mục, thêm/ đổi tên, xóa thư mục, …Java cung cấp 2 class hỗ trợ những việc này:

  • Lớp java.io.File đại diện cho một tập tin (file) hoặc một thư mục (directory) của hệ thống, nó được đưa vào Java từ phiên bản 1.0.
  • Lớp java.nio.file.Files cung cấp các phương thức static và native để thao tác với tập tin và thư mục, nó được bổ sung vào từ phiên bản Java 7.0. Lớp này thường được sử dụng khi maintain/optimize chương trình để tăng hiệu suất chương trình Java. Khả năng tăng hiệu suất chương trình của NIO không chỉ phụ thuộc vào hệ điều hành mà còn phụ thuộc vào phiên bản JVM, hệ thống máy chủ, ổ cứng, hay thậm chí dữ liệu.

Trong phạm vi bài viết này, tôi sẽ hướng dẫn các bạn sử dụng một số thao tác với tập tin và thư mục trong Java thông qua lớp java.io.File. Thao tác với java.nio.file.Files sẽ được giới thiệu ở bài viết khác.

1. Đường dẫn (Path) là gì?

Một hệ thống tập tin lưu trữ và tổ chức các tập tin trên một số loại phương tiện lưu trữ, thường là một hoặc nhiều ổ đĩa cứng, theo cách sao cho chúng có thể dễ dàng lấy ra.

Hầu hết các hệ thống tập tin được sử dụng ngày hôm nay lưu trữ các tập tin trong một cây (hoặc cấu trúc phân cấp). Ở đầu cây là một (hoặc nhiều hơn) các nút gốc. Dưới nút gốc, có các tệp và thư mục (thư mục trong Microsoft Windows). Mỗi thư mục có thể chứa các tệp tin và các thư mục con, do đó có thể chứa các tệp và thư mục con, v.v … có khả năng lưu trữ một chiều sâu gần như vô hạn.

Microsoft Windows hỗ trợ nhiều nút gốc. Mỗi nút gốc ánh xạ tới một phân vùng, chẳng hạn như C:\ hoặc D:\. Hệ điều hành Solaris (Linux) hỗ trợ một nút gốc, được biểu thị bởi ký tự dấu gạch chéo /.

Một tập tin được xác định bởi đường dẫn của nó thông qua hệ thống tập tin, bắt đầu từ nút gốc. Ví dụ, tập tin statusReport trong hình trên được mô tả bằng ký hiệu sau:

Trong Hệ điều hành Solaris: /home/user2/statusReport
Trong Microsoft Windows: C:\home\user2\statusReport

Ký tự được sử dụng để phân cách các tên thư mục cho hệ thống tập tin:

  • Hệ điều hành Solaris sử dụng dấu gạch chéo (/).
  • Microsoft Windows sử dụng dấu gạch chéo ngược (\). Tuy nhiên, vẫn có thể sử dụng dấu gạch chéo (/) trên Microsoft Window.

Chúng ta có thể lấy ký tự cách này bằng cách sử dụng thuộc tính seperator của lớp java.io.File như sau:

File.separator

Thuộc tính seperator là một biến static, nó trả về dấu gạch chéo (/) nếu bạn sử dụng hệ điều hành Solaris hoặc trả về dấu gạch chéo ngược (\) nếu bạn sử dụng hệ điều hành Window.

2. Đường dẫn tương đối và tuyệt đối

Một đường dẫn tuyệt đối luôn chứa các phần tử gốc và danh sách thư mục đầy đủ cần thiết để định vị tệp tin. Ví dụ, /home/user2/statusReport là một đường dẫn tuyệt đối. Tất cả thông tin cần thiết để định vị tệp tin được chứa trong chuỗi đường dẫn.

Một đường dẫn tương đối cần phải được kết hợp với một đường dẫn khác để truy cập một tập tin. Ví dụ, user1/foo là một đường dẫn tương đối. Nếu không có thêm thông tin, một chương trình không thể xác định chính xác vị trí thư mục user2/statusReport trong hệ thống tập tin.

3. Giới thiệu lớp Java.io.File

Lớp java.io.File đại diện cho một tập tin hoặc một thư mục trong hệ thống, nó đại diện cho một đường dẫn (path). Đường dẫn này có thể không thực sự tồn tại trên hệ thống. Nếu tồn tại thì nó có thể là một thư mục (directory) hoặc là một tập tin (file).

Ví dụ tạo một đối tượng File đại diện cho một đường dẫn, và kiểm tra sự tồn tại của nó, ghi ra các thông tin cơ bản của File.

package com.maixuanviet;
 
import java.io.File;
import java.util.Date;
 
public class FileInfoExample {
    public static void main(String[] args) {
        // Tạo một đối tượng File đại diện cho một đường dẫn
        File file = new File("D:/WorkSpace/maixuanviet/data/maixuanviet.txt");
 
        // Kiểm tra sự tồn tại.
        System.out.println("Path exists : " + file.exists());
 
        if (file.exists()) {
            // Kiểm tra có phải có phải là một folder hay không?
            System.out.println("isDirectory : " + file.isDirectory());
 
            // Kiểm tra có phải là một đường dẫn ẩn hay không?
            System.out.println("isHidden : " + file.isHidden());
 
            // Lấy tên file/ folder
            System.out.println("Simple Name: " + file.getName());
 
            // Đường dẫn tuyêt đối.
            System.out.println("Absolute Path: " + file.getAbsolutePath());
 
            // Kiểm tra kích thước file:
            System.out.println("Length : " + file.length() + " (bytes)");
 
            // Thời điểm sửa lần cuối
            long lastMofifyInMillis = file.lastModified(); // milliseconds
            Date lastModifyDate = new Date(lastMofifyInMillis);
            System.out.println("Last modify date: " + lastModifyDate);
        }
    }
}

Kết quả thực thi chương trình trên:

Path exists : true
isDirectory : false
isHidden : false
Simple Name: maixuanviet.txt
Absolute Path: D:\WorkSpace\maixuanviet\data\maixuanviet.txt
Length : 48 (bytes)
Last modify date: Fri Dec 08 21:05:39 ICT 2017

4. Lấy đường dẫn đến thư mục đang làm việc (Project Path)

package com.maixuanviet;
 
import java.io.File;
 
public class AbsoluteRelavativePathExample {
 
    public static void main(String[] args) {
        // Get Current Directory using getAbsolutePath()
        File file = new File("");
        String currentDirectory = file.getAbsolutePath();
        System.out.println("Current working directory : " + currentDirectory);
 
        // Get Current Directory using Property user.dir
        String workingDir = System.getProperty("user.dir");
        System.out.println("Current working directory : " + workingDir);
    }
 
}

Kết quả thực thi chương trình trên:

Current working directory : D:\WorkSpace\gpcoder\JavaIOTutorial
Current working directory : D:\WorkSpace\gpcoder\JavaIOTutorial

5. Tạo thư mục

Lớp File cung cấp 2 phương thức để tạo môt thư mục:

  • mkdir(): Tạo thư mục cho bởi đường dẫn nếu thư mục cha tồn tại.
  • mkdirs(): Tạo thư mục cho bởi đường dẫn, bao gồm cả các thư mục cha nếu nó không tồn tại.

Ví dụ:

package com.maixuanviet;
 
import java.io.File;
 
public class MakeDirExample {
 
    public static void main(String[] args) {
        File dir = new File("D:/WorkSpace/maixuanviet/data/created1/child1");
        System.out.println("Pathname: " + dir.getAbsolutePath());
        System.out.println("Path exists:  " + dir.exists()); // false
        System.out.println("Parent Path exists: " + dir.getParentFile().exists()); // false
 
        // Với mkdir(), thư mục chỉ được tạo ra nếu thư mục cha tồn tại.
        boolean created = dir.mkdir();
        System.out.println("Directory created: " + created); // false
 
        System.out.println();
 
        File dir2 = new File("D:/WorkSpace/maixuanviet/data/created2/child2");
        System.out.println("Pathname: " + dir2.getAbsolutePath());
        System.out.println("File exists: " + dir2.exists()); // false
 
        // Với mkdirs(), thư mục được tạo ra bao gồm cả các thư mục cha nếu nó không tồn tại.
        created = dir2.mkdirs();
        System.out.println("Directory created: " + created); // true
    }
}

Kết quả thực thi chương trình trên:

Pathname: D:\WorkSpace\maixuanviet\data\created1\child1
Path exists:  false
Parent Path exists: false
Directory created: false
--------------------
Pathname: D:\WorkSpace\maixuanviet\data\created2\child2
File exists: false
Directory created: true

6. Bộ lọc File (File Filter)

Lớp java.io.File cung cấp một vài phương thức để lấy ra danh sách các tập tin con và thư mục con trong một thư mục. Và sử dụng FileFilter để lọc các tập tin đó.

  • File[] listRoots(): Trả về một mảng các đối tượng File là đại diện cho các thư mục gốc. Trong Windows nó sẽ là các ổ đĩa (C:, D:,..), trong Unix nó là /
  • File[] listFiles(): Trả về một mảng các đối tượng File, là các tập tin và các thư mục con của thư mục hiện tại.
  • File[] listFiles(FilenameFilter filter): Trả về một mảng các đối tượng File, là các tập tin và các thư mục con của thư mục hiện tại, và phù hợp với bộ lọc FilenameFilter trên tham số.
  • File[] listFiles(FileFilter filter): Trả về một mảng các đối tượng File, là các tập tin và các thư mục con của thư mục hiện tại, và phù hợp với bộ lọc FileFilter trên tham số.
  • String[] list(): Trả về một mảng các đường dẫn, là đường dẫn của các tập tin và đường dẫn của các thư mục con của thư mục hiện tại.
  • String[] list(FilenameFilter filter): Trả về một mảng các đường dẫn, là đường dẫn của các tập tin và đường dẫn của các thư mục con của thư mục hiện tại, và phù hợp với bộ lọc FiltenameFilter trên tham số.

6.1. Ví dụ liệt kê tất cả các thư mục gốc

package com.maixuanviet;
 
import java.io.File;
 
public class RootFilesExample {
    public static void main(String[] args) {
        File[] roots = File.listRoots();
        for (File root : roots) {
            System.out.println(root.getAbsolutePath());
        }
    }
}

Kết quả thực thi chương trình trên:

C:\
D:\
E:\

6.2. Ví dụ liệt kê tất cả các thư mục và tập tin con trực tiếp của một thư mục

package com.maixuanviet;
 
import java.io.File;
 
public class FileListExample {
    public static void main(String[] args) {
        System.out.println("File[] listFiles(): ");
        File dir = new File("D:\\WorkSpace\\maixuanviet\\data\\created1");
        File[] children = dir.listFiles();
        for (File file : children) {
            System.out.println(file.getAbsolutePath());
        }
 
        System.out.println();
 
        System.out.println("String[] list(): ");
        String[] paths = dir.list();
        for (String path : paths) {
            System.out.println(path);
        }
    }
}

Kết quả thực thi chương trình trên:

File[] listFiles(): 
D:\WorkSpace\maixuanviet\data\created1\test1.txt
D:\WorkSpace\maixuanviet\data\created1\test2.txt
D:\WorkSpace\maixuanviet\data\created1\test3.txt
 
String[] list(): 
test1.txt
test2.txt
test3.txt

6.3. Ví dụ liệt kê tất cả thư mục và tập tin con, bao gồm thư mục con, cháu, …

Trong ví dụ bên dưới, tôi sử dụng kỹ thuật đệ quy để liệt kê tất cả các folder và file của một thư mục.

  •  Điều kiện thoát khỏi đệ quy: khi đối tượng File là tập tin
  • Phần đệ quy: khi đối tượng File là thư mục.
package com.maixuanviet;
 
import java.io.File;
 
public class RecursiveFileExample {
 
    public static void main(String[] args) {
        RecursiveFileExample example = new RecursiveFileExample();
        File dir = new File("D:\\WorkSpace\\maixuanviet\\data");
        example.listChild(dir, 0);
    }
 
    private void listChild(File file, int level) {
 
        if (file.isDirectory()) { // Dừng nếu là tập tin
            System.out.println(getPadding(level) + " - " + file.getName());
            File[] children = file.listFiles();
            for (File child : children) {
                this.listChild(child, level + 1); // Gọi đệ quy
            }
        } else {
            System.out.println(getPadding(level) + " + " + file.getName());
        }
 
    }
 
    private String getPadding(int level) {
        StringBuilder sb = new StringBuilder();
        for (int i = 1; i <= level; i++) {
            sb.append("    "); // Thêm dấu tab.
        }
        return sb.toString();
    }
 
}
&#91;/code&#93;
<!-- /wp:shortcode -->

<!-- wp:paragraph -->
<p>Kết quả thực thi chương trình trên:</p>
<!-- /wp:paragraph -->

<!-- wp:shortcode -->

- data
    - created1
        - child1
            + test1.txt
            + test2.txt
        + test1.txt
        + test2.txt
        + test3.txt
    - created2
        - child2
    - created3
    + gpcoder.pdf
    + gpcoder.txt
    + test.docx
    + test1.txt
    + test2.txt
    + test3.txt

6.4. Ví dụ sử dụng FileFilter để lọc ra các tập tin nằm trong một thư mục và có phần mở rộng là .txt

TxtFileFilter.java

package com.maixuanviet;
 
import java.io.File;
import java.io.FileFilter;
 
public class TxtFileFilter implements FileFilter {
 
    @Override
    public boolean accept(File pathname) {
 
        if (!pathname.isFile()) {
            return false;
        }
 
        if (pathname.getAbsolutePath().endsWith(".txt")) {
            return true;
        }
 
        return false;
    }
 
}

FileFilterExample.java

package com.maixuanviet;
 
import java.io.File;
 
public class FileFilterExample {
 
    public static void main(String[] args) {
 
        File dir = new File("D:/WorkSpace/maixuanviet/data");
 
        File[] txtFiles = dir.listFiles(new TxtFileFilter());
 
        for (File txtFile : txtFiles) {
            System.out.println(txtFile.getAbsolutePath());
        }
    }
 
}

Kết quả thực thi chương trình trên:

D:\WorkSpace\maixuanviet\data\maixuanviet2.txt
D:\WorkSpace\maixuanviet\data\test2.txt
D:\WorkSpace\maixuanviet\data\test3.txt

6.5. Ví dụ sử dụng FilenameFilter để lọc ra các tập tin nằm trong một thư mục và có phần mở rộng là .txt

FilenameFilter.java

package com.maixuanviet;
 
import java.io.File;
import java.io.FilenameFilter;
 
public class TxtFileNameFilter implements FilenameFilter {
 
    @Override
    public boolean accept(File dir, String name) {
 
        if (name.endsWith(".txt")) {
            return true;
        }
 
        return false;
    }
 
}

FileFilterExample.java

package com.maixuanviet;
 
import java.io.File;
 
public class FileNameFilterExample {
 
    public static void main(String[] args) {
 
        File dir = new File("D:/WorkSpace/maixuanviet/data");
 
        File[] txtFiles = dir.listFiles(new TxtFileNameFilter());
 
        for (File txtFile : txtFiles) {
            System.out.println(txtFile.getAbsolutePath());
        }
    }
 
}

Kết quả thực thi chương trình trên:

D:\WorkSpace\maixuanviet\data\maixuanviet2.txt
D:\WorkSpace\maixuanviet\data\test2.txt
D:\WorkSpace\maixuanviet\data\test3.txt

7. Đổi tên thư mục và tập tin

Sử dụng phương thưc rename() của lớp File để đổi tên (hoặc cả đường dẫn) của một file hoặc folder.

7.1. Ví dụ đổi tên một tập tin hoặc thư mục nhưng không thay đổi đường dẫn cha

package com.maixuanviet.combine;
 
import java.io.File;
 
public class RenameFileExample {
    public static void main(String[] args) {
        File srcFile = new File("D:/WorkSpace/maixuanviet/data/maixuanviet.txt");
        File destFile = new File("D:/WorkSpace/maixuanviet/data/maixuanviet2.txt");
 
        boolean renamed = srcFile.renameTo(destFile);
        System.out.println("Renamed: " + renamed); // true
    }
}

7.2. Ví dụ đổi tên bao gồm cả đường dẫn cha

Đổi tên và đổi cả đường dẫn cha, giống với hành động Cut một file hoặc một thư mục sang một thư mục khác và sau đó đổi tên.
Với hệ điều hành Windows, phương thức File.rename() sẽ không hoạt động nếu đổi đường dẫn từ ổ đĩa này sang ổ đĩa khác.

package com.maixuanviet;
 
import java.io.File;
 
public class RenameFolderExample {
    public static void main(String[] args) {
        File srcFile = new File("D:/WorkSpace/maixuanviet/data/test1.txt");
        File destFile = new File("D:/WorkSpace/maixuanviet/data/created4/maixuanviet.txt");
 
        if (!srcFile.exists()) {
            System.out.println("Src File doest not exists");
            return;
        }
 
        // Tạo thư mục cha của file đích
        destFile.getParentFile().mkdirs();
 
        boolean renamed = srcFile.renameTo(destFile);
        System.out.println("Renamed: " + renamed); // true
    }
}

8. Xóa thư mục và tập tin

Sử dụng phương thức delete() để xóa tập tin và thư mục.
Lưu ý: phương thức delete() chỉ cho phép xóa thư mục rỗng.

8.1. Ví dụ xóa tập tin

package com.maixuanviet;
 
import java.io.File;
 
public class DeleteFileExample {
    public static void main(String[] args) {
        File file = new File("D:/WorkSpace/maixuanviet/data/test2.txt");
        if (file.delete()) {
            System.out.println(file.getName() + " is deleted!");
        } else {
            System.out.println("Delete operation is failed.");
        }
    }
}

8.2. Ví dụ xóa thư mục rỗng

package com.maixuanviet;
 
import java.io.File;
 
public class DeleteFolderExample {
    public static void main(String[] args) {
        File folder = new File("D:/WorkSpace/maixuanviet/data/created4");
        if (folder.delete()) {
            System.out.println(folder.getName() + " is deleted!");
        } else {
            System.out.println("Delete operation is failed.");
        }
    }
}

8.3. Ví dụ xóa thư mục có chưa thư mục con và tập tin bằng đệ quy

package com.maixuanviet;
 
import java.io.File;
 
public class DeleteFolderExample {
    public static void main(String[] args) {
        File folder = new File("D:/WorkSpace/maixuanviet/data/created3");
        delete(folder);
    }
 
    public static void delete(File file) {
 
        if (file.isDirectory()) {
            // directory is empty, then delete it
            if (file.list().length == 0) {
                file.delete();
                System.out.println("Directory is deleted : " + file.getAbsolutePath());
            } else {
                // list all the directory contents
                String files[] = file.list();
                for (String temp : files) {
                    // construct the file structure
                    File fileDelete = new File(file, temp);
 
                    // recursive delete
                    delete(fileDelete);
                }
 
                // check the directory again, if empty then delete it
                if (file.list().length == 0) {
                    file.delete();
                    System.out.println("Directory is deleted : " + file.getAbsolutePath());
                }
            }
 
        } else {
            // if file, then delete it
            file.delete();
            System.out.println("File is deleted : " + file.getAbsolutePath());
        }
    }
}

9. Sao chép tập tin và thư mục

Ví dụ dưới đây minh họa việc chép một thư mục và các tập tin trong thư mục sử dụng phương pháp đệ quy.

package com.maixuanviet.copy;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
 
public class CopyFolderExample {
 
    public static void main(String[] args) throws IOException {
        File sourceDir = new File("E:/TestCopy");
        File destDir = new File("D:/Copy");
        copyDirectory(sourceDir, destDir);
    }
 
    public static void copyDirectory(File sourceDir, File destDir) throws IOException {
        // creates the destination directory if it does not exist
        if (!destDir.exists()) {
            destDir.mkdirs();
        }
 
        // throws exception if the source does not exist
        if (!sourceDir.exists()) {
            throw new IllegalArgumentException("sourceDir does not exist");
        }
 
        // throws exception if the arguments are not directories
        if (sourceDir.isFile() || destDir.isFile()) {
            throw new IllegalArgumentException("Either sourceDir or destDir is not a directory");
        }
 
        doCopyDirectoryImpl(sourceDir, destDir);
    }
 
    private static void doCopyDirectoryImpl(File sourceDir, File destDir) throws IOException {
        File[] items = sourceDir.listFiles();
        if (items != null &amp;&amp; items.length &gt; 0) {
            for (File anItem : items) {
                if (anItem.isDirectory()) {
                    // create the directory in the destination
                    File newDir = new File(destDir, anItem.getName());
                    System.out.println("CREATED DIR: " + newDir.getAbsolutePath());
                    newDir.mkdir();
 
                    // copy the directory (recursive call)
                    doCopyDirectoryImpl(anItem, newDir);
                } else {
                    // copy the file
                    File destFile = new File(destDir, anItem.getName());
                    doCopySingleFile(anItem, destFile);
                }
            }
        }
    }
 
    private static void doCopySingleFile(File sourceFile, File destFile) throws IOException {
        if (!destFile.exists()) {
            destFile.createNewFile();
        }
 
        FileChannel sourceChannel = null;
        FileChannel destChannel = null;
        FileInputStream fis = null;
        FileOutputStream fos = null;
 
        try {
            fis = new FileInputStream(sourceFile);
            fos = new FileOutputStream(destFile);
 
            sourceChannel = fis.getChannel();
            destChannel = fos.getChannel();
            sourceChannel.transferTo(0, sourceChannel.size(), destChannel);
        } finally {
            if (fis != null) {
                fis.close();
            }
            if (fos != null) {
                fos.close();
            }
            if (sourceChannel != null) {
                sourceChannel.close();
            }
            if (destChannel != null) {
                destChannel.close();
            }
        }
    }
 
}

Related posts: