Andmete kokkusurumine Huffmani algoritmiga

Sissejuhatus

Selles artiklis rÀÀgin ma tuntud Huffmani algoritmist ja selle rakendamisest andmete tihendamisel.

KokkuvĂ”ttes kirjutame lihtsa arhivaatri. Sellest on juba olnud artikkel Habrist, kuid praktilise rakenduseta. KĂ€esoleva postituse teoreetilised materjalid on saadud kooli informaatika tundidest ja Robert LaFore'i raamatust „Data Structures and Algorithms in Java“. Nii et, kĂ”ik allolevat lugema!

Veidi mÔtteid

Tavalises tekstifailis kodeeritakse ĂŒks sĂŒmbol 8 bitiga (ASCII kodeering) vĂ”i 16 bitiga (Unicode kodeering). JĂ€rgmisena vaatleme ASCII kodeeringut. NĂ€iteks vĂ”tame stringi s1 = "SUSIE SAYS IT IS EASYn". Kokku on stringis 22 sĂŒmbolit, sealhulgas tĂŒhikud ja rida vahetamise sĂŒmbol — 'n'. Fail, mis sisaldab seda stringi, kaalub 22*8 = 176 bitti. Tekib kohe kĂŒsimus: kas on mĂ”istlik kasutada kĂ”iki 8 bitti ĂŒhe sĂŒmboli kodeerimiseks? Me ei kasuta ju kĂ”iki ASCII kodeeringu sĂŒmboleid. Isegi kui kasutaksime, oleks mĂ”istlikum kĂ”ige sagedasema tĂ€he — S — jaoks anda lĂŒhim vĂ”imalik kood ning kĂ”ige haruldasema tĂ€he — T (vĂ”i U, vĂ”i 'n') jaoks anda pikem kood. See ongi Huffmani algoritmi pĂ”himĂ”te: leida optimaalne kodeerimisvariant, mille korral fail on minimaalse kaaluga. On tĂ€iesti normaalne, et erinevatel sĂŒmbolitel on koodipikkused erinevad — see ongi algoritmi toime.

Kodeerimine

Miks ei vĂ”iks sĂŒmbol ‘S’ saada koodi, nĂ€iteks, 1 bitiga: 0 vĂ”i 1. Oletame, et see on 1. Siis anname teisele sagedamini esinevale sĂŒmbolile — ‘ ‘(tĂŒhik) — 0. Kujutage ette, et hakkate dekodeerima oma sĂ”numit — kodeeritud stringi s1 — ja nĂ€ete, et kood algab 1-ga. Nii et, mida teha: kas see on sĂŒmbol S vĂ”i mĂ”ni muu sĂŒmbol, nĂ€iteks A? SeetĂ”ttu tekib oluline reegel:

Ükski kood ei tohi olla teise eesliide

See reegel on algoritmi vĂ”tmeelement. SeetĂ”ttu alustatakse koodi loomist sagedustabelist, kus on nĂ€idatud iga sĂŒmboli sagedus (esinevuste arv):

Andmete kokkusurumine Huffmani algoritmiga SĂŒmbolid, millel on kĂ”ige rohkem esinemisi, peaksid olema kodeeritud vĂ”imalikult vĂ€heste bitidega. Tooksin nĂ€ite ĂŒhest vĂ”imalikust kooditabelist:

Andmete kokkusurumine Huffmani algoritmiga Nii et, kodeeritud sÔnum nÀeb vÀlja nii:

10 01111 10 110 1111 00 10 010 1110 10 00 110 0110 00 110 10 00 1111 010 10 1110 01110

Iga sĂŒmboli koodi eraldasin tĂŒhikuga. TĂ”eliselt tihendatud failis sellist ei ole!
TĂ”statub kĂŒsimus: kuidas see nooruk mĂ”tles vĂ€lja koodi koodide tabeli loomiseks? Sellest allpool.

Huffman'i puu loomine

