getResourceAsStream () vs FileInputStream

173

Eu estava tentando carregar um arquivo em um aplicativo da web e estava recebendo uma FileNotFoundexceção quando o usei FileInputStream. No entanto, usando o mesmo caminho, consegui carregar o arquivo quando o fiz getResourceAsStream(). Qual é a diferença entre os dois métodos e por que um funciona enquanto o outro não?

Vivin Paliath
fonte

Respostas:

256

As java.io.Fileconsorts e atuam no sistema de arquivos do disco local. A causa raiz do seu problema é que os caminhos relativosjava.io são dependentes do diretório de trabalho atual. Ou seja, o diretório a partir do qual a JVM (no seu caso: a do servidor da web) é iniciada. Por exemplo, isso pode ser C:\Tomcat\binou algo completamente diferente, mas, portanto, não C:\Tomcat\webapps\contextname ou o que você espera que seja. Em um projeto normal do Eclipse, isso seria C:\Eclipse\workspace\projectname. Você pode aprender sobre o diretório de trabalho atual da seguinte maneira:

System.out.println(new File(".").getAbsolutePath());

No entanto, o diretório de trabalho não é de forma programável controlável. Você realmente deve preferir usar caminhos absolutos na FileAPI em vez de caminhos relativos. Por exemplo C:\full\path\to\file.ext.

Você não deseja codificar ou adivinhar o caminho absoluto nos aplicativos Java (web). Isso é apenas um problema de portabilidade (ou seja, é executado no sistema X, mas não no sistema Y). A prática normal é colocar esse tipo de recursos no caminho de classe ou adicionar seu caminho completo ao caminho de classe (em um IDE como o Eclipse, essa é a srcpasta e o "caminho de construção", respectivamente). Desta forma, você pode pegá-los com a ajuda do ClassLoaderby ClassLoader#getResource()ou ClassLoader#getResourceAsStream(). Ele é capaz de localizar arquivos relativos à "raiz" do caminho de classe, como você descobriu por coincidência. Nos aplicativos da web (ou em qualquer outro aplicativo que use vários carregadores de classe), é recomendável usar o valor ClassLoaderretornado Thread.currentThread().getContextClassLoader()para que você possa procurar "fora" do contexto do aplicativo da web.

Outra alternativa nos aplicativos da web é a ServletContext#getResource()e sua contraparte ServletContext#getResourceAsStream(). É capaz de acessar arquivos localizados na webpasta pública do projeto webapp, incluindo a /WEB-INFpasta. O ServletContextestá disponível em servlets pelo getServletContext()método herdado , você pode chamá-lo como está.

Veja também:

BalusC
fonte
5
@khylo: related: stackoverflow.com/questions/7952090/…
BalusC
27

getResourceAsStream é a maneira certa de fazer isso para aplicativos da web (como você já aprendeu).

O motivo é que a leitura do sistema de arquivos não funcionará se você empacotar seu aplicativo da web em um WAR. Essa é a maneira correta de empacotar um aplicativo da web. É portátil dessa maneira, porque você não depende de um caminho absoluto de arquivo ou do local em que o servidor de aplicativos está instalado.

duffymo
fonte
3
+1 - embora "não possa funcionar" seja muito forte. (Leitura do sistema de arquivos pode ser feita para o trabalho, mas fazê-lo portably é um complicado ... e um código muito mais, especialmente se o recurso está em um JAR.)
Stephen C
1
duffy, resposta muito boa e você explicou qual foi o meu erro, mas o BalusC entrou em muitos detalhes - acho que a resposta dele seria útil para pessoas que também gostariam de conhecer os detalhes internos. Espero que você não se importe que eu mude a resposta aceita para a dele!
Vivin Paliath
@ Stephen - Eu não acho que "não pode funcionar" é muito forte. Mesmo algo tão simples como ser implantado em dois servidores diferentes com caminhos diferentes para o servidor de aplicativos o interromperá. O ponto é que você precisa tornar seu WAR o mais independente possível. Seu ponto de vista está correto, mas eu vou manter minha redação.
Duffymo
14

FileInputStream carregará o caminho do arquivo que você passa para o construtor como relativo do diretório de trabalho do processo Java. Geralmente em um contêiner da web, isso é algo como a binpasta.

getResourceAsStream()carregará um caminho de arquivo relativo do caminho de classe do seu aplicativo .

matt b
fonte
12

A FileInputStreamclasse trabalha diretamente com o sistema de arquivos subjacente. Se o arquivo em questão não estiver fisicamente presente lá, ele não será aberto. O getResourceAsStream()método funciona de maneira diferente. Ele tenta localizar e carregar o recurso usando o ClassLoaderda classe em que é chamado. Isso permite encontrar, por exemplo, recursos incorporados aos jararquivos.

Dirk
fonte
Bem, os arquivos dentro de um jar ainda estão fisicamente "presentes" em um sistema de arquivos, apenas contidos em outros arquivos
matt b
1
Bem, sim, claro. Porém, eles geralmente não são vistos como entidades independentes no sistema de arquivos, a menos que você saiba o jarformato do arquivo e suas implicações. E em Java, o apropriado ClassLoaderpode ter esse conhecimento, enquanto um claro FileInputStreamcertamente não tem.
Dirk
7

classname.getResourceAsStream () carrega um arquivo através do carregador de classe de classname. Se a classe vier de um arquivo jar, é daí que o recurso será carregado.

FileInputStream é usado para ler um arquivo do sistema de arquivos.

Lachlan Roche
fonte
0

Estou aqui separando os usos, marcando-os como Leitura de arquivo (java.io) e Leitura de recurso (ClassLoader.getResourceAsStream ()).

Leitura de arquivo - 1. Funciona no sistema de arquivos local. 2. Tenta localizar o arquivo solicitado do diretório ativado pela JVM atual como raiz 3. Idealmente bom ao usar arquivos para processamento em um local predeterminado, como / dev / files ou C: \ Data.

Leitura de recurso - 1. Funciona no caminho da classe 2. Tenta localizar o arquivo / recurso no caminho de classe atual ou pai do carregador de classes. 3. Idealmente bom ao tentar carregar arquivos de arquivos compactados como war ou jar.

Aditya Bhuyan
fonte