Các cách khác nhau để tải tệp dưới dạng InputStream


216

Sự khác biệt giữa:

InputStream is = this.getClass().getClassLoader().getResourceAsStream(fileName)

InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)

InputStream is = this.getClass().getResourceAsStream(fileName)

Khi nào thì mỗi cái thích hợp để sử dụng hơn những cái khác?

Tệp mà tôi muốn đọc nằm trong đường dẫn lớp là lớp tôi đọc tệp. Lớp của tôi và tệp nằm trong cùng một tệp và được đóng gói trong tệp EAR và được triển khai trong WebSphere 6.1.

Câu trả lời:


289

Có những khác biệt tinh tế như cách fileNamebạn vượt qua được diễn giải. Về cơ bản, bạn có 2 phương pháp khác nhau: ClassLoader.getResourceAsStream()Class.getResourceAsStream(). Hai phương pháp này sẽ định vị tài nguyên khác nhau.

Trong Class.getResourceAsStream(path), đường dẫn được hiểu là một đường dẫn cục bộ đến gói của lớp mà bạn đang gọi nó từ đó. Ví dụ như gọi, String.getResourceAsStream("myfile.txt")sẽ tìm tệp trong đường dẫn lớp của bạn tại vị trí sau : "java/lang/myfile.txt". Nếu đường dẫn của bạn bắt đầu bằng a /, thì nó sẽ được coi là một đường dẫn tuyệt đối và sẽ bắt đầu tìm kiếm từ gốc của đường dẫn lớp. Vì vậy, việc gọi String.getResourceAsStream("/myfile.txt")sẽ xem xét vị trí sau trong đường dẫn lớp của bạn ./myfile.txt.

ClassLoader.getResourceAsStream(path)sẽ coi tất cả các đường dẫn là đường dẫn tuyệt đối. Vì vậy, gọi String.getClassLoader().getResourceAsStream("myfile.txt")String.getClassLoader().getResourceAsStream("/myfile.txt")cả hai sẽ tìm kiếm một tệp trong đường dẫn lớp của bạn tại vị trí sau : ./myfile.txt.

Mỗi lần tôi đề cập đến một vị trí trong bài đăng này, nó có thể là một vị trí trong chính hệ thống tệp của bạn hoặc bên trong tệp jar tương ứng, tùy thuộc vào Class và / hoặc ClassLoader mà bạn đang tải tài nguyên từ đó.

Trong trường hợp của bạn, bạn đang tải lớp từ Máy chủ ứng dụng, vì vậy bạn nên sử dụng Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)thay vì this.getClass().getClassLoader().getResourceAsStream(fileName). this.getClass().getResourceAsStream()cũng sẽ làm việc

Đọc bài viết này để biết thêm thông tin chi tiết về vấn đề cụ thể đó.


Cảnh báo cho người dùng Tomcat 7 trở xuống

Một trong những câu trả lời cho câu hỏi này nói rằng lời giải thích của tôi dường như không đúng với Tomcat 7. Tôi đã cố gắng nhìn xung quanh để xem tại sao lại như vậy.

Vì vậy, tôi đã xem mã nguồn của Tomcat WebAppClassLoadercho một số phiên bản của Tomcat. Việc triển khai findResource(String name)(chịu trách nhiệm hoàn toàn trong việc tạo URL cho tài nguyên được yêu cầu) gần như giống hệt nhau trong Tomcat 6 và Tomcat 7, nhưng khác với Tomcat 8.

Trong phiên bản 6 và 7, việc triển khai không cố gắng bình thường hóa tên tài nguyên. Điều này có nghĩa là trong các phiên bản này, classLoader.getResourceAsStream("/resource.txt")có thể không tạo ra kết quả giống như classLoader.getResourceAsStream("resource.txt")sự kiện mặc dù vậy (vì đó là những gì Javadoc chỉ định). [mã nguồn]