Siin tulevad appi binaarsed otsingupuud. Ära muretse, siin ei ole vajalikud otsimise, sisestamise ja kustutamise meetodid. Siin on puu struktuur Java’s:

public class Node {
    private int frequence;
    private char letter;
    private Node leftChild;
    private Node rightChild;
    ...
}

class BinaryTree {
    private Node root;

    public BinaryTree() {
        root = new Node();
    }
    public BinaryTree(Node root) {
        this.root = root;
    }
    ...
}

See ei ole tÀielik kood, tÀielik kood tuleb allpool.

Siin on ise algoritm puu loomise jaoks:

  1. Loo iga sĂŒmboli jaoks Node objekt sĂ”numist (string s1). Meie puhul tuleb 9 sĂ”lme (Node objekti). Iga sĂ”lm koosneb kahest andmevĂ€ljast: sĂŒmbol ja sagedus.
  2. Loo BinaryTree objekt igasuguse Node sÔlme jaoks. SÔlm muutub puu juureks.
  3. Sisesta need puud prioriteetjÀrjekorda. Mida madalam on sagedus, seda suurem on prioriteet. Seega valitakse alati vÀhima sagedusega puu.

SeejĂ€rel tuleb tsĂŒkliliselt teha jĂ€rgmist:

  1. TĂ”sta kaks puu prioriteetjĂ€rjekorrast ja tee neist uue sĂ”lme (just loodud sĂ”lm ilma sĂŒmbolita) jĂ€reltulijad. Uue sĂ”lme sagedus on kahe jĂ€reltulija puu sageduste summa.
  2. Selle sÔlme jaoks loo puu, mille juur on antud sÔlm. Aseta see puu tagasi prioriteetjÀrjekorda. (Kuna puul on uus sagedus, ilmselt jÀÀb see jÀrjekorras uude kohta.)
  3. JĂ€tka sammude 1 ja 2 tĂ€itmist, kuni jĂ€rjekorras jÀÀb ainult ĂŒks puu — Huffmani puu.

RÀÀgime sellest algoritmist stringil s1:

Andmete kokkusurumine Huffmani algoritmiga

Siin tĂ€histab sĂŒmbol „lf“ (linefeed) uut rida, „sp“ (space) on tĂŒhik.

Ja mis siis edasi?

Me saime Huffmani puu. Noh, okei. Ja mida sellega teha? Seda ei vĂ”eta isegi tasuta. SeejĂ€rel tuleb jĂ€lgida kĂ”iki vĂ”imalikke teid juurest puu lehtedeni. Kokku leppida, et serv 0 tĂ€histab vasakut jĂ€reltulijat ja 1 – paremat. Rangelt öeldes, antud tĂ€histustes on sĂŒmboli kood tee puu juurest lehteni, mis sisaldab antud sĂŒmbolit.

Andmete kokkusurumine Huffmani algoritmiga

Nii, nĂŒĂŒd on meil tekkinud koodide tabel. Kui seda tabelit vaadata, siis vĂ”ime jĂ€reldada iga sĂŒmboli „kaalu“ — see on tema koodi pikkus. SeetĂ”ttu kaalub algfail kokku: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bitti. Alguses oli selle kaal 176 bitti. Seega oleme seda vĂ€hendanud 176/65 = 2.7 korda! Kuid see on utoopia. Sellist koefitsenti on ebatĂ”enĂ€oline saavutada. Miks? Sellest rÀÀgime veidi hiljem.

Dekodeerimine

