Diversi modi di caricare un file come InputStream


216

Qual è la differenza tra:

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

e

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

e

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

Quando ognuno è più appropriato da usare rispetto agli altri?

Il file che voglio leggere è nel percorso di classe come la mia classe che legge il file. La mia classe e il file sono nello stesso jar e impacchettati in un file EAR e distribuiti in WebSphere 6.1.

Risposte:


289

Ci sono sottili differenze su come fileNameviene interpretato il passaggio. Fondamentalmente, hai 2 metodi diversi: ClassLoader.getResourceAsStream()e Class.getResourceAsStream(). Questi due metodi individueranno la risorsa in modo diverso.

In Class.getResourceAsStream(path), il percorso viene interpretato come un percorso locale al pacchetto della classe da cui lo si chiama. Per esempio chiamata, String.getResourceAsStream("myfile.txt")sarà cercare un file nel classpath al seguente indirizzo: "java/lang/myfile.txt". Se il tuo percorso inizia con un /, allora sarà considerato un percorso assoluto e inizierà la ricerca dalla radice del percorso di classe. Quindi la chiamata String.getResourceAsStream("/myfile.txt")esaminerà la seguente posizione nel percorso della classe ./myfile.txt.

ClassLoader.getResourceAsStream(path)considererà tutti i percorsi come percorsi assoluti. Quindi chiamare String.getClassLoader().getResourceAsStream("myfile.txt")e String.getClassLoader().getResourceAsStream("/myfile.txt")saranno entrambi cercare un file nel classpath al seguente indirizzo: ./myfile.txt.

Ogni volta che menziono una posizione in questo post, potrebbe essere una posizione nel tuo filesystem stesso o all'interno del file jar corrispondente, a seconda della classe e / o ClassLoader da cui stai caricando la risorsa.

Nel tuo caso, stai caricando la classe da un Application Server, quindi dovresti usare Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)invece di this.getClass().getClassLoader().getResourceAsStream(fileName). this.getClass().getResourceAsStream()funzionerà anche.

Leggi questo articolo per informazioni più dettagliate su quel particolare problema.


Avviso per gli utenti di Tomcat 7 e versioni precedenti

Una delle risposte a questa domanda afferma che la mia spiegazione sembra essere errata per Tomcat 7. Ho cercato di guardarmi intorno per capire perché sarebbe così.

Quindi ho esaminato il codice sorgente di Tomcat WebAppClassLoaderper diverse versioni di Tomcat. L'implementazione di findResource(String name)(che è assolutamente responsabile della produzione dell'URL per la risorsa richiesta) è praticamente identica in Tomcat 6 e Tomcat 7, ma è diversa in Tomcat 8.

Nelle versioni 6 e 7, l'implementazione non tenta di normalizzare il nome della risorsa. Ciò significa che in queste versioni, classLoader.getResourceAsStream("/resource.txt")potrebbe non produrre lo stesso risultato classLoader.getResourceAsStream("resource.txt")dell'evento, anche se dovrebbe (dal momento che ciò che Javadoc specifica). [codice sorgente]

Nella versione 8, tuttavia, il nome della risorsa è normalizzato per garantire che la versione assoluta del nome della risorsa sia quella utilizzata. Pertanto, in Tomcat 8, le due chiamate sopra descritte dovrebbero sempre restituire lo stesso risultato. [codice sorgente]

Di conseguenza, devi fare molta attenzione quando usi ClassLoader.getResourceAsStream()o Class.getResourceAsStream()su versioni Tomcat precedenti alla 8. E devi anche tenere presente che in class.getResourceAsStream("/resource.txt")realtà chiama classLoader.getResourceAsStream("resource.txt")(il lead /è privato).


2
Sono abbastanza sicuro che getClass().getResourceAsStream("/myfile.txt")si comporti diversamente da getClassLoader().getResourceAsStream("/myfile.txt").
Brian Gordon,

@BrianGordon: non si comportano diversamente. In realtà, il javadoc per Class.getResourceAsStream (String) dice la seguente cosa: "Questo metodo delega al programma di caricamento classi di questo oggetto.", E quindi fornisce un sacco di regole su come converte un percorso relativo in un percorso assoluto prima di delegare al classloader.
LordOfThePigs,

@LordOfThePigs Guarda la fonte reale. Class.getResourceAsStream elimina la barra in avanti principale se si fornisce un percorso assoluto.
Brian Gordon,

