Stream closed - Obwohl nicht manuell geschlossen!

Hallo erstmal!

Ich versuche eine Methode zu schreiben, welche Zip-Files rekursiv durchsucht, und darin enthaltene Textdateien nach einem bestimmten String durchsucht.

Das ganze funktioniert, bis die Methode die 3. Verschachtelung erreicht, (also eine Zip-Datei in einer Zip-Datei welche wiederum in einem Zip-File steckt.
Dann krieg ich folgende Exception: java.io.IOException: Stream closed

Wobei der Compiler auf folgende Zeile verweist:
while ((entry = archiveStream.getNextEntry()) != null) {

Ich kapier einfach nicht wieso er den Stream schliesst… bin mit meinem (geringen) Latein am Ende, habe auch schon Stunden Google gequält…
Bin also für jeden Denkanstoss oder Vorschlag extrem dankbar! :wink:

Hier was ich bisher hab:

           
            int numberOfMatches = 0;
           
            String recursionInfo = "(occuring in '" + zipName + " " + archiveLocation;
           
            ZipInputStream archiveStream = null;
            Reader decoder = null;
            BufferedReader bufferedR = null;
           
            try {
                archiveStream = new ZipInputStream(bufferedStream);
           
                decoder = new InputStreamReader(archiveStream);
                bufferedR = new BufferedReader(decoder);
               
                String actualLine;
                ZipEntry entry;
                Enumeration<ZipArchiveEntry> entries = zip.getEntries();
                System.out.println(entries);
               
                while ((entry = archiveStream.getNextEntry()) != null) {
                   
                    String entryName = entry.getName();
                   
                    if (entryName.endsWith(".zip"))
                    {  
                        String tempPath = archiveLocation += "-->" + entryName + " ";
                       
                        ZipArchiveEntry zipEntry = zip.getEntry(entryName);
                       
                        InputStream entryStream = null;
                        BufferedInputStream buffered = null;
                       
                        try {
                            entryStream = zip.getInputStream(zipEntry);
                           
                            buffered = new BufferedInputStream(entryStream);
                           
                            if((buffered != null)){
                                numberOfMatches += searchZip(buffered, searched, zip, zipName, tempPath);
                            }  
                        }
                        catch (Exception e) {
                            e.printStackTrace();
                        }
                        finally{
                            entryStream.close();
                            buffered.close();
                        }
                    }
                    int line = 0;
                   
                    while ((actualLine = bufferedR.readLine()) != null) {
                        line++;
           
                        if(actualLine.toLowerCase().contains(searched.toLowerCase()))
                        {
                            numberOfMatches +=1;
                           
                            System.out.println("Found   *" + searched + "*   in file   '" + entryName + "' on line  -" + line + "-  "+ recursionInfo  +"')
");
                        }
                    }
                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }
            finally{
                decoder.close();
                bufferedR.close();
                archiveStream.close();
            }
            return numberOfMatches;
        }```

Hier noch der Aufbau der Test-Datei und die Ausgabe die ich in meinem (FX-GUI) kriege:

Das Problem ist, dass du der Methode einen Stream als Parameter durchgibst.
Schließt eine der Rekursionsebenen den Stream, ist dieser Stream auch für alle anderen Rekursionsebenen geschlossen und dann knallts.

Grundsätzlich gibt, dass man Streams genau da schließen sollte wo man sie auch aufgemacht hat. In deinem Fall wäre das da wo du die searchZip Methode aufrufst.
Ich würde daher mal versuchen den finally-Block zu entfernen und den BufferedInputStream dort zu schließen wo du searchZip aufrufst.

Der Code ist etwas wir und enthält einige überflüssige Zeilen.
Grundsätzlich ist das Problem, dass Du - wenn Du das rekursiv abwickeln willst - mit ZipInputStream Probleme bekommst.
http://java-performance.info/how-to-iterate-zip-file-records/
Die einzige Möglichkeit dürfte sein, die zip im zip temporär zu extrahieren (bei geringer größer ginge sicher auch diese einfach nur im Speicher zu halten) und dann auf die extrahierten zips die selbe Methode rekursiv anzuwenden.
Hinweis: Bei ineinander verschachtelten Streams reicht ein einmaliger Aufruf von close().

Danke für die raschen Antworten!

Stimmt, ist wahrscheinlich nicht die eleganteste Lösung… Aber was könnte ich sonst übergeben, wenn ich die entries nicht entpacken möchte?

Habe ich versucht, leider dasselbe Resultat… :frowning:

*** Edit ***

Gut möglich, wie erwähnt bin ich Anfänger und es besteht sicherlich viel Verbesserungspotential… :slight_smile:

Was ich nicht verstehe, ist wieso ich dann Probleme kriege. Weil die Dateien im zweiten Zip-File kann er ja ohne Probleme lesen, wieso kommt er dann nicht auch in die dritte Datei?
Entpacken möchte ich wenn irgendwie möglich vermeiden, da eine sehr grosse Zahl an Dateien dursucht werden soll… das Ganze würde dann extrem verlangsamt befürchte ich…
Gibt es wirklich keine andere Möglichkeit?

Danke für den Tip! Wenn ich das richtig verstehe, reicht es also, den „ersten“ übergebenen BufferedInputStream zu schliessen, aus dem alle anderen Streams ezeugt werden?

[QUOTE=egge89]
Danke für den Tip! Wenn ich das richtig verstehe, reicht es also, den “ersten” übergebenen BufferedInputStream zu schliessen, aus dem alle anderen Streams ezeugt werden?[/QUOTE]
wahrscheinlich richtig, edit: nein, auf den obersten Stream close, hier bufferedR, siehe im weiteren, aber:

aber mit erzeugten Unter-Streams auf ZipEntries (falls du das unter ‘erzeugen’ verstehst), eine Spezialsituation, hat der Tipp nicht zu tun,
sondern mit:

[quote=egge89;91383]entryStream = zip.getInputStream(zipEntry);
buffered = new BufferedInputStream(entryStream);[/quote]
Streams setzen auf andere auf, ein Verhalten dass man überall trifft,

hier reicht es, bei buffered close() aufzurufen,
und wenn nicht alles auf dem Kopf steht, dann wird der obere Stream auch bei den intern referenzierten, also entryStream close() aufrufen

womöglich bei Zip allerdings letztlich wirkungslos, also unnötig, aber wird auch nicht schaden,
jedenfalls reicht 1x auf den obersten, der verlinkten/ gewrappten, wenn nicht verschachtelten, Streams

bei allen API-Klassen wie BufferedInputStream kannst du dich auf das Weiterreichen des close-Befehls verlassen


decoder + bufferedR erscheinen mir auf eine Zip-Datei mehr als fragwürdig,
wieso holst du ZipArchiveEntry und Sub-Streams nur, wenn die Entry-Datei selber ein Zip ist?
der Inhalt des Entries kann doch nicht wichtig sein dafür, wie dieser gelesen wird,

die API mit ZipArchiveEntry kenne ich gerade nicht, aber finde Tutorials im Internet dazu,
oder probiere zumindest aus: IMMER ZipArchiveEntry + Sub-Streams, dann die Unterscheidung: Entry ist Zip -> Rekursionsaufruf,
sonst ERST NUN decoder + bufferedR auf die Sub-Streams (!) anlegen und auslesen

Ok, ich habe Vorschläge einfliessn lassen SlaterB.
Leider ändert dass immer noch nichts an der Tatsache, dass er die Stream closed Exception wirft… :frowning:

Könntest du mir noch erläutern wieso das fragwürdig ist? Wenn ich schon am lernen bin… :slight_smile:

Einen Input, wie ich die Aufgabe bewerkstelligen kann, ohne die Daten zu entpacken hättest du nicht per Zufall auf Lager? :wink:

wie schon geschrieben, eine Zip-Datei ist eine komplizierte Angelegenheit, die man nur nach Anleitung angehen sollte,
einen Reader direkt auf den Zip-Stream ist mir nicht bekannt, nur das ZipEntry-Verfahren,

aber du brauchst nicht zweimal das gleiche von mir hören sondern ein Tutorial,
Apache Commons?
http://developer-tips.hubpages.com/hub/Zipping-and-Unzipping-Nested-Directories-in-Java-using-Apache-Commons-Compress

wozu lamentieren, einfach an Standard halten
(Umbau für dein Programm, Unterscheidung Zip oder Datei lesen usw. kommt natürlich rein,
aber nur korrekte Sachen sinnvoll umbauen, nicht eigene Streams ohne Entry ausdenken)

hmm, was in einem Zip drin ist, muss erstmal raus, einen anderen Weg gibt es nicht,

die Dateien nicht auf der Festplatte zu speichern (falls du das ansprichst) sondern nur im Arbeitsspeicher zu halten, hast du schon geplant?,
auch die Rekursion grundsätzlich funktional,
nur noch Fehler ausbauen :wink:


ein sich anbietender Standardtipp bisher nicht erwähnt:
fange nicht mit einem dreistufigen Zip an, sondern einfache Fälle:

  1. ein Zip nur mit einer Textdatei drin (Zip A)
  2. ein Zip nur zwei Textdateien drin (Zip B)
  3. ein Zip mit Zip A drin
  4. ein Zip mit Zip A sowie noch einer Textdatei drin
    usw.

als Textdateien möglichst kurze, genau wissen was wo drinsteht, Treffer oder nicht


und durch meine Fragen zuvor fast gegeben, aber auch immer wichtig zu erwähnen:
alles loggen, nicht nur vermuten wo du bist, wo die Exception auftritt, sondern wissen!
Rekursion kann verräterisch sein, wenn zuwenig Ausgaben dann Fehler vielleicht drei Ebenen höher als letzte Ausgabe wer weiß wo unten,

nur Ausgabe bei ‚Found‘ ist zuwenig, du brauchst genaue Ablaufkontrolle, zu Beginn jeder Methode Ausgabe der aktuellen Datei,
falls als Zip erkannt eine Ausgabe zusammen mit Anzahl Einträge,

falls du ein Txt angehst dann auch erstmal Ausgabe dieser Entscheidung, und vorerst jede gelesene Textzeile ausgeben
damit du weiß ob alles (und NUR der Inhalt) der bekannten Dateien gelesen wird, oder wer weiß wie komisches aus dem Zip-Stream

für die Ablaufkontrolle im Zweifel auch Ausgaben direkt hinter rekursiven Aufrufen, damit man weiß dass zurückgekehrt,
normale Ausgaben zu Beginn des nächsten Schleifendurchlaufs, nächsten ZipEntrys, reichen aber oft auch

Das Problem hierbei ist, dass Du nur ein zip File hast, in dem Du immer wieder nach Einträgen suchst.
Damit findest Du zwar den Eintrag 2Level_Zip.zip und kannst dieses sogar als zip Stream auswerten, um dadurch auf 3Level_Zip.zip zu stoßen. Diesen Eintrag 3Level_Zip.zip suchst Du dann wird in dem ursprünglichen Zip File (ein anderes hast Du ja nicht). Dieser wir kann somit via zip.getEntry(entryName) gar nicht gefunden werden.
Ich kenne die Apache API die Du verwendest nicht, aber diese wirf dann wohl aufgrund der nicht initialisierten zipEntry ein Stream closed statt einer NullPointerException wie man es auch erwarten könnte.
Letztendlich egal, denn so kommst Du nicht an das zip im zip im zip. Evtl. bietet die Apache API hier Möglichkeiten - es wird ja einen Grund haben warum die hier eine eigene API entwickelt haben.

Und Du solltest Dich vielleicht auf die eine oder andere API festlegen und nicht die Standard zip Klassen mit den Apache zip Klassen mischen.

dass das ursprüngliche ZipFile in der Rekursion übergeben wird hatte ich bisher nicht gesehen,
das hilft natürlich nicht, die zweite Schicht darf in allem nur von dem Teil-Zip abhängen

http://commons.apache.org/proper/commons-compress/apidocs/org/apache/commons/compress/archivers/zip/ZipFile.html
hat nur Konstruktoren für File oder String filename, das ist natürlich mies,
sollte genauso auf einen Stream funktionieren,
nach Quellcode RandomAccessFile usw., da kommt man wohl nicht weiter, bietet dann vielleicht auch mehr als allgemeinere Lösungen

hinsichtlich dieser Frage als wirklich interessant, ob man Dateien auf Festplatte zwischenspeichern muss oder nicht

hier


mit java.util.zip.ZipInputStream geht es anscheinend auch auf Stream, nicht Apache Commons, gibt ja einiges zur Auswahl…