Noh, ilmselt on jÀÀnud kĂ”ige lihtsam — dekodeerimine. Ma arvan, et paljud teist mĂ”istsid, et lihtsalt kokku suruda fail ilma igasuguste vihjeteta selle kodeerimise kohta ei Ă”nnestu — me ei suuda seda dekodeerida! Jah, mul oli seda raske mĂ”ista, aga peame looma tekstifaili table.txt kompressioonitabeliga:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Tabeli kirjutamine kujul 'sĂŒmbol'«sĂŒmboli kood». Miks on 01110 ilma sĂŒmbolita? Tegelikult on seal sĂŒmbol, lihtsalt java vahendid, mida kasutasin faili vĂ€ljundiks, konverteerivad rea vahetuse sĂŒmboli — 'n' — rea vahetuseks (kui rumal see ka ei tunduks). SeetĂ”ttu on ĂŒlal tĂŒĂŒtu tĂŒhi rida sĂŒmbol koodi 01110 jaoks. Koodi 00 sĂŒmboliks on rea alguses tĂŒhik. Ütlen kohe, et meie koefitsiendile on selle tabeli salvestamise viis ilmselt kĂ”ige ebaefektiivsem. Kuid see on arusaadav ja teostatav. Ootan hea meelega teie soovitusi kommentaarides optimeerimise kohta.

Selle tabeliga on dekodeerimine vÀga lihtne. Meenutame, millist reeglit kasutasime kodeerimise loomisel:

Ükski kood ei tohi olla teise prefix.

Siin mĂ€ngib see hĂ”lbustavat rolli. Me loeme jĂ€rjestikku bitti ja kui saadud string d, mis koosneb loetud bittidest, vastab koodile, mis vastab sĂŒmbolile character, teame kohe, et sĂŒmbol character on kodeeritud (ja ainult tema!). JĂ€tkame character'i kirjutamist dekodeerimistringi (string, mis sisaldab dekodeeritud sĂ”numit), seadistame stringi d nulliks ja loeme edasi kodeeritud faili.

Rakendamine

On aeg alandada minu koodi kirjutada arhivator. Nimeks paneme sellele Compressor.

Alustame algusest. Esmalt kirjutame klassi Node:

public class Node {
    private int frequence; // sagedus
    private char letter; // tÀht
    private Node leftChild; // vasak laps
    private Node rightChild; // parempoolne laps

    public Node(char letter, int frequence) { // konstruktor
        this.letter = letter;
        this.frequence = frequence;
    }

    public Node() {} // konstruktor ilma nimega (vt ĂŒlal ĂŒhtepuutuv osa Huffmani puu ehitamisest)
    public void addChild(Node newNode) { // lisada laps
        if (leftChild == null) // kui vasak on tĂŒhi => parem ka tĂŒhjaks => lisame vasakule
            leftChild = newNode;
        else {
            if (leftChild.getFrequence() <= newNode.getFrequence()) // sisuliselt, vasak laps
                rightChild = newNode; // saab see, kelle sagedus on vÀiksem
            else {
                rightChild = leftChild;
                leftChild = newNode;
            }
        }

        frequence += newNode.getFrequence(); // lÔpp sagedus
    }

    public Node getLeftChild() {
        return leftChild;
    }

    public Node getRightChild() {
        return rightChild;
    }

    public int getFrequence() {
        return frequence;
    }

    public char getLetter() {
        return letter;
    }

    public boolean isLeaf() { // lehe kontroll
        return leftChild == null && rightChild == null;
    }
}

NĂŒĂŒd puu:

class BinaryTree {
    private Node root;

    public BinaryTree() {
        root = new Node();
    }

    public BinaryTree(Node root) {
        this.root = root;
    }

    public int getFrequence() {
        return root.getFrequence();
    }

    public Node getRoot() {
        return root;
    }
}

Prioriteetne jÀrjekord:

import java.util.ArrayList; // jah, jÀrjekord pÔhineb loendil

class PriorityQueue {
    private ArrayList data; // jÀrjekorra loend
    private int nElems; // elementide arv jÀrjekorras

    public PriorityQueue() {
        data = new ArrayList();
        nElems = 0;
    }

