كيفية تقسيم ملف مضغوط ضخم إلى مجلدات متعددة؟

StackOverflow https://stackoverflow.com/questions/243992

  •  04-07-2019
  •  | 
  •  

سؤال

عندما أقوم بإنشاء أرشيف مضغوط عبر java.util.zip.*, هل هناك طريقة لتقسيم الأرشيف الناتج إلى مجلدات متعددة؟

لنفترض أن أرشيفي الإجمالي يحتوي على filesize ل 24 MB وأريد تقسيمه إلى 3 ملفات بحد أقصى 10 ميغابايت لكل ملف.
هل هناك Zip API الذي يحتوي على هذه الميزة؟أو أي طرق لطيفة أخرى لتحقيق ذلك؟

شكرا Thollsten

هل كانت مفيدة؟

المحلول

وتحقق: HTTP: // الصالون. javaranch.com/cgi-bin/ubb/ultimatebb.cgi؟ubb=get_topic&f=38&t=004618

أنا لست على علم بأي API العامة التي من شأنها أن تساعدك على القيام بذلك. (ولكن إذا كنت لا تريد أن تفعل ذلك برمجيا، هناك أدوات مساعدة مثل WinSplitter من شأنها أن تفعل ذلك)

لم أحاول ذلك، ولكن، كل ZipEntry أثناء استخدام ZippedInput / OutputStream يبلغ حجم مضغوط. قد تحصل على تقدير تقريبي لحجم ملف مضغوط في حين إنشائه. اذا كنت بحاجة الى 2MB من ملفات مضغوطة، ثم يمكنك التوقف عن الكتابة إلى ملف بعد الحجم التراكمي من إدخالات تصبح 1.9MB، مع .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 وبرنامج لضغط الملفات، وأنا لست على علم بأي المكتبات مفتوحة المصدر أن تفعل هذا. كان لدينا شرط مماثل لأحد التطبيقات لدينا، وأنا أنهى كتابة تنفيذ منطقتنا (متوافق مع معيار البريدي). إذا ما أذكر، كان أصعب شيء بالنسبة لنا أن لدينا لإنشاء ملفات فردية على الطاير (الطريقة أن معظم المرافق البريدي العمل هو أنها تخلق ملف مضغوط كبير، ثم نعود وتقسيمه فيما بعد - وهذا أسهل كثيرا ل تنفيذ. وأحاط حوالي يوم واحد لكتابة و2 أيام التصحيح.

والمعيار البريدي يفسر ما لديه تنسيق ملف لتبدو وكأنها. إذا لم تكن خائفا من تشمير الأكمام الخاص بك قليلا، وهذا هو بالتأكيد قابلة للتنفيذ. لديك لتنفيذ مولد ملف مضغوط نفسك، ولكن يمكنك استخدام الطبقة امتصاص التضخم جافا لتوليد تيارات شريحة لضغط البيانات. سيكون لديك لإنشاء ملف ورؤوس المقاطع نفسك، لكنها بايت فقط - لا شيء صعبا للغاية بمجرد الغوص في

وهنا يكمن البريدي - قسم K لديه معلومات كنت تبحث عن على وجه التحديد، ولكن عليك أن تقرأ A، B، C وF كذلك. إذا كنت تتعامل مع الملفات الكبيرة حقا (كنا)، سيكون لديك للوصول الى الاشياء ZIP64 كذلك - ولكن لمدة 24 MB وأنتم بخير

إذا كنت تريد الغطس في وذلك في محاولة - إذا واجهت الأسئلة، إضافة إلى الوراء وسوف نرى ما اذا كان يمكن أن توفر بعض المؤشرات

وفيما يلي متاحة لي حل لتقسيم ملف مضغوط في بنية الدليل إلى قطع بناء على الحجم المطلوب. لقد وجدت الإجابات السابقة مفيدة جدا، أراد أن يساهم مع نهج مماثل ولكن قليلا أكثر أنيق. يعمل هذا الرمز بالنسبة لي لاحتياجاتي معينة، وأعتقد أن هناك مجالا للتحسين.

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);
}

لما يستحق، أحب استخدامه حاول مع الموارد في كل مكان.إذا كنت من محبي نمط التصميم هذا، فسوف يعجبك هذا.كما أن هذا يحل مشكلة الأجزاء الفارغة إذا كانت الإدخالات أكبر من حجم الجزء المطلوب.سوف تفعلها على الأقل تحتوي على العديد من الأجزاء مثل الإدخالات في أسوأ الحالات.

في:

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
        }
    }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top