Изменение текстового файла в ZIP-архиве на Java
мой прецедент требует, чтобы я открыл 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();
3 ответов
вы почти все сделали правильно. Одна из возможных причин, файл был показан как поврежден, что вы могли бы использовать
zos.putNextEntry (entryIn)
в другой части. Это создает новую запись в файле zip, содержащую информацию из существующего файла zip. Существующая информация содержит имя записи (имя файла) и его CRC среди прочего.
а затем, когда вы попытаетесь обновить текстовый файл и закрыть zip-файл, он будет бросьте ошибку, поскольку CRC, определенный в записи, и CRC объекта, который вы пытаетесь написать, отличаются.
также u может получить ошибку, если длина текста, который вы пытаетесь заменить, отличается от существующей, т. е. вы пытаетесь заменить
key1=value1
С
ключ1=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
Java 7 представила гораздо более простой способ выполнения zip-архивных манипуляций -файловые системы 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
образец которой вы можете скачать здесь (ищите демонстрации и образцы JDK 8).
только небольшое улучшение:
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" в следующем, не будучи в состоянии захватить вхождение, которое вы хотите изменить