    public void insert(BinaryTree newTree) { // sisestamine
        if (nElems == 0)
            data.add(newTree);
        else {
            for (int i = 0; i  newTree.getFrequence()) { // kui sisestatud puu sagedus on vÀiksem
                    data.add(i, newTree); // kui on vĂ€iksem, nihutame kĂ”ik puud paremal positsioonil ĂŒhe vĂ”rra edasi
                    break; // seejÀrel asetame uue puu praeguse positsiooni kohale
                }
                if (i == nElems - 1) 
                    data.add(newTree);
            }
        }
        nElems++; // suurendame elementide arvu 1 vÔrra
    }

    public BinaryTree remove() { // eemaldamine jÀrjekorrast
        BinaryTree tmp = data.get(0); // kopeerime eemaldatava elemendi
        data.remove(0); // eemaldame
        nElems--; // vÀhendame elementide arvu 1 vÔrra
        return tmp; // tagastame eemaldatud elemendi (element, millel on kÔige madalam sagedus)
    }
}

Klass, mis loob Huffmani puu:

public class HuffmanTree {
    private final byte ENCODING_TABLE_SIZE = 127; // kooditabeli pikkus
    private String myString; // sÔnum
    private BinaryTree huffmanTree; // Huffmani puu
    private int[] freqArray; // sagedustabel
    private String[] encodingArray; // kodeerimistabel


    // ----------------constructor----------------------
    public HuffmanTree(String newString) {
        myString = newString;

        freqArray = new int[ENCODING_TABLE_SIZE];
        fillFrequenceArray();

        huffmanTree = getHuffmanTree();

        encodingArray = new String[ENCODING_TABLE_SIZE];
        fillEncodingArray(huffmanTree.getRoot(), "", "");
    }

    // --------------------frequence array------------------------
    private void fillFrequenceArray() {
        for (int i = 0; i < myString.length(); i++) {
            freqArray[(int)myString.charAt(i)]++;
        }
    }

    public int[] getFrequenceArray() {
        return freqArray;
    }

    // ------------------------huffman tree creation------------------
    private BinaryTree getHuffmanTree() {
        PriorityQueue pq = new PriorityQueue();
        // algoritm eespool
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            if (freqArray[i] != 0) { // kui sĂŒmbol eksisteerib stringis
                Node newNode = new Node((char) i, freqArray[i]); // luua Node
                BinaryTree newTree = new BinaryTree(newNode); // ning Node'ile luua BinaryTree
                pq.insert(newTree); // sisestada jÀrjekorda
            }
        }

        while (true) {
            BinaryTree tree1 = pq.remove(); // eemaldada jÀrjekorrast esimene puu.

            try {
                BinaryTree tree2 = pq.remove(); // eemaldada jÀrjekorrast teine puu

                Node newNode = new Node(); // luua uus Node
                newNode.addChild(tree1.getRoot()); // teha kahest eemaldatud puust selle jÀreltulijad
                newNode.addChild(tree2.getRoot());

                pq.insert(new BinaryTree(newNode));
            } catch (IndexOutOfBoundsException e) { // jĂ€rjekorras jĂ€i alles ĂŒks puu
                return tree1;
            }
        }
    }

    public BinaryTree getTree() {
        return huffmanTree;
    }

    // -------------------encoding array------------------
    void fillEncodingArray(Node node, String codeBefore, String direction) { // tÀita kodeerimistabel
        if (node.isLeaf()) {
            encodingArray[(int)node.getLetter()] = codeBefore + direction;
        } else {
            fillEncodingArray(node.getLeftChild(), codeBefore + direction, "0");
            fillEncodingArray(node.getRightChild(), codeBefore + direction, "1");
        }
    }

    String[] getEncodingArray() {
        return encodingArray;
    }

    public void displayEncodingArray() { // veaks
        fillEncodingArray(huffmanTree.getRoot(), "", "");

        System.out.println("======================Encoding table====================");
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            if (freqArray[i] != 0) {
                System.out.print((char)i + " ");
                System.out.println(encodingArray[i]);
            }
        }
        System.out.println("========================================================");
    }
    // -----------------------------------------------------
    String getOriginalString() {
        return myString;
    }
}

Klass, mis sisaldab kodeerimist/dekodeerimist:

