Pergunta

Atualmente, estou extrair o conteúdo de um arquivo de guerra e, em seguida, adicionar alguns novos arquivos para a estrutura de diretório e, em seguida, criar um novo arquivo de guerra.

Isso tudo é feito através de programação de Java - mas eu estou querendo saber se não seria mais eficiente para copiar o arquivo de guerra e, em seguida, basta adicionar os arquivos - então eu não teria que esperar tanto tempo como se expande de guerra e então tem de ser comprimido novamente.

Eu não consigo encontrar uma maneira de fazer isso na documentação embora ou quaisquer exemplos on-line.

Qualquer um pode dar algumas dicas ou sugestões?

UPDATE:

TrueZip como mencionado em uma das respostas parece ser uma boa biblioteca java para anexar a um arquivo zip (apesar de outras respostas que dizem que não é possível fazer isso).

Alguém tem experiência ou feedback sobre TrueZip ou pode recomendar outros libaries semelhantes?

Foi útil?

Solução

Em Java 7 chegamos Zip Sistema que permite adicionar e alterar arquivos em ZIP (jar, guerra) sem reembalagem manual.

Nós podemos escrever diretamente a arquivos zip dentro de arquivos como no exemplo a seguir.

Map<String, String> env = new HashMap<>(); 
env.put("create", "true");
Path path = Paths.get("test.zip");
URI uri = URI.create("jar:" + path.toUri());
try (FileSystem fs = FileSystems.newFileSystem(uri, env))
{
    Path nf = fs.getPath("new.txt");
    try (Writer writer = Files.newBufferedWriter(nf, StandardCharsets.UTF_8, StandardOpenOption.CREATE)) {
        writer.write("hello");
    }
}

Outras dicas

Como os outros mencionados, não é possível para o conteúdo acréscimo para um zip existente (ou guerra). No entanto, é possível criar um novo zip na mosca sem escrever temporariamente conteúdo extraído para o disco. É difícil imaginar o quanto mais rápido isso vai ser, mas é o mais rápido você pode obter (pelo menos tanto quanto eu sei) com Java padrão. Como mencionado por Carlos Tasada, SevenZipJBindings pode espremer-lhe alguns segundos extras, mas portar essa abordagem para SevenZipJBindings ainda vai ser mais rápido do que usando arquivos temporários com a mesma biblioteca.

Aqui está um código que escreve o conteúdo de um zip existente (war.zip) e anexa um arquivo extra (answer.txt) para um novo zip (append.zip). Só é preciso Java 5 ou posterior, há bibliotecas extras necessários.

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

public class Main {

    // 4MB buffer
    private static final byte[] BUFFER = new byte[4096 * 1024];

    /**
     * copy input to output stream - available in several StreamUtils or Streams classes 
     */    
    public static void copy(InputStream input, OutputStream output) throws IOException {
        int bytesRead;
        while ((bytesRead = input.read(BUFFER))!= -1) {
            output.write(BUFFER, 0, bytesRead);
        }
    }

    public static void main(String[] args) throws Exception {
        // read war.zip and write to append.zip
        ZipFile war = new ZipFile("war.zip");
        ZipOutputStream append = new ZipOutputStream(new FileOutputStream("append.zip"));

        // first, copy contents from existing war
        Enumeration<? extends ZipEntry> entries = war.entries();
        while (entries.hasMoreElements()) {
            ZipEntry e = entries.nextElement();
            System.out.println("copy: " + e.getName());
            append.putNextEntry(e);
            if (!e.isDirectory()) {
                copy(war.getInputStream(e), append);
            }
            append.closeEntry();
        }

        // now append some extra content
        ZipEntry e = new ZipEntry("answer.txt");
        System.out.println("append: " + e.getName());
        append.putNextEntry(e);
        append.write("42\n".getBytes());
        append.closeEntry();

        // close
        war.close();
        append.close();
    }
}