Trong phiên bản 8, tên tài nguyên được chuẩn hóa để đảm bảo rằng phiên bản tuyệt đối của tên tài nguyên là tên được sử dụng. Do đó, trong Tomcat 8, hai cuộc gọi được mô tả ở trên phải luôn trả về cùng một kết quả. [mã nguồn]

Do đó, bạn phải hết sức cẩn thận khi sử dụng ClassLoader.getResourceAsStream()hoặc Class.getResourceAsStream()trên các phiên bản Tomcat sớm hơn 8. Và bạn cũng phải ghi nhớ rằng class.getResourceAsStream("/resource.txt")thực sự gọi classLoader.getResourceAsStream("resource.txt")(hàng đầu /bị tước).


2
Tôi khá chắc chắn rằng getClass().getResourceAsStream("/myfile.txt")cư xử khác với getClassLoader().getResourceAsStream("/myfile.txt").
Brian Gordon

@BrianGordon: Họ không cư xử khác biệt. Trên thực tế, javadoc cho Class.getResourceAsStream (Chuỗi) cho biết điều sau: "Phương thức này ủy quyền cho trình nạp lớp của đối tượng này.", Và sau đó đưa ra một loạt các quy tắc về cách nó chuyển đổi một đường dẫn tương đối thành một đường dẫn tuyệt đối trước khi ủy thác cho bộ nạp lớp.
LordOfThePigs

@LordOfThePigs Nhìn vào nguồn thực tế. Class.getResourceAsStream loại bỏ dấu gạch chéo về phía trước nếu bạn cung cấp một đường dẫn tuyệt đối.
Brian Gordon

4
@BrianGordon: Điều này làm cho nó hoạt động giống hệt như ClassLoader.getResourceAsStream () vì sau này diễn giải tất cả các đường dẫn là tuyệt đối, cho dù chúng có bắt đầu bằng dấu gạch chéo hàng đầu hay không. Vì vậy, miễn là đường dẫn của bạn là tuyệt đối, cả hai phương thức đều hoạt động giống hệt nhau. Nếu đường dẫn của bạn là tương đối, thì hành vi là khác nhau.
LordOfThePigs

Tôi không thể tìm getClassLoader()của String, nó là một sai lầm hoặc cần một phần mở rộng?
AaA

21

Sử dụng MyClass.class.getClassLoader().getResourceAsStream(path)để tải tài nguyên liên quan đến mã của bạn. Sử dụng MyClass.class.getResourceAsStream(path)làm lối tắt và cho các tài nguyên được đóng gói trong gói của lớp bạn.

Sử dụng Thread.currentThread().getContextClassLoader().getResourceAsStream(path)để có được tài nguyên là một phần của mã máy khách, không giới hạn chặt chẽ với mã gọi. Bạn nên cẩn thận với điều này vì trình nạp lớp ngữ cảnh của luồng có thể trỏ vào bất cứ thứ gì.


6

Java cũ đơn giản trên Java 7 cũ đơn giản và không có phụ thuộc nào khác thể hiện sự khác biệt ...

Tôi đặt file.txtvào c:\temp\và tôi đặt c:\temp\trên classpath.

Chỉ có một trường hợp có sự khác biệt giữa hai cuộc gọi.

class J {

 public static void main(String[] a) {
    // as "absolute"

    // ok   
    System.err.println(J.class.getResourceAsStream("/file.txt") != null); 

    // pop            
    System.err.println(J.class.getClassLoader().getResourceAsStream("/file.txt") != null); 

    // as relative

    // ok
    System.err.println(J.class.getResourceAsStream("./file.txt") != null); 

    // ok
    System.err.println(J.class.getClassLoader().getResourceAsStream("./file.txt") != null); 

    // no path

    // ok
    System.err.println(J.class.getResourceAsStream("file.txt") != null); 

   // ok
   System.err.println(J.class.getClassLoader().getResourceAsStream("file.txt") != null); 
  }
}