public class HuffmanOperator {
    private final byte ENCODING_TABLE_SIZE = 127; // tabeli pikkus
    private HuffmanTree mainHuffmanTree; // Huffmani puu (kasutatakse ainult kokkusurumiseks)
    private String myString; // algne sÔnum
    private int[] freqArray; // sagedustabel
    private String[] encodingArray; // kodeerimistabel
    private double ratio; // kokkusurumise suhe 


    public HuffmanOperator(HuffmanTree MainHuffmanTree) { // kokkusurumiseks
        this.mainHuffmanTree = MainHuffmanTree;

        myString = mainHuffmanTree.getOriginalString();

        encodingArray = mainHuffmanTree.getEncodingArray();

        freqArray = mainHuffmanTree.getFrequenceArray();
    }

    public HuffmanOperator() {} // vÀljavÔtmiseks;

    // ---------------------------------------kokkusurumine-----------------------------------------------------------
    private String getCompressedString() {
        String compressed = "";
        String intermidiate = ""; // vahestring (ilma tÀiendavate nullideta)
        // System.out.println("=============================Compression=======================");
        // displayEncodingArray();
        for (int i = 0; i 
        // tuleb lisada nullid lÔpuks (vÔib olla 1, pole vahet)
        byte counter = 0; // lisatud nullide arv (baidi jaoks piisab: 0 <= counter < 8 < 127)
        for (int length = intermidiate.length(), delta = 8 - length % 8; 
                counter < delta; counter++) { // delta - lisatud nullide arv
            intermidiate += "0";
        }
        
        // liida lisatud nullide arv binaarses esituses ja vahestring 
        compressed = String.format("%8s", Integer.toBinaryString(counter & 0xff)).replace(" ", "0") + intermidiate;
                
        // idealiseeritud suhe
        setCompressionRatio();
        // System.out.println("===============================================================");
        return compressed;
    }
    
    private void setCompressionRatio() { // ideaalset suhet arvutada 
        double sumA = 0, sumB = 0; // A - originaalsumma
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            if (freqArray[i] != 0) {
                sumA += 8 * freqArray[i];
                sumB += encodingArray[i].length() * freqArray[i];
            }
        }
        ratio = sumA / sumB;
    }

    public byte[] getBytedMsg() { // lÔplik kokkusurumine
        StringBuilder compressedString = new StringBuilder(getCompressedString());
        byte[] compressedBytes = new byte[compressedString.length() / 8];
        for (int i = 0; i < compressedBytes.length; i++) {
                compressedBytes[i] = (byte) Integer.parseInt(compressedString.substring(i * 8, (i + 1) * 8), 2);
        }
        return compressedBytes;
    }
    // ---------------------------------------kokkusurumise lÔpp----------------------------------------------------------------
    // ------------------------------------------------------------vÀljavÔttmine-----------------------------------------------------
    public String extract(String compressed, String[] newEncodingArray) {
        String decompressed = "";
        String current = "";
        String delta = "";
        encodingArray = newEncodingArray;
        
        // displayEncodingArray();
        // saada teada lisatud nullide arv
        for (int i = 0; i < 8; i++) 
            delta += compressed.charAt(i);
        int ADDED_ZEROES = Integer.parseInt(delta, 2);
       
        for (int i = 8, l = compressed.length() - ADDED_ZEROES; i < l; i++) {
            // i = 8, kuna esimeseks bait on lisatud nullide arv
            current += compressed.charAt(i);
            for (int j = 0; j < ENCODING_TABLE_SIZE; j++) {
                if (current.equals(encodingArray[j])) { // kui sobib
                    decompressed += (char)j; // siis lisame elemendi
                    current = ""; // ja nullime praeguse stringi
                }
            }
        }

        return decompressed;
    }

    public String getEncodingTable() {
        String enc = "";
        for (int i = 0; i < encodingArray.length; i++) {
            if (freqArray[i] != 0) 
                enc += (char)i + encodingArray[i] + 'n';
        }
        return enc;
    }

    public double getCompressionRatio() {
        return ratio;
    }


