Переименовать файл/папку внутри Zip-файла в Java?

У меня есть zip-файл, содержащий структуру папок, например

  • main-folder/
    • subFolder1/
    • подпапка2/
    • subFolder3/
      • file3.1
      • файл 3.2

Я хотел бы переименовать папку main-folder, скажем, versionXY внутри этого самого zip-файла с помощью Java.

Есть ли более простой способ, чем извлечь весь zip-файл и воссоздать новый, используя новые имена папок?


person Simon07    schedule 11.05.2009    source источник


Ответы (4)


Zip — это формат архива, поэтому изменение обычно включает перезапись файла.

Некоторые особенности zip также мешают (zip полон функций). Как и центральный каталог в конце архива, каждому файлу компонента предшествует его имя файла. Zip не имеет концепции каталогов — имена файлов — это просто строки, которые содержат символы "/" (и подстроки, такие как "../".

Итак, вам действительно нужно скопировать файл, используя ZipInputStream и ZipOutputStream, переименовывая по ходу дела. Если вы действительно хотите, вы можете переписать файл на месте, выполняя собственную буферизацию. Процесс вызывает повторное сжатие содержимого, поскольку стандартный API не имеет средств для получения данных в сжатой форме.

Изменить: @Doval указывает, что в ответе @megasega используется Поставщик файловой системы Zip в NIO, новый (относительно этого ответа) в Java SE 7. Его производительность, вероятно, будет невелика, как и архивные файловые системы в графическом интерфейсе ОС RISC тридцатилетней давности.

person Tom Hawtin - tackline    schedule 11.05.2009
comment
Центральный каталог заменяет заголовки локального каталога, чтобы zip-файлы можно было эффективно читать и обновлять. Используемый поставщик файловой системы Zip в ответ megasega был недоступен на момент написания этого ответа, но он обновляет центральный каталог без распаковки и повторного архивирования файлов. - person Doval; 07.08.2020

Я знаю, что вы спрашивали о Java, но просто для архивных целей я решил добавить заметку о .NET.

DotNetZip – это библиотека .NET для zip-файлов, позволяющая переименовывать записи. Как говорится в ответе Тома Хотина, каталоги не являются первоклассными сущностями в метаданных zip-файла, и, как следствие, никакие zip-библиотеки, о которых я знаю, не предоставляют глагол «переименовать каталог». Но некоторые библиотеки позволяют вам переименовывать все записи, имена которых указывают на определенный каталог, что дает вам желаемый результат.

В DotNetZip это будет выглядеть так:

 var regex = new Regex("/OldDirName/.*$");
 int renameCount= 0;
 using (ZipFile zip = ZipFile.Read(ExistingZipFile))
 {
    foreach (ZipEntry e in zip)
    {
        if (regex.IsMatch(e.FileName))
        {
            // rename here
            e.FileName = e.FileName.Replace("/OldDirName/", "/NewDirName/");
            renameCount++;
        }
    }
    if (renameCount > 0)
    {
        zip.Comment = String.Format("This archive has been modified. {0} entries have been renamed.", renameCount);
        // any changes to the entries are made permanent by Save()
        zip.Save();  // could also save to a new zip file here
    }
 }

Вы также можете добавлять или удалять записи внутри предложения using.

Если вы сохраняете в тот же файл, то DotNetZip перезаписывает только измененные метаданные — заголовки записей и записи центрального каталога для переименованных записей, что экономит время при работе с большими архивами. Если вы сохраните в новый файл или поток, все данные zip будут записаны.

person Cheeso    schedule 11.05.2009
comment
@Cheeso: вопрос был о библиотеке Java. Мы не можем использовать ваш фрагмент. - person dma_k; 16.03.2010
comment
Я знаю. Я начал ответ с того, что сказал это. Я просто хотел поместить его туда, на случай, если кто-то еще выполнит поиск по переименованию и заархивированию и не будет ограничен Java. - person Cheeso; 16.03.2010

Я думаю, вы сможете найти помощь в решении этой задачи, используя Commons Compress, особенно ZipArchiveEntry

person Valentin Rocher    schedule 11.05.2009

Это делает свое дело. Молниеносно быстро, поскольку работает только с центральным каталогом, а не с файлами.

//  rezip( zipfile, "/main-folder", "/versionXY" );

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;


protected void rezip( String zipfile, String olddir, String newdir ) {

    Path zipFilePath = Paths.get( zipfile );
    try (FileSystem fs = FileSystems.newFileSystem( zipFilePath, null )) {
        Path oldpathInsideZipPath = fs.getPath( olddir );
        if( ! Files.exists( Paths.get( newdir ) ) )
            Files.createDirectory( Paths.get( newdir ) );

        if ( Files.exists( oldpathInsideZipPath, LinkOption.NOFOLLOW_LINKS ) ) {
            Files.walkFileTree(oldpathInsideZipPath, new SimpleFileVisitor<Path>() {
                 @Override
                 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                     throws IOException
                 {
                     if( file.toString().indexOf( olddir ) > -1 ){
                         String a = file.toString().replaceAll( olddir, newdir );
                         Path b = fs.getPath( a );
                         if( ! Files.exists( b.getParent() ) ){
                             Files.createDirectories( b.getParent() );
                         }
                         Files.move( file, b, LinkOption.NOFOLLOW_LINKS );
                     }
                     return FileVisitResult.CONTINUE;
                 }
                 @Override
                 public FileVisitResult postVisitDirectory(Path dir, IOException e)
                     throws IOException
                 {
                     if (e == null) {
                         Files.delete(dir);
                         return FileVisitResult.CONTINUE;
                     } else {
                         // directory iteration failed
                         throw e;
                     }
                 }
             });
        }
        fs.close();
    } catch ( Exception e ) {
        e.printStackTrace();
    }
}
person megasega    schedule 19.08.2016
comment
Мне не нужно было рекурсивное решение, но FileSystems.newFileSystem плюс Files.move сделали свое дело. Единственное предостережение заключается в том, что это создаст обновленную копию zip-файла и удалит старую версию, поэтому для очень больших архивов это все еще может быть немного медленным, и вам нужно достаточно места на диске для копии. Хотя все равно на порядок быстрее. Это позволило мне переименовать файл внутри архива размером более 10 ГБ, создание которого заняло несколько минут всего за несколько секунд. - person Doval; 07.08.2020