rất cảm ơn, đối với tôi chỉ hoạt động 'J. class.getResourceAsStream ("file.txt")'
abbasalim

3

Tất cả những câu trả lời quanh đây, cũng như câu trả lời trong câu hỏi này , đề nghị rằng việc tải các URL tuyệt đối, như "/foo/bar.properies" cũng được xử lý như vậy class.getResourceAsStream(String)class.getClassLoader().getResourceAsStream(String). Đây không phải là trường hợp, ít nhất là không có trong cấu hình / phiên bản Tomcat của tôi (hiện là 7.0.40).

MyClass.class.getResourceAsStream("/foo/bar.properties"); // works!  
MyClass.class.getClassLoader().getResourceAsStream("/foo/bar.properties"); // does NOT work!

Xin lỗi, tôi hoàn toàn không có lời giải thích thỏa mãn, nhưng tôi đoán rằng tomcat thực hiện các thủ đoạn bẩn thỉu và ma thuật đen của anh ta với các trình nạp lớp và gây ra sự khác biệt. Tôi luôn luôn sử dụng class.getResourceAsStream(String)trong quá khứ và không có vấn đề gì.

PS: Tôi cũng đã đăng cái này lên đây


Có lẽ tomcat quyết định không tôn trọng đặc điểm kỹ thuật và không coi tất cả các đường dẫn ClassLoader.getResourceAsStream()là tuyệt đối? Điều này là hợp lý bởi vì như đã đề cập trong một số nhận xét ở trên, Class.getResourceAsStreamthực sự gọi getClassLoader (). GetResourceAsStream` nhưng loại bỏ bất kỳ dấu gạch chéo hàng đầu nào.
LordOfThePigs

Sau khi kiểm tra mã nguồn của Java SE, tôi nghĩ rằng tôi giữ câu trả lời: Cả hai Class.getResourceAsStream()ClassLoader.getResourceAsStream()cuối cùng đều gọi ClassLoader.findResource()đó là một phương thức được bảo vệ có triển khai mặc định là trống, nhưng javadoc nói rõ "Việc triển khai trình tải lớp nên ghi đè phương thức này để chỉ định nơi để tìm tài nguyên ". Tôi nghi ngờ việc thực hiện phương pháp đặc biệt này của tomcat có thể là thiếu sót.
LordOfThePigs

Tôi cũng đã so sánh việc triển khai WebAppClassLoader.findResource(String name)trong Tomcat 7 với Tomcat 8 và có vẻ như có một sự khác biệt chính. Tomcat 8 rõ ràng bình thường hóa tên tài nguyên bằng cách thêm một đầu /nếu nó không chứa bất kỳ, điều này làm cho tất cả các tên tuyệt đối. Tomcat 7 không. Đó rõ ràng là một lỗi trong Tomcat 7
LordOfThePigs

Tôi đã thêm một đoạn về điều đó trong câu trả lời của tôi.
LordOfThePigs

0

Sau khi thử một số cách để tải tệp mà không thành công, tôi nhớ rằng tôi có thể sử dụng FileInputStream, nó hoạt động hoàn hảo.

InputStream is = new FileInputStream("file.txt");

Đây là một cách khác để đọc tệp vào một tệp InputStream, nó đọc tệp từ thư mục hiện đang chạy.


Nó không phải là một tập tin, nó là một tài nguyên. Trả lời không đúng.
Hầu tước Lorne

1
@EJP Tôi kết thúc câu trả lời SO này, tìm cách tải tệp, mà không biết sự khác biệt giữa tệp và tài nguyên. Tôi sẽ không xóa câu trả lời của mình vì nó có thể giúp đỡ người khác.
António Almeida

-3

Nó hoạt động, hãy thử điều này:

InputStream in_s1 =   TopBrandData.class.getResourceAsStream("/assets/TopBrands.xml");
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.