    public void displayEncodingArray() { // silumise jaoks
        System.out.println("======================Kodeerimistabel====================");
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            // if (freqArray[i] != 0) {
                System.out.print((char)i + " ");
                System.out.println(encodingArray[i]);
            // }
        }
        System.out.println("========================================================");
    }
}

Klass, mis lihtsustab faili kirjutamist:

import java.io.File;
import java.io.PrintWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Closeable;

public class FileOutputHelper implements Closeable {
    private File outputFile;
    private FileOutputStream fileOutputStream;

    public FileOutputHelper(File file) throws FileNotFoundException {
        outputFile = file;
        fileOutputStream = new FileOutputStream(outputFile);
    }

    public void writeByte(byte msg) throws IOException {
        fileOutputStream.write(msg);
    }

    public void writeBytes(byte[] msg) throws IOException {
        fileOutputStream.write(msg);
    }

    public void writeString(String msg) {
    	try (PrintWriter pw = new PrintWriter(outputFile)) {
    		pw.write(msg);
    	} catch (FileNotFoundException e) {
    		System.out.println("Vale tee, vÔi sellist faili ei eksisteeri!");
    	}
    }

    @Override
    public void close() throws IOException {
        fileOutputStream.close();
    }

    public void finalize() throws IOException {
        close();
    }
}

Klass, mis lihtsustab faili lugemist:

import java.io.FileInputStream;
import java.io.EOFException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;

public class FileInputHelper implements Closeable {
	private FileInputStream fileInputStream;
	private BufferedReader fileBufferedReader;
	
	public FileInputHelper(File file) throws IOException {
		fileInputStream = new FileInputStream(file);
		fileBufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
	}
	
	
    public byte readByte() throws IOException {
    	int cur = fileInputStream.read();
    	if (cur == -1) // kui fail on lÔppenud
    		throw new EOFException();
    	return (byte)cur;
    }
    
    public String readLine() throws IOException {
    	return fileBufferedReader.readLine();
    }
    
    @Override
    public void close() throws IOException{
    	fileInputStream.close();
    }
}

Ja peamine klass:

import java.io.File;
import java.nio.charset.MalformedInputException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import java.util.List;
import java.io.EOFException;
public class Main {
	private static final byte ENCODING_TABLE_SIZE = 127;
	
