Đọc một tệp tài nguyên từ trong jar


241

Tôi muốn đọc một tài nguyên từ trong bình của tôi như vậy:

File file;
file = new File(getClass().getResource("/file.txt").toURI());
BufferredReader reader = new BufferedReader(new FileReader(file));

//Read the file

và nó hoạt động tốt khi chạy nó trong Eclipse, nhưng nếu tôi xuất nó sang một jar thì chạy nó có IllegalArgumentException:

Exception in thread "Thread-2"
java.lang.IllegalArgumentException: URI is not hierarchical

và tôi thực sự không biết tại sao nhưng với một số thử nghiệm tôi đã tìm thấy nếu tôi thay đổi

file = new File(getClass().getResource("/file.txt").toURI());

đến

file = new File(getClass().getResource("/folder/file.txt").toURI());

sau đó nó hoạt động ngược lại (nó hoạt động trong bình nhưng không nhật thực).

Tôi đang sử dụng Eclipse và thư mục chứa tệp của tôi nằm trong một thư mục lớp.


Nếu bạn muốn đọc các tệp từ một thư mục trong jar với bất kỳ số nào cho các tệp, hãy xem Stackoverflow-Link
Michael Hegner

1
Tôi không chắc chắn rằng câu hỏi ban đầu liên quan đến Spring. Liên kết trong bình luận trước đề cập đến một câu trả lời cụ thể của Spring từ một câu hỏi khác. Tôi tin rằng getResourceAsStreamvẫn là một giải pháp đơn giản và di động hơn cho vấn đề.
Drew MacInni

Câu trả lời:


397

Thay vì cố gắng giải quyết tài nguyên dưới dạng Tệp, chỉ cần yêu cầu ClassLoader trả về InputStream cho tài nguyên thay vì thông qua getResourceAsStream :

InputStream in = getClass().getResourceAsStream("/file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));

Miễn là file.txttài nguyên có sẵn trên đường dẫn lớp thì cách tiếp cận này sẽ hoạt động theo cùng một cách bất kể file.txttài nguyên nằm trong một classes/thư mục hay bên trong a jar.

Điều URI is not hierarchicalnày xảy ra vì URI cho một tài nguyên trong tệp jar sẽ trông giống như thế này : file:/example.jar!/file.txt. Bạn không thể đọc các mục trong một jar(một ziptệp) như nó là một tệp cũ đơn giản .

Điều này được giải thích tốt bằng các câu trả lời cho:


3
Cảm ơn bạn, điều này rất hữu ích và mã hoạt động hoàn hảo, nhưng tôi có một vấn đề, tôi cần xác định xem có InputStreamtồn tại (như File.exists()) để trò chơi của tôi có thể biết có nên sử dụng tệp mặc định hay không. Cảm ơn.
PrinceCJC

1
Oh và BTW lý do getClass().getResource("**/folder**/file.txt")khiến nó hoạt động là vì tôi có thư mục đó trong cùng thư mục với jar của tôi :).
PrinceCJC

5
getResourceAsStreamtrả về null nếu tài nguyên không tồn tại để có thể là bài kiểm tra "tồn tại" của bạn.
Drew MacInni

1
BTW, bạn có một lỗi đánh máy: nó phải là BufferedReader, không phải BufferredReader (chú ý thêm 'r' sau)
mailmindlin

2
Và tất nhiên ... đừng quên đóng inputStream và BufferedReader
Noremac

26

Để truy cập một tệp trong một jar bạn có hai tùy chọn:

  • Đặt tệp trong cấu trúc thư mục khớp với tên gói của bạn (sau khi giải nén tệp .jar, tệp phải nằm trong cùng thư mục với tệp. Class), sau đó truy cập tệp bằng getClass().getResourceAsStream("file.txt")

  • Đặt tệp tại thư mục gốc (sau khi giải nén tệp .jar, tệp phải nằm trong thư mục gốc), sau đó truy cập tệp bằng cách sử dụng Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")

