用Java将文件附加到一个zip文件
我正在提取战争文件的内容,然后添加一些新的文件到目录结构,然后创build一个新的战争文件。
这一切都是从Java编程完成的 – 但是我想知道如果复制战争文件,然后只是附加文件是不是更有效率 – 那么只要战争扩大,我就不必等待,然后必须再次压缩。
我似乎无法find一个方法来做到这一点在文档,或任何在线的例子。
任何人都可以提供一些提示或指针?
更新:
正如其中一个答案中提到的TrueZip似乎是一个非常好的Java库来追加到一个zip文件(尽pipe其他答案说,这是不可能做到这一点)。
任何人在TrueZip有经验或反馈,或可以推荐其他类似的库?
在Java 7中,我们获得了Zip文件系统 ,允许在zip(jar,war)中添加和更改文件,而无需手动重新打包。
我们可以直接写入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(或war)中。 但是,可以在不临时将提取的内容写入磁盘的情况下即时创build新的压缩文件。 很难猜测这将会是多快,但是用标准Java可以达到最快(至less据我所知)。 正如Carlos Tasada所提到的,SevenZipJBindings可能会挤出一些额外的时间,但将这种方法移植到SevenZipJBindings仍然比使用具有相同库的临时文件更快。
下面是一些编写现有zip(war.zip)内容的代码,并将一个额外的文件(answer.txt)附加到一个新的zip(append.zip)中。 所需要的只是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 Zipstream来做,但发现写作部分很麻烦 – 特别是当涉及目录时。
我build议你尝试一下TrueZIP (开放源代码 – apache风格许可)库,将任何压缩文件公开为虚拟文件系统,你可以像普通文件系统一样读写。 它对我来说就像一个魅力,并大大简化了我的发展。
你可以使用我写的这段代码
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库。 但是你所描述的是实用的。 你可以在.NET中使用DotNetZip 。
Michael Krauklis是正确的,你不能简单地将数据附加到war文件或zip文件中,但是这并不是因为严格来说在war文件中存在“文件结束”指示。 这是因为war(zip)格式包含一个通常存在于文件末尾的目录,其中包含war文件中各个条目的元数据。 天真地附加到战争文件导致没有更新的目录,所以你只是有一个战争文件与垃圾附加到它。
什么是必要的智能类,理解的格式,并可以阅读+更新战争文件或zip文件,包括适当的目录。 DotNetZip这样做,没有解压缩/重新压缩不变的条目,就像你所描述或期望的。
正如切索所说,没有办法做到这一点。 AFAIK的拉链前端正在做和你一样的内部。
无论如何,如果你担心提取/压缩一切的速度,你可能想尝试SevenZipJBindings库。
几个月前我在博客中介绍过这个库(遗憾的是自动推广)。 举个例子,使用java.util.zip提取一个104MB的压缩文件需要12秒,而使用这个库需要4秒。
在这两个链接,你可以find关于如何使用它的例子。
希望它有帮助。
看到这个错误报告 。
在任何types的结构化数据(如zip文件或tar文件)上使用附加模式不是您真正期望的工作。 这些文件格式具有数据格式内置的固有“文件结束”指示。
如果你真的想要跳过不参与战争的中间步骤,你可以阅读战争档案文件,得到所有的zip条目,然后写入一个新的war文件,“追加”你想添加的新条目。 不完美,但至less有一个更自动化的解决scheme。
另一种解决scheme:您也可以在其他情况下find以下代码。 我用这种方式来编译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); } }
这是一个简单的代码,使用servlet获得响应并发送响应
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)首先,将文件附加到压缩的类..
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轻松地将文件附加到现有压缩文件的示例:
// 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的inheritance者TrueVFS在适当的时候使用了Java 7的NIO 2特性,但是提供了更多的特性,比如线程安全的asynchronous并行压缩。
还要注意,默认情况下,Java 7 ZipFileSystem在大量input时易受OutOfMemoryError影响 。