質問
java.util.zip。*
でzipアーカイブを作成する場合、結果のアーカイブを複数のボリュームに分割する方法はありますか?
アーカイブ全体の filesize
が 24 MB
で、ファイルごとに10 MBの制限で3つのファイルに分割したいとしましょう。
この機能を備えたzip APIはありますか?または、これを達成する他の良い方法はありますか?
ありがとう トールステン
解決
チェック: http:// saloon。 javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=38&t=004618
これを行うのに役立つパブリックAPIについては知りません。 (プログラムで実行したくない場合でも、WinSplitterのようなユーティリティがあります)
試したことはありませんが、ZippedInput / OutputStreamの使用中のすべてのZipEntryのサイズは圧縮されています。作成中に、zipファイルのサイズの大まかな見積もりが表示される場合があります。 2MBの圧縮ファイルが必要な場合は、エントリの累積サイズが1.9MBになった後、ファイルへの書き込みを停止できます。マニフェストファイルとその他のzipファイル固有の要素には.1MBが必要です。 つまり、簡単に言えば、次のようにZippedInputStreamにラッパーを書くことができます。
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipEntry;
import java.io.FileOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ChunkedZippedOutputStream {
private ZipOutputStream zipOutputStream;
private String path;
private String name;
private long currentSize;
private int currentChunkIndex;
private final long MAX_FILE_SIZE = 16000000; // Whatever size you want
private final String PART_POSTFIX = ".part.";
private final String FILE_EXTENSION = ".zip";
public ChunkedZippedOutputStream(String path, String name) throws FileNotFoundException {
this.path = path;
this.name = name;
constructNewStream();
}
public void addEntry(ZipEntry entry) throws IOException {
long entrySize = entry.getCompressedSize();
if((currentSize + entrySize) > MAX_FILE_SIZE) {
closeStream();
constructNewStream();
} else {
currentSize += entrySize;
zipOutputStream.putNextEntry(entry);
}
}
private void closeStream() throws IOException {
zipOutputStream.close();
}
private void constructNewStream() throws FileNotFoundException {
zipOutputStream = new ZipOutputStream(new FileOutputStream(new File(path, constructCurrentPartName())));
currentChunkIndex++;
currentSize = 0;
}
private String constructCurrentPartName() {
// This will give names is the form of <file_name>.part.0.zip, <file_name>.part.1.zip, etc.
StringBuilder partNameBuilder = new StringBuilder(name);
partNameBuilder.append(PART_POSTFIX);
partNameBuilder.append(currentChunkIndex);
partNameBuilder.append(FILE_EXTENSION);
return partNameBuilder.toString();
}
}
上記のプログラムはアプローチのヒントに過ぎず、決して最終的な解決策ではありません。
他のヒント
出力がpkzipおよびwinzipと互換性を持つことである場合、これを行うオープンソースライブラリを認識していません。アプリの1つにも同様の要件があり、独自の実装(zip標準に準拠)を作成することにしました。思い出すと、私たちにとって最も難しいことは、個々のファイルをその場で生成する必要があったことです(ほとんどのzipユーティリティの動作は、大きなzipファイルを作成し、後で戻って分割することです-それははるかに簡単です実装に1日、デバッグに2日かかりました。
zip形式は、ファイル形式の外観を説明しています。あなたが少し袖をまくり上げることを恐れないなら、これは間違いなく実行可能です。 zipファイルジェネレーターを自分で実装する必要がありますが、JavaのDeflatorクラスを使用して、圧縮データのセグメントストリームを生成できます。ファイルとセクションヘッダーを自分で生成する必要がありますが、これらは単なるバイトです-一度ダイブすると、それほど難しくありません。
zip仕様-セクションKには探している情報があります具体的には、A、B、C、Fも読む必要があります。本当に大きなファイルを扱っている場合(私たちはそうでした)、Zip64のものにも入らなければなりません-しかし、24 MBの場合は、大丈夫です。
飛び込んで試してみたい場合-質問にぶつかった場合は投稿してください。ポインタを提供できるかどうかを確認します。
以下のコードは、ディレクトリ構造内のzipファイルを目的のサイズに基づいてチャンクに分割する私のソリューションです。私は以前の回答が有用だと感じたので、同様だが少しきちんとしたアプローチで貢献したいと思った。このコードは、私の特定のニーズに応えてくれています。改善の余地があると思います。
private final static long MAX_FILE_SIZE = 1000 * 1000 * 1024; // around 1GB
private final static String zipCopyDest = "C:\\zip2split\\copy";
public static void splitZip(String zipFileName, String zippedPath, String coreId) throws IOException{
System.out.println("process whole zip file..");
FileInputStream fis = new FileInputStream(zippedPath);
ZipInputStream zipInputStream = new ZipInputStream(fis);
ZipEntry entry = null;
int currentChunkIndex = 0;
//using just to get the uncompressed size of the zipentries
long entrySize = 0;
ZipFile zipFile = new ZipFile(zippedPath);
Enumeration enumeration = zipFile.entries();
String copDest = zipCopyDest + "\\" + coreId + "_" + currentChunkIndex +".zip";
FileOutputStream fos = new FileOutputStream(new File(copDest));
BufferedOutputStream bos = new BufferedOutputStream(fos);
ZipOutputStream zos = new ZipOutputStream(bos);
long currentSize = 0;
try {
while ((entry = zipInputStream.getNextEntry()) != null && enumeration.hasMoreElements()) {
ZipEntry zipEntry = (ZipEntry) enumeration.nextElement();
System.out.println(zipEntry.getName());
System.out.println(zipEntry.getSize());
entrySize = zipEntry.getSize();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//long entrySize = entry.getCompressedSize();
//entrySize = entry.getSize(); //gives -1
if((currentSize + entrySize) > MAX_FILE_SIZE) {
zos.close();
//construct a new stream
//zos = new ZipOutputStream(new FileOutputStream(new File(zippedPath, constructCurrentPartName(coreId))));
currentChunkIndex++;
zos = getOutputStream(currentChunkIndex, coreId);
currentSize = 0;
}else{
currentSize += entrySize;
zos.putNextEntry(new ZipEntry(entry.getName()));
byte[] buffer = new byte[8192];
int length = 0;
while ((length = zipInputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
byte[] unzippedFile = outputStream.toByteArray();
zos.write(unzippedFile);
unzippedFile = null;
outputStream.close();
zos.closeEntry();
}
//zos.close();
}
} finally {
zos.close();
}
}
public static ZipOutputStream getOutputStream(int i, String coreId) throws IOException {
System.out.println("inside of getOutputStream()..");
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipCopyDest + "\\" + coreId + "_" + i +".zip"));
// out.setLevel(Deflater.DEFAULT_COMPRESSION);
return out;
}
public static void main(String args[]) throws IOException{
String zipFileName = "Large_files _for_testing.zip";
String zippedPath= "C:\\zip2split\\Large_files _for_testing.zip";
String coreId = "Large_files _for_testing";
splitZip(zipFileName, zippedPath, coreId);
}
価値があるもののために、私はどこでも try-with-resources を使用するのが好きです。あなたがそのデザインパターンに興味があるなら、あなたはこれが好きでしょう。また、エントリが目的のパーツサイズより大きい場合、空のパーツの問題を解決します。最悪の場合、少なくとも エントリと同じ数の部分があります。
In:
my-archive.zip
アウト:
my-archive.part1of3.zip
my-archive.part2of3.zip
my-archive.part3of3.zip
注:ロギングとApache Commons FilenameUtilsを使用していますが、ツールキットにあるものを自由に使用してください。
/**
* Utility class to split a zip archive into parts (not volumes)
* by attempting to fit as many entries into a single part before
* creating a new part. If a part would otherwise be empty because
* the next entry won't fit, it will be added anyway to avoid empty parts.
*
* @author Eric Draken, 2019
*/
public class Zip
{
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
private static final String ZIP_PART_FORMAT = "%s.part%dof%d.zip";
private static final String EXT = "zip";
private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() );
/**
* Split a large archive into smaller parts
*
* @param zipFile Source zip file to split (must end with .zip)
* @param outZipFile Destination zip file base path. The "part" number will be added automatically
* @param approxPartSizeBytes Approximate part size
* @throws IOException Exceptions on file access
*/
public static void splitZipArchive(
@NotNull final File zipFile,
@NotNull final File outZipFile,
final long approxPartSizeBytes ) throws IOException
{
String basename = FilenameUtils.getBaseName( outZipFile.getName() );
Path basePath = outZipFile.getParentFile() != null ? // Check if this file has a parent folder
outZipFile.getParentFile().toPath() :
Paths.get( "" );
String extension = FilenameUtils.getExtension( zipFile.getName() );
if ( !extension.equals( EXT ) )
{
throw new IllegalArgumentException( "The archive to split must end with ." + EXT );
}
// Get a list of entries in the archive
try ( ZipFile zf = new ZipFile( zipFile ) )
{
// Silliness check
long minRequiredSize = zipFile.length() / 100;
if ( minRequiredSize > approxPartSizeBytes )
{
throw new IllegalArgumentException(
"Please select a minimum part size over " + minRequiredSize + " bytes, " +
"otherwise there will be over 100 parts."
);
}
// Loop over all the entries in the large archive
// to calculate the number of parts required
Enumeration<? extends ZipEntry> enumeration = zf.entries();
long partSize = 0;
long totalParts = 1;
while ( enumeration.hasMoreElements() )
{
long nextSize = enumeration.nextElement().getCompressedSize();
if ( partSize + nextSize > approxPartSizeBytes )
{
partSize = 0;
totalParts++;
}
partSize += nextSize;
}
// Silliness check: if there are more parts than there
// are entries, then one entry will occupy one part by contract
totalParts = Math.min( totalParts, zf.size() );
logger.debug( "Split requires {} parts", totalParts );
if ( totalParts == 1 )
{
// No splitting required. Copy file
Path outFile = basePath.resolve(
String.format( ZIP_PART_FORMAT, basename, 1, 1 )
);
Files.copy( zipFile.toPath(), outFile );
logger.debug( "Copied {} to {} (pass-though)", zipFile.toString(), outFile.toString() );
return;
}
// Reset
enumeration = zf.entries();
// Split into parts
int currPart = 1;
ZipEntry overflowZipEntry = null;
while ( overflowZipEntry != null || enumeration.hasMoreElements() )
{
Path outFilePart = basePath.resolve(
String.format( ZIP_PART_FORMAT, basename, currPart++, totalParts )
);
overflowZipEntry = writeEntriesToPart( overflowZipEntry, zf, outFilePart, enumeration, approxPartSizeBytes );
logger.debug( "Wrote {}", outFilePart );
}
}
}
/**
* Write an entry to the to the outFilePart
*
* @param overflowZipEntry ZipEntry that didn't fit in the last part, or null
* @param inZipFile The large archive to split
* @param outFilePart The part of the archive currently being worked on
* @param enumeration Enumeration of ZipEntries
* @param approxPartSizeBytes Approximate part size
* @return Overflow ZipEntry, or null
* @throws IOException File access exceptions
*/
private static ZipEntry writeEntriesToPart(
@Nullable ZipEntry overflowZipEntry,
@NotNull final ZipFile inZipFile,
@NotNull final Path outFilePart,
@NotNull final Enumeration<? extends ZipEntry> enumeration,
final long approxPartSizeBytes
) throws IOException
{
try (
ZipOutputStream zos =
new ZipOutputStream( new FileOutputStream( outFilePart.toFile(), false ) )
)
{
long partSize = 0;
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
while ( overflowZipEntry != null || enumeration.hasMoreElements() )
{
ZipEntry entry = overflowZipEntry != null ? overflowZipEntry : enumeration.nextElement();
overflowZipEntry = null;
long entrySize = entry.getCompressedSize();
if ( partSize + entrySize > approxPartSizeBytes )
{
if ( partSize != 0 )
{
return entry; // Finished this part, but return the dangling ZipEntry
}
// Add the entry anyway if the part would otherwise be empty
}
partSize += entrySize;
zos.putNextEntry( entry );
// Get the input stream for this entry and copy the entry
try ( InputStream is = inZipFile.getInputStream( entry ) )
{
int bytesRead;
while ( (bytesRead = is.read( buffer )) != -1 )
{
zos.write( buffer, 0, bytesRead );
}
}
}
return null; // Finished splitting
}
}