Risorsa Java come file


122

Esiste un modo in Java per costruire un'istanza di file su una risorsa recuperata da un jar tramite il classloader?

La mia applicazione utilizza alcuni file dal jar (impostazione predefinita) o da una directory del file system specificata in fase di esecuzione (input dell'utente). Sto cercando un modo coerente per
a) caricare questi file come flusso
b) elencare i file nella directory definita dall'utente o nella directory nel jar rispettivamente

Modifica: a quanto pare, l'approccio ideale sarebbe stare alla larga da java.io.File del tutto. C'è un modo per caricare una directory dal classpath ed elencarne il contenuto (file / entità in essa contenuti)?

Risposte:


58

ClassLoader.getResourceAsStreame Class.getResourceAsStreamsono sicuramente la strada da percorrere per caricare i dati delle risorse. Tuttavia, non credo ci sia alcun modo per "elencare" il contenuto di un elemento del classpath.

In alcuni casi ciò potrebbe essere semplicemente impossibile, ad esempio a ClassLoader potrebbe generare dati al volo, in base al nome della risorsa richiesto. Se guardi l' ClassLoaderAPI (che è fondamentalmente ciò attraverso cui funziona il meccanismo del percorso di classe) vedrai che non c'è niente da fare quello che vuoi.

Se sai di avere effettivamente un file jar, puoi caricarlo con ZipInputStreamper scoprire cosa è disponibile. Significa che avrai un codice diverso per directory e file jar.

Un'alternativa, se i file vengono prima creati separatamente, consiste nell'includere una sorta di file manifest contenente l'elenco delle risorse disponibili. Raggruppalo nel file jar o includilo nel file system come file e caricalo prima di offrire all'utente una scelta di risorse.


3
Sono d'accordo che sia fastidioso, ma rende ClassLoader più ampiamente applicabile in altri modi. Ad esempio, è facile scrivere un "web classloader" perché il web è utile per il recupero dei file, ma in genere non elenca i file.
Jon Skeet

Sembra che se la risorsa che stai cercando di caricare è molto più grande di 1MiB InputStream, ottieni dalle getResourceAsStreamfermate per recuperare i byte della risorsa dopo quella dimensione e invece restituisce 0 se e solo se è contenuta in un filesystem compresso come un jar. In questo caso, sembra che tu sia costretto a utilizzare getResourcee caricare il file indipendentemente da questo.
mgttlinger

1
@mgttlinger: Mi sembra improbabile ... probabilmente vale la pena pubblicare una nuova domanda con una riproduzione se puoi.
Jon Skeet

Il problema era dovuto al readmetodo che decideva quanti dati leggere (non ne ero a conoscenza). E sembra che l'intero file venga letto se il file letto si trova in una cartella normale. Se il file si trova all'interno di un jar perché è stato impacchettato, ne legge solo parti.
mgttlinger

1
@mgttlinger: No, non leggerà solo parti di esso, ma potrebbe non riempire l'intero buffer fornito su ogni chiamata di lettura. Come sempre InputStream, se vuoi leggere l'intera risorsa, dovresti ripetere il ciclo finché non readrestituisce -1.
Jon Skeet

98

Ho avuto lo stesso problema e sono stato in grado di utilizzare quanto segue:

// 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
Questo approccio non funziona con i JAR in classpath, solo con file e directory
yegor256

1
@ yegor256 Se il tuo file è in un jar puoi creare un FileSystemoggetto e ricavarne il file Path. Quindi puoi leggerlo come al solito. (vedi stackoverflow.com/a/22605905/1876344 )
mgttlinger

53

Ecco un po 'di codice da una delle mie applicazioni ... Fammi sapere se si adatta alle tue esigenze. Puoi usarlo se conosci il file che vuoi usare.

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

Spero che aiuti.


Sì, ma toURI()fallirà.
Olivier Faucheux

10

Un modo affidabile per costruire un'istanza di file su una risorsa recuperata da un jar è copiare la risorsa come flusso in un file temporaneo (il file temporaneo verrà eliminato quando la JVM esce):

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

Prova questo:

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

Sono disponibili più metodi, ad esempio vedere qui: http://www.javaworld.com/javaworld/javaqa/2003-08/01-qa-0808-property.html


Grazie per la risposta. Conosco getResourceAsStream, ma non credo di poter caricare una directory dal classpath attraverso quello.
Mantrum

Nella directory Java c'è un file (radici UNIX immagino) quindi dovresti almeno provare.
topchef

Penso che per elencare i file in una directory avrai bisogno di usare java.io.File. Puoi cercare i file con ClassLoader.findResource che restituisce un URL che può essere passato a File.
Nathan Voxland

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.