Eu tinha uma exigência semelhante algum tempo atrás - mas foi para ler e escrever arquivos zip (formato .war devem ser semelhantes). Eu tentei fazê-lo com os fluxos Java Zip existentes, mas encontrou a parte complicada da escrita - especialmente quando diretórios onde envolvido.

Eu vou recomendar-lhe para experimentar o TrueZIP (aberta source - apache estilo licenciado) biblioteca que expõe qualquer arquivo como um sistema de arquivos virtual na qual você pode ler e escrever como um sistema de arquivos normal. Ele trabalhou como um encanto para mim e muito simplificada meu desenvolvimento.

Você pode usar este pedaço de código que eu escrevi

public static void addFilesToZip(File source, File[] files)
{
    try
    {

        File tmpZip = File.createTempFile(source.getName(), null);
        tmpZip.delete();
        if(!source.renameTo(tmpZip))
        {
            throw new Exception("Could not make temp file (" + source.getName() + ")");
        }
        byte[] buffer = new byte[1024];
        ZipInputStream zin = new ZipInputStream(new FileInputStream(tmpZip));
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(source));

        for(int i = 0; i < files.length; i++)
        {
            InputStream in = new FileInputStream(files[i]);
            out.putNextEntry(new ZipEntry(files[i].getName()));
            for(int read = in.read(buffer); read > -1; read = in.read(buffer))
            {
                out.write(buffer, 0, read);
            }
            out.closeEntry();
            in.close();
        }

        for(ZipEntry ze = zin.getNextEntry(); ze != null; ze = zin.getNextEntry())
        {
            out.putNextEntry(ze);
            for(int read = zin.read(buffer); read > -1; read = zin.read(buffer))
            {
                out.write(buffer, 0, read);
            }
            out.closeEntry();
        }

        out.close();
        tmpZip.delete();
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }
}

Eu não sei de uma biblioteca Java que faz o que você descreve. Mas o que você descreveu é prático. Você pode fazê-lo em .NET, utilizando DotNetZip .

Michael Krauklis é correto que você não pode simplesmente dados "Anexar" para um arquivo de guerra ou arquivo zip, mas não é porque existe um "fim de arquivo" indicação, estritamente falando, em um arquivo de guerra. É porque o formato guerra (zip) inclui um diretório, que está normalmente presente no final do arquivo, que contém metadados para as várias entradas no arquivo de guerra. Ingenuamente anexando a um arquivo resulta guerra no nenhuma atualização para o diretório, e assim você só tem um arquivo de guerra com lixo anexado a ele.

O que é necessário é uma classe inteligente que entende o formato, e pode ler + atualizar um arquivo de guerra ou arquivo zip, incluindo o diretório conforme o caso. DotNetZip faz isso, sem descomprimir / recompressing as entradas inalterados, assim como você descreveu ou desejado.

Como diz Cheeso, não há nenhuma maneira de fazê-lo. AFAIK o zip front-ends estão fazendo exatamente o mesmo que você internamente.

De qualquer forma, se você está preocupado com a velocidade de extração / comprimindo tudo, você pode querer tentar o SevenZipJBindings biblioteca.

Eu cobri esta biblioteca no meu blogue há alguns meses (não para o auto-promoção). Apenas como exemplo, extrair um arquivo zip 104MB usando o java.util.zip me levou 12 segundos, enquanto estiver usando esta biblioteca levou 4 segundos.

Em ambos os links que você pode encontrar exemplos sobre como usá-lo.

Hope isso ajuda.

Veja este relatório de bug .

Usando o modo anexar em qualquer tipo de dados estruturados, como arquivos zip ou tar arquivos não é algo que você pode realmente esperar para trabalhar. Esses formatos de arquivo têm um "fim de arquivo" intrínseco indicação construído no formato de dados.

