2014-05-25 11 views
9

Il pacchetto java.nio ha un bellissimo modo di gestire i file zip trattandoli come file system. Questo ci permette di trattare i contenuti dei file zip come i soliti file. Pertanto, è possibile realizzare zippare un'intera cartella semplicemente utilizzando Files.copy per copiare tutti i file nel file zip. Dal momento che le sottocartelle devono essere copiati così, abbiamo bisogno di un visitatore:Zippare una cartella enorme utilizzando un file ZipFile provoca l'errore OutOfMemoryError

private static class CopyFileVisitor extends SimpleFileVisitor<Path> { 
    private final Path targetPath; 
    private Path sourcePath = null; 
    public CopyFileVisitor(Path targetPath) { 
     this.targetPath = targetPath; 
    } 

    @Override 
    public FileVisitResult preVisitDirectory(final Path dir, 
    final BasicFileAttributes attrs) throws IOException { 
     if (sourcePath == null) { 
      sourcePath = dir; 
     } else { 
     Files.createDirectories(targetPath.resolve(sourcePath 
        .relativize(dir).toString())); 
     } 
     return FileVisitResult.CONTINUE; 
    } 

    @Override 
    public FileVisitResult visitFile(final Path file, 
    final BasicFileAttributes attrs) throws IOException { 
    Files.copy(file, 
     targetPath.resolve(sourcePath.relativize(file).toString()), StandardCopyOption.REPLACE_EXISTING); 
    return FileVisitResult.CONTINUE; 
    } 
} 

Si tratta di una "directory ricorsivamente copia" semplice visitatore. È usato per copiare una directory in modo ricorsivo. Tuttavia, con l'ZipFileSystem, possiamo anche usare per copiare una directory in un file zip, in questo modo:

public static void zipFolder(Path zipFile, Path sourceDir) throws ZipException, IOException 
{ 
    // Initialize the Zip Filesystem and get its root 
    Map<String, String> env = new HashMap<>(); 
    env.put("create", "true"); 
    URI uri = URI.create("jar:" + zipFile.toUri());  
    FileSystem fileSystem = FileSystems.newFileSystem(uri, env); 
    Iterable<Path> roots = fileSystem.getRootDirectories(); 
    Path root = roots.iterator().next(); 

    // Simply copy the directory into the root of the zip file system 
    Files.walkFileTree(sourceDir, new CopyFileVisitor(root)); 
} 

Questo è quello che io chiamo un modo elegante di zippare un'intera cartella. Tuttavia, quando si utilizza questo metodo su una cartella enorme (circa 3 GB) ricevo uno OutOfMemoryError (spazio heap). Quando si utilizza una normale libreria di gestione zip, questo errore non viene generato. Pertanto, sembra che il modo in cui lo ZipFileSystem gestisce la copia sia molto inefficiente: troppo gran parte dei file da scrivere viene conservata in memoria, pertanto si verifica OutOfMemoryError.

Perché è questo il caso? L'utilizzo di ZipFileSystem è generalmente considerato inefficiente (in termini di consumo di memoria) o sto facendo qualcosa di sbagliato qui?

risposta

-2

È necessario preparare la jvm per consentire tali quantità di memoria con -Xms {memory} -Xmx {memory}.

Si consiglia di controllare la directory di calcolo dello spazio su disco e di impostare un limite, in 1 GB utilizzare il file system di memoria, oltre 1 GB utilizzare un file system su disco.

Un'altra cosa, controllare la concorrenza del metodo, si non ti piace più di 1 filo zippare 3Gb di file

+2

dispiace, ma questa risposta non aiuta affatto. 1) So come aumentare la dimensione dell'heap, questa non è la domanda. 2) Che cos'è "file system di memoria" o "sistema di file su disco"? 3) Il metodo non è concomitante come si dovrebbe vedere dal codice – gexicide

+0

@gexicide Si prega di verificare la mia risposta e se risolve il problema (come ha fatto per gli altri) si prega di contrassegnare come la risposta corretta. Grazie. –

17

ho guardato ZipFileSystem.java e credo che ho trovato la fonte del consumo di memoria . Per impostazione predefinita, l'implementazione utilizza ByteArrayOutputStream come buffer per comprimere i file, il che significa che è limitato dalla quantità di memoria assegnata alla JVM.

C'è una variabile (non documentato) ambiente che possiamo usare per rendere l'applicazione utilizzare file temporanei ("useTempFile"). Funziona in questo modo:

Map<String, Object> env = new HashMap<>(); 
env.put("create", "true"); 
env.put("useTempFile", Boolean.TRUE); 

Maggiori dettagli qui: http://www.docjar.com/html/api/com/sun/nio/zipfs/ZipFileSystem.java.html, linee interessanti sono 96, il 1358 e il 1362.

+2

Grazie mille per le tue indagini su questo. Osservando la directory temp quando 'useTempFile = TRUE durante la compressione dei file in parallelo (usando http://goo.gl/woa0Ab) sembra che ogni file sia compresso in modo indipendente in parallelo in un file temporaneo compresso separato, e tutti questi sono quindi concatenato in un unico file. Tale file viene quindi rinominato atomicamente nel nome dell'archivio. Che peccato che questo non sia documentato, e quale altra vergogna non c'è ancora lo zip in streaming parallelo nella libreria standard java. –