Java-Ressource als Datei

122

Gibt es in Java eine Möglichkeit, eine Dateiinstanz für eine Ressource zu erstellen, die über den Classloader aus einem JAR abgerufen wurde?

Meine Anwendung verwendet einige Dateien aus dem JAR (Standard) oder aus einem zur Laufzeit angegebenen Dateisystemverzeichnis (Benutzereingabe). Ich suche nach einer konsistenten Möglichkeit,
a) diese Dateien als Stream zu laden,
b) die Dateien im benutzerdefinierten Verzeichnis bzw. im Verzeichnis im JAR aufzulisten

Bearbeiten: Anscheinend wäre der ideale Ansatz, sich von java.io.File insgesamt fernzuhalten. Gibt es eine Möglichkeit, ein Verzeichnis aus dem Klassenpfad zu laden und dessen Inhalt (darin enthaltene Dateien / Entitäten) aufzulisten?

Mantrum
quelle

Antworten:

58

ClassLoader.getResourceAsStreamund Class.getResourceAsStreamsind definitiv der richtige Weg, um die Ressourcendaten zu laden. Ich glaube jedoch nicht, dass es eine Möglichkeit gibt, den Inhalt eines Elements des Klassenpfads "aufzulisten".

In einigen Fällen kann dies einfach unmöglich sein - zum Beispiel eine ClassLoader könnte Daten on the fly generieren, basierend auf , was es Ressource - Namen gefragt wird . Wenn Sie sich die ClassLoaderAPI ansehen (was im Grunde der Klassenpfadmechanismus ist), werden Sie feststellen, dass Sie nichts tun können, was Sie wollen.

Wenn Sie wissen, dass Sie tatsächlich eine JAR-Datei haben, können Sie diese laden, ZipInputStreamum herauszufinden, was verfügbar ist. Dies bedeutet jedoch, dass Sie unterschiedlichen Code für Verzeichnisse und JAR-Dateien haben.

Eine Alternative, wenn die Dateien zuerst separat erstellt werden, besteht darin, eine Art Manifestdatei einzuschließen, die die Liste der verfügbaren Ressourcen enthält. Bündeln Sie das in der JAR-Datei oder fügen Sie es als Datei in das Dateisystem ein und laden Sie es, bevor Sie dem Benutzer eine Auswahl an Ressourcen anbieten.

Jon Skeet
quelle
3
Ich bin damit einverstanden, dass es ärgerlich ist - aber es macht ClassLoader auf andere Weise breiter anwendbar. Zum Beispiel ist es einfach einen „Web - Classloader“ zu schreiben , weil die Bahn gut ist , um Dateien für das Abrufen, aber es funktioniert nicht in der Regel Liste Dateien.
Jon Skeet
Es scheint, als ob die Ressource, die Sie laden möchten, viel größer als 1 MB ist, die InputStreamSie von getResourceAsStreamStopps erhalten, um die Bytes der Ressource nach dieser Größe abzurufen, und stattdessen 0 zurückgibt, wenn sie in einem komprimierten Dateisystem wie einem JAR enthalten ist. getResourceIn diesem Fall scheinen Sie gezwungen zu sein, die Datei unabhängig davon zu verwenden und zu laden.
Mgttlinger
1
@mgttlinger: Das klingt für mich unwahrscheinlich ... wahrscheinlich lohnt es sich, eine neue Frage mit einem Repro zu stellen, wenn Sie können.
Jon Skeet
Das Problem war auf die readMethode zurückzuführen, mit der entschieden wurde, wie viele Daten gelesen werden sollten (das war mir nicht bewusst). Und es scheint, dass die gesamte Datei gelesen wird, wenn sich die gelesene Datei in einem regulären Ordner befindet. Befindet sich die Datei in einem JAR, weil sie verpackt wurde, werden nur Teile davon gelesen.
Mgttlinger
1
@mgttlinger: Nein, es werden nicht nur Teile davon gelesen, sondern möglicherweise nicht der gesamte Puffer, der bei jedem Leseaufruf bereitgestellt wird. Wie immer InputStreamsollten Sie, wenn Sie die gesamte Ressource lesen möchten, eine Schleife ausführen, bis read-1 zurückgegeben wird.
Jon Skeet
98

Ich hatte das gleiche Problem und konnte Folgendes verwenden:

// 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()
Chris Conway
quelle
45
Dieser Ansatz funktioniert nicht mit JARs im Klassenpfad, nur mit Dateien und Verzeichnissen
yegor256
1
@ yegor256 Wenn sich Ihre Datei in einem Glas befindet, können Sie ein FileSystemObjekt erstellen und das Pathdaraus abrufen . Dann können Sie es wie gewohnt lesen. (siehe stackoverflow.com/a/22605905/1876344 )
mgttlinger
53

Hier ist ein bisschen Code aus einer meiner Anwendungen ... Lassen Sie mich wissen, ob er Ihren Anforderungen entspricht. Sie können dies verwenden, wenn Sie die Datei kennen, die Sie verwenden möchten.

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

Hoffentlich hilft das.

Maurice Rogers
quelle
Ja, das tut es, aber es toURI()wird scheitern.
Olivier Faucheux
10

Eine zuverlässige Methode zum Erstellen einer Dateiinstanz für eine aus einem JAR abgerufene Ressource besteht darin, die Ressource als Stream in eine temporäre Datei zu kopieren (die temporäre Datei wird beim Beenden der JVM gelöscht):

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;
    }
}
Lukas Masuch
quelle
4

Versuche dies:

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

Es stehen weitere Methoden zur Verfügung, z. B. hier: http://www.javaworld.com/javaworld/javaqa/2003-08/01-qa-0808-property.html

Spitzenkoch
quelle
Vielen Dank für Ihre Antwort. Ich kenne getResourceAsStream, aber ich glaube nicht, dass ich dadurch ein Verzeichnis aus dem Klassenpfad laden kann.
Mantrum
Im Java-Verzeichnis befindet sich eine Datei (UNIX-Roots, denke ich), also sollten Sie es zumindest versuchen.
Topchef
Ich denke, um Dateien in einem Verzeichnis aufzulisten, müssen Sie java.io.File verwenden. Sie können Dateien mit ClassLoader.findResource nachschlagen, die eine URL zurückgibt, die an File übergeben werden kann.
Nathan Voxland
2

Dies ist eine Option: http://www.uofr.net/~greg/java/get-resource-listing.html

Fisch
quelle
4
Während dieser Link die Frage beantworten kann, ist es vorzuziehen , die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen, da sich ein Link ändern oder die verlinkte Seite entfernt werden kann, wodurch die Antwort unbrauchbar wird.
JonasCz