Se você realmente quiser pular a etapa intermediária de un-Waring / re-Waring, você pode ler o arquivo de arquivo de guerra, obter todas as entradas zip, em seguida, escrever para um novo arquivo de guerra "acrescentando" as novas entradas que você queria adicionar. Não é perfeito, mas pelo menos uma solução mais automatizada.

No entanto, outra solução: Você pode encontrar o código abaixo útil em outras situações também. Eu tenho usado formiga esta forma de compilar diretórios Java, gerando arquivos jar, atualizando arquivos zip, ...

    public static void antUpdateZip(String zipFilePath, String libsToAddDir) {
    Project p = new Project();
    p.init();

    Target target = new Target();
    target.setName("zip");
    Zip task = new Zip();
    task.init();
    task.setDestFile(new File(zipFilePath));
    ZipFileSet zipFileSet = new ZipFileSet();
    zipFileSet.setPrefix("WEB-INF/lib");
    zipFileSet.setDir(new File(libsToAddDir));
    task.addFileset(zipFileSet);
    task.setUpdate(true);

    task.setProject(p);
    task.init();
    target.addTask(task);
    target.setProject(p);
    p.addTarget(target);

    DefaultLogger consoleLogger = new DefaultLogger();
    consoleLogger.setErrorPrintStream(System.err);
    consoleLogger.setOutputPrintStream(System.out);
    consoleLogger.setMessageOutputLevel(Project.MSG_DEBUG);
    p.addBuildListener(consoleLogger);

    try {
        // p.fireBuildStarted();

        // ProjectHelper helper = ProjectHelper.getProjectHelper();
        // p.addReference("ant.projectHelper", helper);
        // helper.parse(p, buildFile);
        p.executeTarget(target.getName());
        // p.fireBuildFinished(null);
    } catch (BuildException e) {
        p.fireBuildFinished(e);
        throw new AssertionError(e);
    }
}

este código simples para obter uma resposta com o uso de servlet e enviar uma resposta

myZipPath = bla bla...
    byte[] buf = new byte[8192];
    String zipName = "myZip.zip";
    String zipPath = myzippath+ File.separator+"pdf" + File.separator+ zipName;
    File pdfFile = new File("myPdf.pdf");
    ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipPath));
    ZipEntry zipEntry = new ZipEntry(pdfFile.getName());
    out.putNextEntry(zipEntry);
    InputStream in = new FileInputStream(pdfFile);
    int len;
    while ((len = in.read(buf)) > 0) {
         out.write(buf, 0, len);
     }
    out.closeEntry();
    in.close();
     out.close();
                FileInputStream fis = new FileInputStream(zipPath);
                response.setContentType("application/zip");
                response.addHeader("content-disposition", "attachment;filename=" + zipName);
    OutputStream os = response.getOutputStream();
            int length = is.read(buffer);
            while (length != -1)
            {
                os.write(buffer, 0, length);
                length = is.read(buffer);
            }

Aqui está Java 1.7 versão de resposta Liam quais usos tentar com recursos e Apache Commons IO.