4
@BrianGordon: il che lo fa comportare esattamente come ClassLoader.getResourceAsStream () poiché quest'ultimo interpreta tutti i percorsi come assoluti, indipendentemente dal fatto che inizino o meno con una barra. Quindi, purché il percorso sia assoluto, entrambi i metodi si comportano in modo identico. Se il tuo percorso è relativo, il comportamento è diverso.
LordOfThePigs il

Non riuscivo a trovare getClassLoader()di String, si tratta di un errore o bisogno di una proroga?
AaA,

21

Utilizzare MyClass.class.getClassLoader().getResourceAsStream(path)per caricare la risorsa associata al proprio codice. Usa MyClass.class.getResourceAsStream(path)come scorciatoia e per le risorse impacchettate nel pacchetto della tua classe.

Utilizzare Thread.currentThread().getContextClassLoader().getResourceAsStream(path)per ottenere risorse che fanno parte del codice client, non strettamente legate al codice chiamante. Dovresti stare attento con questo poiché il caricatore di classi di contesto di thread potrebbe puntare a qualsiasi cosa.


6

Semplicemente vecchio Java su semplice vecchio Java 7 e nessun'altra dipendenza dimostra la differenza ...

Ho messo file.txtin c:\temp\e ho messo c:\temp\nel classpath.

C'è solo un caso in cui c'è una differenza tra le due chiamate.

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); 
  }
}

molto grazie, per me ha funzionato solo 'J.class.getResourceAsStream ("file.txt")'
abbasalim

3

Tutte queste risposte qui intorno, così come le risposte a questa domanda , suggeriscono che il caricamento di URL assoluti, come "/foo/bar.properties", ha trattato lo stesso di class.getResourceAsStream(String)e class.getClassLoader().getResourceAsStream(String). Questo NON è il caso, almeno non nella mia configurazione / versione di Tomcat (attualmente 7.0.40).

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

Mi dispiace, non ho assolutamente una spiegazione soddisfacente, ma immagino che Tomcat faccia trucchi sporchi e la sua magia nera con i caricatori di classe e faccia la differenza. Ho sempre usato class.getResourceAsStream(String)in passato e non ho avuto problemi.

PS: l'ho anche pubblicato qui


Forse Tomcat decide di non rispettare le specifiche e non considera tutti i percorsi passati ClassLoader.getResourceAsStream()come assoluti? Ciò è plausibile perché, come menzionato in alcuni commenti sopra, in Class.getResourceAsStreamrealtà chiama getClassLoader (). GetResourceAsStream` ma rimuove qualsiasi barra iniziale.
LordOfThePigs il

Dopo aver verificato il codice sorgente di Java SE, penso di avere la risposta: Entrambi Class.getResourceAsStream()e ClassLoader.getResourceAsStream()alla fine finiscono per chiamare ClassLoader.findResource()quale è un metodo protetto la cui implementazione predefinita è vuota, ma il cui javadoc afferma esplicitamente "Le implementazioni del caricatore di classe dovrebbero sovrascrivere questo metodo per specificare dove per trovare risorse ". Sospetto che l'implementazione di Tomcat di questo particolare metodo possa essere imperfetta.
LordOfThePigs il

Ho anche confrontato l'implementazione di WebAppClassLoader.findResource(String name)in Tomcat 7 con quella di Tomcat 8 , e sembra che ci sia una differenza fondamentale. Tomcat 8 normalizza esplicitamente il nome della risorsa aggiungendo un vantaggio /se non ne contiene, il che rende tutti i nomi assoluti. Tomcat 7 no. Questo è chiaramente un bug in Tomcat 7
LordOfThePigs

Ho aggiunto un paragrafo su questo nella mia risposta.
LordOfThePigs il

0

Dopo aver provato alcuni modi per caricare il file senza successo, mi sono ricordato che avrei potuto usare FileInputStream, che ha funzionato perfettamente.

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

Questo è un altro modo per leggere un file in un InputStream, legge il file dalla cartella attualmente in esecuzione.


Non è un file, è una risorsa. La risposta non è corretta
Marchese di Lorne,

1
@EJP Finisco in questa risposta SO, alla ricerca di modi per caricare un file, senza conoscere la differenza tra un file e una risorsa. Non eliminerò la mia risposta perché potrebbe aiutare gli altri.
António Almeida,

-3

Funziona, prova questo:

InputStream in_s1 =   TopBrandData.class.getResourceAsStream("/assets/TopBrands.xml");
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.