ZIP fájl méretének gyors és pontos becslése Java-ban

    A ZIP fájlok végső méretének becslése nehéz feladat, de a végső fájlméretet viszonylag pontosan is meg lehet határozni olyan módon, hogy mintákat veszünk a fájlból és a mintákon elvégezzük a tömörítést, majd a tömörített minták méretéből megbecsüljük a teljes tömörített állomány méretét.

    Az általam fejlesztett megvalósításban:

    • ha kisebb a fájl a BUFFER_SIZE mintaméretnél, akkor az egészt fájlt betömörítem a memóriában, így a pontos méretet kapom
    • ha nagyobb a fájlméret a mintánál, akkor ZIP_SAMPLES mennyiségű mintát veszek a fájlból, a mintákat összefűzöm egy egységes tömbbé, a minta tömböt tömörítem össze a memóriában, végül a tömörített tömb méretéből kiszámolom a teljes állomány becsült méretét.

    Az így kapott becsült tömörített fájlméret jól közelíti a valódi méretet.

    A teljes osztályt innen lehet letölteni:

    ZipUtils.java

    A következő metódus akár egy több GByte-os fájl méretét is hatékonyan és gyorsan meghatározza:

    public static long sizeOfZippedFile(File file) throws FileNotFoundException, IOException {
            long fullLength = Math.min(Integer.MAX_VALUE, file.length());
            int parts = (int) (fullLength / BUFFER_SIZE);
            if (parts <= ZIP_SAMPLES) {
                return exactSizeOfZippedFile(file);
            }
            byte[] sampleArray = new byte[ZIP_SAMPLES * BUFFER_SIZE];
            long ratio = parts / ZIP_SAMPLES;
            int pos = 0;
            int fullPos = 0;
            try (FileInputStream fis = new FileInputStream(file)) {
                while (fullPos < parts) {
                    if (fullPos % ratio == 0 && pos < ZIP_SAMPLES) {
                        fis.read(sampleArray, pos * BUFFER_SIZE, BUFFER_SIZE);
                        pos += 1;
                    } else {
                        fis.skip(BUFFER_SIZE);
                    }
                    fullPos += 1;
                }
            }
            return (long) (exactSizeOfZippedByteArray(sampleArray) * (((double) file.length()) / sampleArray.length));
        }

     

    Ha a vizsgált fájl mérete elég kicsi, a következő metódusal számoljuk ki a pontos tömörített méretet:

        public static long exactSizeOfZippedFile(File file) throws FileNotFoundException, IOException {
            try (LenghtOutputStream sos = new LenghtOutputStream();) {
                try (ZipOutputStream zos = new ZipOutputStream(sos);) {
                    int bytesIn = 0;
                    byte[] buffer = new byte[1024];
                    try (FileInputStream fis = new FileInputStream(file)) {
                        ZipEntry anEntry = new ZipEntry(file.getName());
                        zos.putNextEntry(anEntry);
                        while ((bytesIn = fis.read(buffer)) != -1) {
                            zos.write(buffer, 0, bytesIn);
                        }
                    }
                }
                return sos.getLength();
            }
        }

     

    A minta tömb tömörített méretét a következő metódussal lehet meghatározni:

        public static long exactSizeOfZippedByteArray(byte[] file)
            throws IOException {
            Deflater oDeflate = new Deflater();
            oDeflate.setInput(file);
            oDeflate.finish();
            try (LenghtOutputStream los = new LenghtOutputStream();) {
                byte[] byRead = new byte[BUFFER_SIZE];
                while (!oDeflate.finished()) {
                    int iBytesRead = oDeflate.deflate(byRead);
                    if (iBytesRead == byRead.length) {
                        los.write(byRead);
                    } else {
                        los.write(byRead, 0, iBytesRead);
                    }
                }
                oDeflate.end();
                return los.getLength();
            }
        }

     

    A streamek pontos ZIP-elt méret meghatározásához nem kell mást tenni, mint készíteni egy dummy OutputStream-et, amely nem tesz mást, csak megszámolja a neki átadott bájtokat:

    public class LengthOutputStream extends OutputStream {
    
        private long length = 0L;
    
        @Override
        public void write(int b) throws IOException {
            length++;
        }
    
        public long getLength() {
            return length;
        }
    }
    

     

    LengthOutputStream-et egyszerűen átadjuk a ZipOutputStream objektumnak, így a tömörített adatfolyamot a ZipOutputStream a LengthOutputStream-nek adja át, ami megszámolja a bájtokat, és a végén le tudjuk kérdezni tőle a ZIP méretét:

    public static long sizeOfZippedDirectory(File dir) throws FileNotFoundException, IOException {
        try (LengthOutputStream sos = new LengthOutputStream();
            ZipOutputStream zos = new ZipOutputStream(sos);) {
            ... // Fájlok hozzáadás a ZIP folyamhoz
            return sos.getLength();
        }
    }