在 Java 中修改 ZIP 存档中的文本文件
Posted
技术标签:
【中文标题】在 Java 中修改 ZIP 存档中的文本文件【英文标题】:Modifying a text file in a ZIP archive in Java 【发布时间】:2012-07-15 04:18:54 【问题描述】:我的用例要求我打开一个 txt 文件,比如 abc.txt,它位于一个 zip 存档中,其中包含表单中的键值对
key1=value1
key2=value2
.. 以此类推,每个键值对都在一个新行中。 我必须更改与某个键对应的一个值,并将文本文件放回存档的新副本中。我如何在 java 中做到这一点?
到目前为止我的尝试:
ZipFile zipFile = new ZipFile("test.zip");
final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("out.zip"));
for(Enumeration e = zipFile.entries(); e.hasMoreElements(); )
ZipEntry entryIn = (ZipEntry) e.nextElement();
if(!entryIn.getName().equalsIgnoreCase("abc.txt"))
zos.putNextEntry(entryIn);
InputStream is = zipFile.getInputStream(entryIn);
byte [] buf = new byte[1024];
int len;
while((len = (is.read(buf))) > 0)
zos.write(buf, 0, len);
else
// I'm not sure what to do here
// Tried a few things and the file gets corrupt
zos.closeEntry();
zos.close();
【问题讨论】:
那么,除了刷新输出流之外,还有什么不工作的? 我没听懂你。我没有明确刷新输出流。 【参考方案1】:你几乎做对了。文件显示为已损坏的一个可能原因是您可能使用过
zos.putNextEntry(entryIn)
在其他部分也是如此。这会在 zip 文件中创建一个新条目,其中包含来自现有 zip 文件的信息。现有信息包括条目名称(文件名)及其CRC。
然后,当您尝试更新文本文件并关闭 zip 文件时,它会抛出错误,因为条目中定义的 CRC 和您尝试写入的对象的 CRC 不同。
如果您尝试替换的文本的长度与现有文本的长度不同,即您正在尝试替换,您可能会收到错误
key1=value1
与
key1=val1
这归结为您尝试写入的缓冲区长度与指定长度不同的问题。
ZipFile zipFile = new ZipFile("test.zip");
final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("out.zip"));
for(Enumeration e = zipFile.entries(); e.hasMoreElements(); )
ZipEntry entryIn = (ZipEntry) e.nextElement();
if (!entryIn.getName().equalsIgnoreCase("abc.txt"))
zos.putNextEntry(entryIn);
InputStream is = zipFile.getInputStream(entryIn);
byte[] buf = new byte[1024];
int len;
while((len = is.read(buf)) > 0)
zos.write(buf, 0, len);
else
zos.putNextEntry(new ZipEntry("abc.txt"));
InputStream is = zipFile.getInputStream(entryIn);
byte[] buf = new byte[1024];
int len;
while ((len = (is.read(buf))) > 0)
String s = new String(buf);
if (s.contains("key1=value1"))
buf = s.replaceAll("key1=value1", "key1=val2").getBytes();
zos.write(buf, 0, (len < buf.length) ? len : buf.length);
zos.closeEntry();
zos.close();
以下代码确保即使被替换的数据长度小于原始长度,也不会发生 IndexOutOfBoundsExceptions。
(len
【讨论】:
您应该将偏移量和长度从字节数组传递给 String 初始化,否则当 len 小于缓冲区时,仍然会使用整个缓冲区创建字符串。这甚至可能在大多数情况下都有效!以上代码严重错误! 如果您在创建 ZipEntry 之后修改文件,则后者将与数据不一致(例如 CRC)。不是问题吗?【参考方案2】:只有一点改进:
else
zos.putNextEntry(new ZipEntry("abc.txt"));
InputStream is = zipFile.getInputStream(entryIn);
byte[] buf = new byte[1024];
int len;
while ((len = (is.read(buf))) > 0)
String s = new String(buf);
if (s.contains("key1=value1"))
buf = s.replaceAll("key1=value1", "key1=val2").getBytes();
zos.write(buf, 0, (len < buf.length) ? len : buf.length);
应该是:
else
zos.putNextEntry(new ZipEntry("abc.txt"));
InputStream is = zipFile.getInputStream(entryIn);
long size = entry.getSize();
if (size > Integer.MAX_VALUE)
throw new IllegalStateException("...");
byte[] bytes = new byte[(int)size];
is.read(bytes);
zos.write(new String(bytes).replaceAll("key1=value1", "key1=val2").getBytes());
为了捕捉所有的出现
原因是,在第一次读取时,您可能在一次读取时读取“key1”,在下一次读取时读取“=value1”,无法捕获您想要更改的事件
【讨论】:
在 replaceAll(String, String) 中第一个 String 是一个正则表达式 - 如果第一个键值对 String 中有任何特殊字符,这会带来麻烦。【参考方案3】:Java 7 引入了一种更简单的 zip 归档操作方法 - FileSystems API,它允许将文件的内容作为文件系统访问。
除了更直接的 API 之外,它还在原地进行修改,不需要重写 zip 存档中的其他(不相关)文件(如已接受的答案中所做的那样)。
这是解决 OP 用例的示例代码:
import java.io.*;
import java.nio.file.*;
public static void main(String[] args) throws IOException
modifyTextFileInZip("test.zip");
static void modifyTextFileInZip(String zipPath) throws IOException
Path zipFilePath = Paths.get(zipPath);
try (FileSystem fs = FileSystems.newFileSystem(zipFilePath, null))
Path source = fs.getPath("/abc.txt");
Path temp = fs.getPath("/___abc___.txt");
if (Files.exists(temp))
throw new IOException("temp file exists, generate another name");
Files.move(source, temp);
streamCopy(temp, source);
Files.delete(temp);
static void streamCopy(Path src, Path dst) throws IOException
try (BufferedReader br = new BufferedReader(
new InputStreamReader(Files.newInputStream(src)));
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(Files.newOutputStream(dst))))
String line;
while ((line = br.readLine()) != null)
line = line.replace("key1=value1", "key1=value2");
bw.write(line);
bw.newLine();
有关更多 zip 存档操作示例,请参阅demo/nio/zipfs/Demo.java
示例,您可以下载该示例here(查找 JDK 8 演示和示例)。
【讨论】:
作为文件系统打开文件时,文件访问方式好像发生了变化,所以我稍后重新设置。以上是关于在 Java 中修改 ZIP 存档中的文本文件的主要内容,如果未能解决你的问题,请参考以下文章