A saída é gravada em um novo arquivo zip, mas pode ser facilmente modificado para escrever para o arquivo original.

  /**
   * Modifies, adds or deletes file(s) from a existing zip file.
   *
   * @param zipFile the original zip file
   * @param newZipFile the destination zip file
   * @param filesToAddOrOverwrite the names of the files to add or modify from the original file
   * @param filesToAddOrOverwriteInputStreams the input streams containing the content of the files
   * to add or modify from the original file
   * @param filesToDelete the names of the files to delete from the original file
   * @throws IOException if the new file could not be written
   */
  public static void modifyZipFile(File zipFile,
      File newZipFile,
      String[] filesToAddOrOverwrite,
      InputStream[] filesToAddOrOverwriteInputStreams,
      String[] filesToDelete) throws IOException {


    try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(newZipFile))) {

      // add existing ZIP entry to output stream
      try (ZipInputStream zin = new ZipInputStream(new FileInputStream(zipFile))) {
        ZipEntry entry = null;
        while ((entry = zin.getNextEntry()) != null) {
          String name = entry.getName();

          // check if the file should be deleted
          if (filesToDelete != null) {
            boolean ignoreFile = false;
            for (String fileToDelete : filesToDelete) {
              if (name.equalsIgnoreCase(fileToDelete)) {
                ignoreFile = true;
                break;
              }
            }
            if (ignoreFile) {
              continue;
            }
          }

          // check if the file should be kept as it is
          boolean keepFileUnchanged = true;
          if (filesToAddOrOverwrite != null) {
            for (String fileToAddOrOverwrite : filesToAddOrOverwrite) {
              if (name.equalsIgnoreCase(fileToAddOrOverwrite)) {
                keepFileUnchanged = false;
              }
            }
          }

          if (keepFileUnchanged) {
            // copy the file as it is
            out.putNextEntry(new ZipEntry(name));
            IOUtils.copy(zin, out);
          }
        }
      }

      // add the modified or added files to the zip file
      if (filesToAddOrOverwrite != null) {
        for (int i = 0; i < filesToAddOrOverwrite.length; i++) {
          String fileToAddOrOverwrite = filesToAddOrOverwrite[i];
          try (InputStream in = filesToAddOrOverwriteInputStreams[i]) {
            out.putNextEntry(new ZipEntry(fileToAddOrOverwrite));
            IOUtils.copy(in, out);
            out.closeEntry();
          }
        }
      }

    }

  }

isso funciona 100%, se você não quiser usar libs extras .. 1) Em primeiro lugar, a classe que arquivos de acréscimo para o zip ..

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class AddZip {

    public void AddZip() {
    }

    public void addToZipFile(ZipOutputStream zos, String nombreFileAnadir, String nombreDentroZip) {
        FileInputStream fis = null;
        try {
            if (!new File(nombreFileAnadir).exists()) {//NO EXISTE 
                System.out.println(" No existe el archivo :  " + nombreFileAnadir);return;
            }
            File file = new File(nombreFileAnadir);
            System.out.println(" Generando el archivo '" + nombreFileAnadir + "' al ZIP ");
            fis = new FileInputStream(file);
            ZipEntry zipEntry = new ZipEntry(nombreDentroZip);
            zos.putNextEntry(zipEntry);
            byte[] bytes = new byte[1024];
            int length;
            while ((length = fis.read(bytes)) >= 0) {zos.write(bytes, 0, length);}
            zos.closeEntry();
            fis.close();

        } catch (FileNotFoundException ex ) {
            Logger.getLogger(AddZip.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(AddZip.class.getName()).log(Level.SEVERE, null, ex);
        } 
    }

}

2) você pode chamá-lo em seu controlador ..

//in the top
try {
fos = new FileOutputStream(rutaZip);
zos =   new ZipOutputStream(fos);
} catch (FileNotFoundException ex) {
Logger.getLogger(UtilZip.class.getName()).log(Level.SEVERE, null, ex);
}

...
//inside your method
addZip.addToZipFile(zos, pathFolderFileSystemHD() + itemFoto.getNombre(), "foto/" + itemFoto.getNombre());

Aqui estão alguns exemplos como facilmente arquivos podem ser anexados a zip existente usando TrueVFS :

// append a file to archive under different name
TFile.cp(new File("existingFile.txt"), new TFile("archive.zip", "entry.txt"));

// recusively append a dir to the root of archive
TFile src = new TFile("dirPath", "dirName");
src.cp_r(new TFile("archive.zip", src.getName()));

TrueVFS, o sucessor de TrueZIP, usa Java 7 NIO 2 recursos sob o capô quando apropriado, mas ofertas muito mais recursos como compressão assíncrono paralelo thread-safe.

Cuidado também que Java 7 ZipFileSystem por padrão é vulnerável a OutOfMemoryError nas entradas enormes.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top