Переименование файла/папки внутри ZIP-файла в Java?
Вопрос
У меня есть zip-файл, содержащий структуру папок, например
- основная папка/
- подпапка1/
- подпапка2/
- подпапка3/
- файл3.1
- файл3.2
Я хотел бы переименовать папку main-folder
скажем так versionXY
внутри этого самого zip-файла с использованием Java.
Есть ли более простой способ, чем извлечь весь zip-файл и воссоздать новый, используя новые имена папок?
Решение
Zip — это формат архива, поэтому изменение обычно предполагает перезапись файла.
Некоторые особенности zip также мешают (zip полон «функций»).Как и центральный каталог в конце архива, каждому файлу компонента предшествует его имя.В Zip нет концепции каталогов — имена файлов — это просто строки, которые включают символы «/» (и подстроки, такие как «../».
Итак, вам действительно нужно скопировать файл, используя ZipInputStream
и ZipOutputStream
, переименовывая по ходу дела.Если вы действительно этого хотите, вы можете переписать файл на месте, выполнив собственную буферизацию.Этот процесс действительно приводит к повторному сжатию содержимого, поскольку стандартный API не имеет средств для получения данных в сжатой форме.
Другие советы
Я знаю, что вы спрашивали о Java, но я решил оставить заметку о .NET просто в архивных целях.
ДотНетЗип — это библиотека .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-данные будут записаны.
Я думаю, вы сможете найти помощь для этой задачи, используя Коммонс Сжатие, особенно ZipАрхивЗапись
Это делает трюк.Невероятно быстро, поскольку работает только с центральным каталогом, а не с файлами.
// 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();
}
}