Tùy chọn đầu tiên có thể không hoạt động khi jar được sử dụng làm plugin.


Cảm ơn bạn rất nhiều vì câu trả lời tuyệt vời này ... Một lợi ích khác từ tùy chọn thứ hai là nó cũng hoạt động từ phương thức chính, bởi vì getClass () chỉ hoạt động từ một cá thể chứ không phải từ các phương thức tĩnh.
Dan Ortega

6

Tôi đã có vấn đề này trước đây và tôi đã thực hiện cách dự phòng để tải. Về cơ bản cách thứ nhất hoạt động trong tệp .jar và cách thứ hai hoạt động trong nhật thực hoặc IDE khác.

public class MyClass {

    public static InputStream accessFile() {
        String resource = "my-file-located-in-resources.txt";

        // this is the path within the jar file
        InputStream input = MyClass.class.getResourceAsStream("/resources/" + resource);
        if (input == null) {
            // this is how we load file within editor (eg eclipse)
            input = MyClass.class.getClassLoader().getResourceAsStream(resource);
        }

        return input;
    }
}

3

Cho đến bây giờ (tháng 12 năm 2017), đây là giải pháp duy nhất tôi tìm thấy hoạt động cả bên trong và bên ngoài IDE.

Sử dụng PathMatchingResourcePotypeResolver

Lưu ý: nó cũng hoạt động trong spring-boot

Trong ví dụ này tôi đang đọc một số tệp nằm trong src / main / resource / my_folder :

try {
    // Get all the files under this inner resource folder: my_folder
    String scannedPackage = "my_folder/*";
    PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
    Resource[] resources = scanner.getResources(scannedPackage);

    if (resources == null || resources.length == 0)
        log.warn("Warning: could not find any resources in this scanned package: " + scannedPackage);
    else {
        for (Resource resource : resources) {
            log.info(resource.getFilename());
            // Read the file content (I used BufferedReader, but there are other solutions for that):
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                // ...
                // ...                      
            }
            bufferedReader.close();
        }
    }
} catch (Exception e) {
    throw new Exception("Failed to read the resources folder: " + e.getMessage(), e);
}

3

Vấn đề là các thư viện bên thứ ba nhất định yêu cầu tên đường dẫn tệp chứ không phải luồng đầu vào. Hầu hết các câu trả lời không giải quyết vấn đề này.

Trong trường hợp này, một cách giải quyết là sao chép nội dung tài nguyên vào một tệp tạm thời. Ví dụ sau sử dụng jUnit's TemporaryFolder.

    private List<String> decomposePath(String path){
        List<String> reversed = Lists.newArrayList();
        File currFile = new File(path);
        while(currFile != null){
            reversed.add(currFile.getName());
            currFile = currFile.getParentFile();
        }
        return Lists.reverse(reversed);
    }

    private String writeResourceToFile(String resourceName) throws IOException {
        ClassLoader loader = getClass().getClassLoader();
        InputStream configStream = loader.getResourceAsStream(resourceName);
        List<String> pathComponents = decomposePath(resourceName);
        folder.newFolder(pathComponents.subList(0, pathComponents.size() - 1).toArray(new String[0]));
        File tmpFile = folder.newFile(resourceName);
        Files.copy(configStream, tmpFile.toPath(), REPLACE_EXISTING);
        return tmpFile.getAbsolutePath();
    }

Điều này được che đậy rất nhiều. Cảm ơn đã ghi lại nó. Tôi tò mò không biết lựa chọn nào khác tồn tại mà không có JUnit.
Alex Moore-Niemi

0

Nếu bạn muốn đọc dưới dạng tệp, tôi tin rằng vẫn có một giải pháp tương tự:

    ClassLoader classLoader = getClass().getClassLoader();
    File file = new File(classLoader.getResource("file/test.xml").getFile());

