Подтвердить что ты не робот

Изменение текстового файла в ZIP-архиве в Java

В моем случае использования я должен открыть txt файл, скажем, abc.txt, который находится внутри zip-архива, который содержит пары ключ-значение в форме

ключ1 = значение1

ключ2 = значение2

.. и так далее, где каждая пара ключ-значение находится в новой строке. Я должен изменить одно значение, соответствующее определенному ключу, и вернуть текстовый файл в новую копию архива. Как это сделать в 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();
4b9b3361

Ответ 1

Ты почти все понял. Одна из возможных причин, файл был показан как поврежденный, так это то, что вы могли использовать

zos.putNextEntry(entryIn)

в другой части. Это создает новую запись в zip файле, содержащую информацию из существующего zip файла. Существующая информация содержит имя записи (имя файла) и ее CRC, среди прочего.

И затем, когда вы попытаетесь обновить текстовый файл и закройте zip файл, он выдаст ошибку, поскольку CRC, определенный в записи, и CRC объекта, который вы пытаетесь записать, отличаются.

Также вы можете получить сообщение об ошибке, если длина текста, который вы пытаетесь заменить, отличается от существующего, т.е. вы пытаетесь заменить

ключ1 = значение1

с

ключ1 = знач1

Это сводится к проблеме, что буфер, который вы пытаетесь записать, имеет длину, отличную от указанной.

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 < buf.length)? len: buf.length

Ответ 2

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 образце, который вы можете скачать здесь (посмотрите JDK 8 Demos и Samples).

Ответ 3

Только небольшое улучшение:

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" в одном read и "= value1" в следующем, не имея возможности зафиксировать вхождение, которое вы хотите изменить