development

Java를 사용하여 zip 파일에 파일 추가

big-blog 2020. 12. 26. 16:27
반응형

Java를 사용하여 zip 파일에 파일 추가


현재 war 파일의 내용을 추출하고 디렉토리 구조에 새 파일을 추가 한 다음 새 war 파일을 만듭니다.

이것은 모두 Java에서 프로그래밍 방식으로 수행됩니다.하지만 전쟁 파일을 복사 한 다음 파일을 추가하는 것이 더 효율적이지 않을지 궁금합니다. 전쟁이 확장되는 한 기다릴 필요가 없습니다. 다시 압축됩니다.

문서 또는 온라인 예제에서이 작업을 수행하는 방법을 찾을 수없는 것 같습니다.

누구나 팁이나 조언을 줄 수 있습니까?

최신 정보:

답변 중 하나에서 언급 한 TrueZip은 zip 파일에 추가하기에 매우 좋은 자바 라이브러리 인 것 같습니다 (이 작업을 수행 할 수 없다고 말하는 다른 답변에도 불구하고).

누구나 TrueZip에 대한 경험이나 피드백이 있거나 다른 유사한 라이브러리를 추천 할 수 있습니까?


Java 7에는 수동 재 패키징없이 zip (jar, war) 파일을 추가하고 변경할 수있는 Zip 파일 시스템 이 있습니다.

다음 예제와 같이 zip 파일 내의 파일에 직접 쓸 수 있습니다.

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");
    }
}

다른 사람들이 언급했듯이 기존 zip (또는 전쟁)에 콘텐츠를 추가하는 것은 불가능합니다. 그러나 추출 된 콘텐츠를 디스크에 임시로 쓰지 않고도 즉석에서 새 zip을 만들 수 있습니다. 이것이 얼마나 빠를 지 추측하기는 어렵지만 표준 Java로 얻을 수있는 가장 빠른 것입니다 (적어도 내가 아는 한). Carlos Tasada가 언급했듯이 SevenZipJBindings는 약간의 추가 시간을 필요로 할 수 있지만이 접근 방식을 SevenZipJBindings로 포팅하는 것이 동일한 라이브러리에서 임시 파일을 사용하는 것보다 여전히 빠릅니다.

다음은 기존 zip (war.zip)의 내용을 작성하고 새 zip (append.zip)에 추가 파일 (answer.txt)을 추가하는 코드입니다. 필요한 것은 Java 5 이상이며 추가 라이브러리가 필요하지 않습니다.

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();
    }
}

언젠가 비슷한 요구 사항이 있었지만 zip 아카이브를 읽고 쓰기위한 것이 었습니다 (.war 형식은 비슷해야 함). 기존 Java Zip 스트림으로 시도했지만 특히 관련된 디렉토리에서 작성 부분이 번거 롭다는 것을 알았습니다.

모든 아카이브를 일반 파일 시스템처럼 읽고 쓸 수있는 가상 파일 시스템으로 노출 하는 TrueZIP (오픈 소스-아파치 스타일 라이센스) 라이브러리 를 사용해 보는 것이 좋습니다 . 그것은 저에게 매력처럼 작용했고 제 개발을 크게 단순화했습니다.


내가 작성한이 코드를 사용할 수 있습니다.

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();
    }
}

나는 당신이 설명하는 것을 수행하는 Java 라이브러리를 모릅니다. 그러나 당신이 설명한 것은 실용적입니다. DotNetZip을 사용하여 .NET에서 할 수 있습니다 .

Michael Krauklis는 단순히 war 파일이나 zip 파일에 데이터를 "추가"할 수 없다는 것이 옳지 만, 엄격히 말하면 war 파일에 "파일 끝"표시가 있기 때문이 아닙니다. 이는 war (zip) 형식에 war 파일의 다양한 항목에 대한 메타 데이터를 포함하는 일반적으로 파일 끝에있는 디렉토리가 포함되기 때문입니다. 순진하게 war 파일에 추가하면 디렉토리가 업데이트되지 않으므로 정크가 추가 된 war 파일 만 있습니다.

필요한 것은 형식을 이해하고 적절한 디렉토리를 포함하여 war 파일 또는 zip 파일을 읽고 업데이트 할 수있는 지능형 클래스입니다. DotNetZip은 설명했거나 원하는대로 변경되지 않은 항목을 압축 해제 / 재 압축하지 않고이를 수행합니다.


Cheeso가 말했듯이 그것을 할 방법이 없습니다. AFAIK zip 프런트 엔드는 내부적으로 똑같은 작업을 수행합니다.

어쨌든 모든 것을 추출 / 압축하는 속도가 걱정된다면 SevenZipJBindings 라이브러리 를 사용해보십시오 .

몇 달 전에 블로그 에서이 라이브러리를 다루었습니다 (자동 프로모션에 대해 죄송합니다). 예를 들어 java.util.zip을 사용하여 104MB zip 파일을 추출하는 데 12 초가 걸리고이 라이브러리를 사용하는 데 4 초가 걸렸습니다.

두 링크에서 사용 방법에 대한 예를 찾을 수 있습니다.

도움이 되었기를 바랍니다.


버그 보고서를 참조하십시오 .

zip 파일이나 tar 파일과 같은 모든 종류의 구조화 된 데이터에 추가 모드를 사용하는 것은 실제로 작동 할 것으로 기대할 수있는 것이 아닙니다. 이러한 파일 형식에는 데이터 형식에 내장 된 본질적인 "파일 끝"표시가 있습니다.

경고 해제 / 다시 경고의 중간 단계를 건너 뛰려면 war 파일 파일을 읽고 모든 zip 항목을 가져온 다음 추가하려는 새 항목을 "추가"하여 새 war 파일에 쓸 수 있습니다. 완벽하지는 않지만 적어도 더 자동화 된 솔루션입니다.


또 다른 솔루션 : 아래 코드는 다른 상황에서도 유용 할 수 있습니다. 이 방법으로 ant를 사용하여 Java 디렉토리를 컴파일하고, jar 파일을 생성하고, 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);
    }
}

이것은 서블릿을 사용하여 응답을 얻고 응답을 보내는 간단한 코드입니다.

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);
            }

다음은 리소스 및 Apache Commons IO와 함께 시도를 사용하는 Liam 답변의 Java 1.7 버전입니다.

출력은 새 zip 파일에 기록되지만 원본 파일에 기록하도록 쉽게 수정할 수 있습니다.

  /**
   * 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();
          }
        }
      }

    }

  }

이것은 100 % 작동합니다. 추가 라이브러리를 사용하지 않으려면 .. 1) 먼저 파일을 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) 컨트롤러에서 호출 할 수 있습니다 ..

//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());

다음은 TrueVFS를 사용하여 파일을 기존 zip에 쉽게 추가 할 수있는 방법의 예입니다 .

// 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()));

TrueZIP의 후속 제품인 TrueVFS는 적절한 경우 Java 7 NIO 2 기능을 사용하지만 스레드로부터 안전한 비동기 병렬 압축과 같은 훨씬 더 많은 기능을 제공합니다 .

기본적으로 Java 7 ZipFileSystem은 대규모 입력 에서 OutOfMemoryError취약합니다 .

ReferenceURL : https://stackoverflow.com/questions/2223434/appending-files-to-a-zip-file-with-java

반응형