4
URL.getFile () không chuyển đổi URL thành tên tệp. Nó trả về một phần URL sau máy chủ, với tất cả các mã hóa phần trăm còn nguyên vẹn, do đó, nếu đường dẫn chứa bất kỳ ký tự không phải ASCII hoặc bất kỳ ký tự ASCII nào không được phép trong URL (bao gồm khoảng trắng), kết quả sẽ không phải là tên tệp hiện có , ngay cả khi URL là một file:URL.
VGR

48
điều này không hoạt động bên trong một khi chương trình được xây dựng thành bình
Akshay Kasar

1
Không hoạt động từ jar trừ khi bạn chuyển đổi thành chuỗi và lưu nó cục bộ trước.
smoosh911

0

Hãy chắc chắn rằng bạn làm việc với dấu phân cách chính xác. Tôi đã thay thế tất cả /trong một con đường tương đối bằng a File.separator. Điều này hoạt động tốt trong IDE, tuy nhiên không hoạt động trong JAR xây dựng.


0

Tôi nghĩ rằng điều này nên được làm việc trong java là tốt. Đoạn mã sau tôi sử dụng là sử dụng kotlin.

val resource = Thread.currentThread().contextClassLoader.getResource('resources.txt')

0

Tôi đã tìm thấy một sửa chữa

BufferedReader br = new BufferedReader(new InputStreamReader(Main.class.getResourceAsStream(path)));

Thay thế "Chính" bằng lớp java mà bạn đã mã hóa. Thay thế "đường dẫn" bằng đường dẫn trong tệp jar.

ví dụ: nếu bạn đặt State1.txt trong gói com.issac.state, thì hãy nhập đường dẫn là "/ com / issac / state / State1" nếu bạn chạy Linux hoặc Mac. Nếu bạn chạy Windows, hãy nhập đường dẫn là "\ com \ issac \ state \ State1". Không thêm phần mở rộng .txt vào tệp trừ khi xảy ra ngoại lệ Tệp không tìm thấy.


-1

Bạn có thể sử dụng trình nạp lớp sẽ đọc từ đường dẫn lớp dưới dạng đường dẫn ROOT (không có "/" ở đầu)

InputStream in = getClass().getClassLoader().getResourceAsStream("file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));

-1

Nếu bạn đang sử dụng spring, thì bạn có thể sử dụng phương pháp sau để đọc tệp từ src / main / resource:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.springframework.core.io.ClassPathResource;

  public String readFileToString(String path) throws IOException {

    StringBuilder resultBuilder = new StringBuilder("");
    ClassPathResource resource = new ClassPathResource(path);

    try (
        InputStream inputStream = resource.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {

      String line;

      while ((line = bufferedReader.readLine()) != null) {
        resultBuilder.append(line);
      }

    }

    return resultBuilder.toString();
  }

1
Chào mừng đến với SO. Điều này không cung cấp một câu trả lời cho câu hỏi. Một khi bạn có đủ danh tiếng, bạn sẽ có thể nhận xét về bất kỳ bài đăng nào. Ngoài ra kiểm tra điều này những gì tôi có thể làm thay thế .
thewaywewere

Điều này sẽ loại bỏ các ngắt dòng trong tập tin!
elad.chen ngày

-1

Vì một số lý do classLoader.getResource()luôn trả về null khi tôi triển khai ứng dụng web lên WildFly 14. nhận classLoader từ getClass().getClassLoader()hoặc Thread.currentThread().getContextClassLoader()trả về null.

getClass().getClassLoader() Tài liệu API nói,

"Trả về trình nạp lớp cho lớp. Một số triển khai có thể sử dụng null để biểu diễn trình nạp lớp bootstrap. Phương thức này sẽ trả về null trong các triển khai đó nếu lớp này được tải bởi trình nạp lớp bootstrap."

có thể nếu bạn đang sử dụng WildFly và ứng dụng web của bạn hãy thử điều này

request.getServletContext().getResource()trả về url tài nguyên. Ở đây yêu cầu là một đối tượng của ServletRequest.


-2

Mã dưới đây hoạt động với Spring boot (kotlin):

val authReader = InputStreamReader(javaClass.getResourceAsStream("/file1.json"))
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.