    public static void main(String[] args) throws IOException {
        try { // specify instruction using command line arguments
            if (args[0].equals("--compress") || args[0].equals("-c"))
                compress(args[1]);
            else if ((args[0].equals("--extract") || args[0].equals("-x"))
            		&& (args[2].equals("--table") || args[2].equals("-t"))) {
            	extract(args[1], args[3]);
            }
            else
                throw new IllegalArgumentException();
        } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException e) {
            System.out.println("Vale vormingud on vale");
            System.out.println("Palun vaadake Readme.txt");
            e.printStackTrace();
        }
    }

	public static void compress(String stringPath) throws IOException {
        List stringList;
        File inputFile = new File(stringPath);
        String s = "";
        File compressedFile, table;
        
        try {
            stringList = Files.readAllLines(Paths.get(inputFile.getAbsolutePath()));
        } catch (NoSuchFileException e) {
            System.out.println("Vale rada, vÔi sellist faili ei eksisteeri!");
            return;
        } catch (MalformedInputException e) {
        	System.out.println("Faili praegune kodeering ei ole toetatud");
        	return;
        }

        for (String item : stringList) {
            s += item;
            s += 'n';
        }

        HuffmanOperator operator = new HuffmanOperator(new HuffmanTree(s));

        compressedFile = new File(inputFile.getAbsolutePath() + ".cpr");
        compressedFile.createNewFile();
        try (FileOutputHelper fo = new FileOutputHelper(compressedFile)) {
        	fo.writeBytes(operator.getBytedMsg());
        }
        // create file with encoding table:
        
        table = new File(inputFile.getAbsolutePath() + ".table.txt");
        table.createNewFile();
        try (FileOutputHelper fo = new FileOutputHelper(table)) {
        	fo.writeString(operator.getEncodingTable());
        }
        
        System.out.println("Komprimitud faili rada: " + compressedFile.getAbsolutePath());
        System.out.println("Kodeerimistabeli rada " + table.getAbsolutePath());
        System.out.println("Ilma tabelita faili ei saa taastada!");
        
        double idealRatio = Math.round(operator.getCompressionRatio() * 100) / (double) 100; // idealiseeritud koefitsent
        double realRatio = Math.round((double) inputFile.length() 
        		/ ((double) compressedFile.length() + (double) table.length()) * 100) / (double)100; // reaalne koefitsent
        
        System.out.println("Ideaalne kompressioonikoefitsent on " + idealRatio);
        System.out.println("Kompressioonikoefitsent arvestades kodeerimistabelit " + realRatio);
    }

    public static void extract(String filePath, String tablePath) throws FileNotFoundException, IOException {
        HuffmanOperator operator = new HuffmanOperator();
        File compressedFile = new File(filePath),
        	 tableFile = new File(tablePath),
        	 extractedFile = new File(filePath + ".xtr");
        String compressed = "";
        String[] encodingArray = new String[ENCODING_TABLE_SIZE];
        // read compressed file
        //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!check here:
        try (FileInputHelper fi = new FileInputHelper(compressedFile)) {
        	byte b;
        	while (true) {
        		b = fi.readByte(); // method returns EOFException
        		compressed += String.format("%8s", Integer.toBinaryString(b & 0xff)).replace(" ", "0");
        	}
        } catch (EOFException e) {
        	
        }
        
        //--------------------
        
        // read encoding table:
        try (FileInputHelper fi = new FileInputHelper(tableFile)) {
        	fi.readLine(); // skip first empty string
        	encodingArray[(byte)'n'] = fi.readLine(); // read code for 'n'
        	while (true) {
        		String s = fi.readLine();
        		if (s == null)
        			throw new EOFException();
        		encodingArray[(byte)s.charAt(0)] = s.substring(1, s.length());
        	}
        } catch (EOFException ignore) {}
        
        extractedFile.createNewFile();
        // extract:
		try (FileOutputHelper fo = new FileOutputHelper(extractedFile)) {
			fo.writeString(operator.extract(compressed, encodingArray));
		}
		
		System.out.println("TaaskÀitatud faili rada " + extractedFile.getAbsolutePath());
    }
}

Te peate ise kirjutama readme.txt faili koos juhistega 🙂

KokkuvÔte

TĂ”enĂ€oliselt on see kĂ”ik, mida ma öelda tahtsin. Kui teil on midagi öelda minu ebakompetentsuse kohta koodikitsendustes, algoritmis vĂ”i ĂŒldse mĂ”nes optimeerimises, kirjutage julgelt. Kui ma ei ole midagi piisavalt selgitanud, kirjutage samuti. Ootan teid kommenteerimisse!

P.S.

Jah, ma olen endiselt siin, sest ma ei unustanud koefitsienti. Stringi s1 kodeeringutabel kaalub 48 baiti — palju rohkem kui algne fail, ja me ei unustanud lisada nullide arvu (lisatud nullide arv on 7) => koefitsient on vĂ€iksem kui ĂŒks: 176/(65 + 48*8 + 7)=0.38. Kui teie ka seda mĂ€rkasite, siis olete tĂ”eliselt tubli. Jah, see rakendus on vĂ€ikeste failide jaoks ÀÀrmiselt ebaefektiivne. Aga mis juhtub suurte failidega? Failide suurus ĂŒletab oluliselt kodeeringutabeli suurust. Siin töötab algoritm tĂ”eliselt hĂ€sti! NĂ€iteks,”,‹ Fausti monoloog arhivaatoreid pakub tegelikku (mitte ideaalset) koefitsienti, mis on 1.46 — peaaegu poole rohkem! Ja jah, eeldati, et fail on inglise keeles.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster