Tài nguyên Java dưới dạng tệp


122

Có cách nào trong Java để xây dựng một cá thể Tệp trên một tài nguyên được truy xuất từ ​​một jar thông qua trình nạp lớp không?

Ứng dụng của tôi sử dụng một số tệp từ jar (mặc định) hoặc từ thư mục hệ thống tệp được chỉ định trong thời gian chạy (đầu vào của người dùng). Tôi đang tìm một cách nhất quán để
a) tải các tệp này dưới dạng một luồng
b) liệt kê các tệp trong thư mục do người dùng xác định hoặc thư mục trong jar tương ứng

Chỉnh sửa: Rõ ràng, cách tiếp cận lý tưởng sẽ là tránh xa hoàn toàn java.io.File. Có cách nào để tải một thư mục từ classpath và liệt kê nội dung của nó (tệp / thực thể chứa trong nó) không?

Câu trả lời:


58

ClassLoader.getResourceAsStreamClass.getResourceAsStreamchắc chắn là cách để tải dữ liệu tài nguyên. Tuy nhiên, tôi không tin rằng có bất kỳ cách nào để "liệt kê" nội dung của một phần tử của classpath.

Trong một số trường hợp, điều này có thể đơn giản là không thể - ví dụ, a ClassLoader có thể tạo dữ liệu nhanh chóng, dựa trên tên tài nguyên mà nó yêu cầu. Nếu bạn nhìn vào ClassLoaderAPI (về cơ bản là những gì cơ chế classpath hoạt động thông qua), bạn sẽ thấy không có bất kỳ thứ gì để làm những gì bạn muốn.

Nếu bạn biết mình thực sự có tệp jar, bạn có thể tải tệp đó bằng ZipInputStream để tìm hiểu những gì có sẵn. Nó có nghĩa là bạn sẽ có mã khác nhau cho các thư mục và tệp jar.

Một giải pháp thay thế, nếu các tệp được tạo riêng trước tiên, là bao gồm một loại tệp kê khai chứa danh sách các tài nguyên có sẵn. Gói nó trong tệp jar hoặc đưa nó vào hệ thống tệp dưới dạng tệp và tải nó trước khi cung cấp cho người dùng lựa chọn tài nguyên.


3
Tôi đồng ý rằng nó khó chịu - nhưng nó làm cho ClassLoader được áp dụng rộng rãi hơn theo những cách khác. Ví dụ: thật dễ dàng để viết một "trình tải lớp web" vì web rất tốt để tìm nạp tệp, nhưng nó thường không liệt kê các tệp.
Jon Skeet

Có vẻ như nếu tài nguyên bạn đang cố gắng tải lớn hơn nhiều 1MiB thì InputStreambạn nhận được từ các getResourceAsStreamđiểm dừng để truy xuất các byte của tài nguyên sau kích thước đó và thay vào đó trả về 0 iff, nó được chứa trong hệ thống tệp nén giống như một cái lọ. Trong trường hợp đó, bạn dường như buộc phải sử dụng getResourcevà tải tệp độc lập với tệp này.
mgttlinger

1
@mgttlinger: Điều đó nghe có vẻ khó xảy ra với tôi ... có lẽ đáng để đăng một câu hỏi mới với repro nếu bạn có thể.
Jon Skeet

Vấn đề là do readphương pháp quyết định lượng dữ liệu cần đọc (tôi không biết về điều đó). Và có vẻ như toàn bộ tệp được đọc nếu tệp đang được đọc nằm trong một thư mục thông thường. Nếu tệp nằm trong một cái lọ vì nó đã được đóng gói, nó chỉ đọc các phần của nó.
mgttlinger

1
@mgttlinger: Không, nó sẽ không chỉ đọc các phần của nó - nhưng nó có thể không lấp đầy toàn bộ bộ đệm được cung cấp trong mỗi lần gọi đọc. Như mọi khi InputStream, nếu bạn muốn đọc toàn bộ tài nguyên, bạn nên lặp readlại cho đến khi trả về -1.
Jon Skeet

98

Tôi đã gặp vấn đề tương tự và có thể sử dụng những thứ sau:

// Load the directory as a resource
URL dir_url = ClassLoader.getSystemResource(dir_path);
// Turn the resource into a File object
File dir = new File(dir_url.toURI());
// List the directory
String files = dir.list()

45
Cách tiếp cận này không làm việc với lọ trong classpath, chỉ với các tập tin và thư mục
yegor256

1
@ yegor256 Nếu tệp của bạn nằm trong jar, bạn có thể tạo một FileSystemđối tượng và lấy Pathtừ đó. Sau đó, bạn có thể đọc nó như bình thường. (xem stackoverflow.com/a/22605905/1876344 )
mgttlinger

53

Đây là một chút mã từ một trong các ứng dụng của tôi ... Hãy cho tôi biết nếu nó phù hợp với nhu cầu của bạn. Bạn có thể sử dụng điều này nếu bạn biết tệp bạn muốn sử dụng.

URL defaultImage = ClassA.class.getResource("/packageA/subPackage/image-name.png");
File imageFile = new File(defaultImage.toURI());

Hy vọng rằng sẽ giúp.


Đúng là có, nhưng toURI()sẽ thất bại.
Olivier Faucheux

10

Một cách đáng tin cậy để xây dựng một cá thể Tệp trên tài nguyên được truy xuất từ ​​một jar là sao chép tài nguyên dưới dạng luồng vào một Tệp tạm thời (tệp tạm thời sẽ bị xóa khi JVM thoát):

public static File getResourceAsFile(String resourcePath) {
    try {
        InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath);
        if (in == null) {
            return null;
        }

        File tempFile = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
        tempFile.deleteOnExit();

        try (FileOutputStream out = new FileOutputStream(tempFile)) {
            //copy stream
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        }
        return tempFile;
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

4

Thử cái này:

ClassLoader.getResourceAsStream ("some/pkg/resource.properties");

Có nhiều phương pháp hơn có sẵn, ví dụ: xem tại đây: http://www.javaworld.com/javaworld/javaqa/2003-08/01-qa-0808-property.html


Cám ơn phản hồi của bạn. Tôi biết về getResourceAsStream, nhưng tôi không nghĩ rằng tôi có thể tải một thư mục từ classpath thông qua đó.
Mantrum

Trong thư mục Java là một tệp (tôi đoán là gốc UNIX) nên ít nhất bạn nên thử.
topchef

Tôi nghĩ rằng để liệt kê các tệp trong một thư mục, bạn sẽ cần sử dụng java.io.File. Bạn có thể tra cứu tệp bằng ClassLoader.findResource trả về một URL có thể được chuyển đến Tệp.
Nathan Voxland

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.