私のユースケースでは、フォームにキーと値のペアが含まれているZipアーカイブ内にあるabc.txtなどのtxtファイルを開く必要があります。
key1 = value1
key2 = value2
..など、各キーと値のペアが新しい行にある場合。特定のキーに対応する1つの値を変更し、テキストファイルをアーカイブの新しいコピーに戻す必要があります。 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 <buf.length)? len:buf.length
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のデモとサンプルを探してください)。
ほんの少しの改善:
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());
}
すべての発生をキャプチャするために
その理由は、最初の読み取りでは、1回の読み取りで「key1」、次の読み取りで「= value1」があり、変更したいオカレンスをキャプチャできない可能性があるためです。