Kompresia údajov pomocou Huffmanovho algoritmu

Vstup

V tomto článku budem hovoriť o slávnom Huffmanovom algoritme, ako aj o jeho aplikácii pri kompresii údajov.

V dôsledku toho napíšeme jednoduchý archivátor. O tom už bola reč článok o Habrém, ale bez praktickej realizácie. Teoretický materiál aktuálneho príspevku je prevzatý zo školských hodín informatiky a knihy Roberta Laforeta „Data Structures and Algorithms in Java“. Takže všetko je prerušené!

Pár myšlienok

V bežnom textovom súbore je jeden znak zakódovaný 8 bitmi (kódovanie ASCII) alebo 16 (kódovanie Unicode). Ďalej zvážime kódovanie ASCII. Napríklad, vezmite riadok s1 = „SUSIE SAYS IT IS EASYn“. V riadku je celkom 22 znakov, samozrejme, vrátane medzier a znaku nového riadku - 'n'. Súbor obsahujúci tento riadok bude vážiť 22*8 = 176 bitov. Okamžite vyvstáva otázka: je rozumné použiť všetkých 8 bitov na zakódovanie 1 znaku? Nepoužívame všetky znaky ASCII. Ak by to aj urobili, bolo by racionálnejšie, keby sa najbežnejšiemu písmenu - S - pridelil najkratší možný kód, a najvzácnejšiemu písmenu - T (alebo U, alebo 'n') - sa pridelil dlhší kód. Z toho pozostáva Huffmanov algoritmus: je potrebné nájsť optimálnu možnosť kódovania, v ktorej bude mať súbor minimálnu váhu. Je celkom normálne, že dĺžky kódov sa budú líšiť pre rôzne symboly - na tom je založený algoritmus.

Kódovanie

Prečo nedať znaku 'S' kód, napríklad dlhý 1 bit: 0 alebo 1. Nech je 1. Potom druhý najbežnejší znak - ' ' (medzera) - dajte 0. Predstavte si, že ste začali dekódovať svoju správu - zakódovaný reťazec s1 - a vidíte, že kód začína 1. Takže, čo urobíte: je to znak S, alebo je to nejaký iný znak, napríklad A? Preto vzniká dôležité pravidlo:

Ani jeden kód by nemal byť predponou iného

Toto pravidlo je kľúčové v algoritme. Preto vytváranie kódu začína frekvenčnou tabuľkou, ktorá udáva frekvenciu (počet výskytov) každého symbolu:

Kompresia údajov pomocou Huffmanovho algoritmu Znaky s najväčším počtom výskytov by mali byť kódované najmenej možné počet bitov. Uvediem príklad jednej z možných kódových tabuliek:

Kompresia údajov pomocou Huffmanovho algoritmu Takže zakódovaná správa bude vyzerať takto:

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

Kód každého znaku som oddelil medzerou. Toto sa nestane v skutočne komprimovanom súbore!
Vynára sa otázka: ako tento mladý muž prišiel s kódom na vytvorenie tabuľky kódov? O tom sa bude diskutovať nižšie.

Stavba Huffmanovho stromu

Tu prichádzajú na pomoc binárne vyhľadávacie stromy. Nebojte sa, tu nebudete potrebovať metódy vyhľadávania, vkladania alebo odstraňovania. Tu je stromová štruktúra v jazyku Java:

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

Toto nie je úplný kód, celý kód bude uvedený nižšie.

Tu je algoritmus na zostavenie stromu:

  1. Vytvorte objekt Node pre každý znak zo správy (riadok s1). V našom prípade to bude 9 uzlov (Node objects). Každý uzol pozostáva z dvoch dátových polí: symbolu a frekvencie
  2. Vytvorte stromový objekt (BinaryTree) pre každý uzol. Uzol sa stáva koreňom stromu.
  3. Vložte tieto stromy do prioritného frontu. Čím nižšia frekvencia, tým vyššia priorita. Pri extrakcii sa teda vždy vyberie dervo s najnižšou frekvenciou.

Ďalej musíte cyklicky robiť nasledovné:

  1. Odstráňte dva stromy z prioritného frontu a urobte z nich deti nového uzla (novo vytvoreného uzla bez písmena). Frekvencia nového uzla sa rovná súčtu frekvencií dvoch podradených stromov.
  2. Pre tento uzol vytvorte strom s koreňom v tomto uzle. Vložte tento strom späť do prioritného frontu. (Keďže strom má novú frekvenciu, s najväčšou pravdepodobnosťou sa objaví na novom mieste vo fronte)
  3. Pokračujte v krokoch 1 a 2, kým vo fronte nezostane iba jeden strom – Huffmanov strom

Zvážte tento algoritmus na riadku s1:

Kompresia údajov pomocou Huffmanovho algoritmu

Symbol „lf“ (linefeed) tu znamená nový riadok, „sp“ (medzera) je medzera.

A čo potom?

Máme Huffmanov strom. OK. A čo s tým robiť? Nevezmú to ani zadarmo. A potom musíte vystopovať všetky možné cesty od koreňa po listy stromu. Dohodnime sa, že označíme hranu 0, ak vedie k ľavému dieťaťu a 1, ak vedie k pravému. Presne povedané, v tomto zápise je kód symbolu cestou od koreňa stromu k listu obsahujúcemu práve tento symbol.

Kompresia údajov pomocou Huffmanovho algoritmu

Takto dopadla tabuľka kódov. Všimnite si, že ak vezmeme do úvahy túto tabuľku, môžeme dospieť k záveru o „váhe“ každého symbolu - toto je dĺžka jeho kódu. Potom bude pôvodný súbor v komprimovanej forme vážiť: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bitov . Najprv vážil 176 bitov. V dôsledku toho sme ho znížili až o 176/65 = 2.7 krát! Ale toto je utópia. Je nepravdepodobné, že by sa dosiahol takýto koeficient. prečo? O tom sa bude diskutovať o niečo neskôr.

Dekódovanie

No, možno najjednoduchšia vec, ktorá zostala, je dekódovanie. Myslím, že mnohí z vás uhádli, že nemôžeme jednoducho vytvoriť komprimovaný súbor bez akéhokoľvek náznaku toho, ako bol zakódovaný – nebudeme ho schopní dekódovať! Áno, áno, bolo pre mňa ťažké si to uvedomiť, ale budem musieť vytvoriť textový súbor table.txt s kompresnou tabuľkou:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Tabuľkový záznam vo forme 'symbol' 'kód znaku'. Prečo je 01110 bez symbolu? V skutočnosti je to so symbolom, ide len o to, že nástroje java, ktoré používam pri výstupe do súboru, znak nového riadku – „n“ – sa skonvertuje na nový riadok (bez ohľadu na to, ako hlúpo to môže znieť). Prázdny riadok v hornej časti je teda znakom pre kód 01110. Pre kód 00 je znakom medzera na začiatku riadku. Hneď poviem, že pre náš Khanov koeficient môže byť tento spôsob ukladania tabuľky najneracionálnejší. Ale je ľahké to pochopiť a implementovať. Budem rád, ak si v komentároch vypočujem vaše odporúčania týkajúce sa optimalizácie.

Táto tabuľka uľahčuje dekódovanie. Pripomeňme si, aké pravidlo sme dodržali pri vytváraní kódovania:

Ani jeden kód by nemal byť predponou iného

Tu zohráva uľahčujúcu úlohu. Čítame postupne bit po bite a akonáhle sa výsledný reťazec d pozostávajúci z načítaných bitov zhoduje s kódovaním zodpovedajúcemu znaku znaku, okamžite vieme, že znak znaku (a iba on!) bol zakódovaný. Ďalej napíšeme znak do dekódovacieho riadku (riadok obsahujúci dekódovanú správu), resetujeme riadok d a potom prečítame zakódovaný súbor.

Реализация

Je čas ponížiť môj kód a napísať archivátor. Nazvime to kompresor.

Začať odznova. Najprv napíšeme triedu Node:

public class Node {
    private int frequence;//частота
    private char letter;//буква
    private Node leftChild;//левый потомок
    private Node rightChild;//правый потомок

   

    public Node(char letter, int frequence) { //собственно, конструктор
        this.letter = letter;
        this.frequence = frequence;
    }

    public Node() {}//перегрузка конструтора для безымянных узлов(см. выше в разделе о построении дерева Хаффмана)
    public void addChild(Node newNode) {//добавить потомка
        if (leftChild == null)//если левый пустой=> правый тоже=> добавляем в левый
            leftChild = newNode;
        else {
            if (leftChild.getFrequence() <= newNode.getFrequence()) //в общем, левым потомком
                rightChild = newNode;//станет тот, у кого меньше частота
            else {
                rightChild = leftChild;
                leftChild = newNode;
            }
        }

        frequence += newNode.getFrequence();//итоговая частота
    }

    public Node getLeftChild() {
        return leftChild;
    }

    public Node getRightChild() {
        return rightChild;
    }

    public int getFrequence() {
        return frequence;
    }

    public char getLetter() {
        return letter;
    }

    public boolean isLeaf() {//проверка на лист
        return leftChild == null && rightChild == null;
    }
}

Teraz strom:

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

Prioritný front:

import java.util.ArrayList;//да-да, очередь будет на базе списка

class PriorityQueue {
    private ArrayList<BinaryTree> data;//список очереди
    private int nElems;//кол-во элементов в очереди

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

    public void insert(BinaryTree newTree) {//вставка
        if (nElems == 0)
            data.add(newTree);
        else {
            for (int i = 0; i < nElems; i++) {
                if (data.get(i).getFrequence() > newTree.getFrequence()) {//если частота вставляемого дерева меньше 
                    data.add(i, newTree);//чем част. текущего, то cдвигаем все деревья на позициях справа на 1 ячейку                   
                    break;//затем ставим новое дерево на позицию текущего
                }
                if (i == nElems - 1) 
                    data.add(newTree);
            }
        }
        nElems++;//увеличиваем кол-во элементов на 1
    }

    public BinaryTree remove() {//удаление из очереди
        BinaryTree tmp = data.get(0);//копируем удаляемый элемент
        data.remove(0);//собственно, удаляем
        nElems--;//уменьшаем кол-во элементов на 1
        return tmp;//возвращаем удаленный элемент(элемент с наименьшей частотой)
    }
}

Trieda, ktorá vytvára Huffmanov strom:

public class HuffmanTree {
    private final byte ENCODING_TABLE_SIZE = 127;//длина кодировочной таблицы
    private String myString;//сообщение
    private BinaryTree huffmanTree;//дерево Хаффмана
    private int[] freqArray;//частотная таблица
    private String[] encodingArray;//кодировочная таблица


    //----------------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();
        //алгоритм описан выше
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            if (freqArray[i] != 0) {//если символ существует в строке
                Node newNode = new Node((char) i, freqArray[i]);//то создать для него Node
                BinaryTree newTree = new BinaryTree(newNode);//а для Node создать BinaryTree
                pq.insert(newTree);//вставить в очередь
            }
        }

        while (true) {
            BinaryTree tree1 = pq.remove();//извлечь из очереди первое дерево.

            try {
                BinaryTree tree2 = pq.remove();//извлечь из очереди второе дерево

                Node newNode = new Node();//создать новый Node
                newNode.addChild(tree1.getRoot());//сделать его потомками два извлеченных дерева
                newNode.addChild(tree2.getRoot());

                pq.insert(new BinaryTree(newNode);
            } catch (IndexOutOfBoundsException e) {//осталось одно дерево в очереди
                return tree1;
            }
        }
    }

    public BinaryTree getTree() {
        return huffmanTree;
    }

    //-------------------encoding array------------------
    void fillEncodingArray(Node node, String codeBefore, String direction) {//заполнить кодировочную таблицу
        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() {//для отладки
        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;
    }
}

Trieda obsahujúca, ktorá kóduje/dekóduje:

public class HuffmanOperator {
    private final byte ENCODING_TABLE_SIZE = 127;//длина таблицы
    private HuffmanTree mainHuffmanTree;//дерево Хаффмана (используется только для сжатия)
    private String myString;//исходное сообщение
    private int[] freqArray;//частотаная таблица
    private String[] encodingArray;//кодировочная таблица
    private double ratio;//коэффициент сжатия 


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

        myString = mainHuffmanTree.getOriginalString();

        encodingArray = mainHuffmanTree.getEncodingArray();

        freqArray = mainHuffmanTree.getFrequenceArray();
    }

    public HuffmanOperator() {}//for extract;

    //---------------------------------------compression-----------------------------------------------------------
    private String getCompressedString() {
        String compressed = "";
        String intermidiate = "";//промежуточная строка(без добавочных нулей)
        //System.out.println("=============================Compression=======================");
        //displayEncodingArray();
        for (int i = 0; i < myString.length(); i++) {
            intermidiate += encodingArray[myString.charAt(i)];
        }
        //Мы не можем писать бит в файл. Поэтому нужно сделать длину сообщения кратной 8=>
        //нужно добавить нули в конец(можно 1, нет разницы)
        byte counter = 0;//количество добавленных в конец нулей (байта в полне хватит: 0<=counter<8<127)
        for (int length = intermidiate.length(), delta = 8 - length % 8; 
        		counter < delta ; counter++) {//delta - количество добавленных нулей
            intermidiate += "0";
        }
        
        //склеить кол-во добавочных нулей в бинарном предаствлении и промежуточную строку 
        compressed = String.format("%8s", Integer.toBinaryString(counter & 0xff)).replace(" ", "0") + intermidiate;
        		
        //идеализированный коэффициент
        setCompressionRatio();
        //System.out.println("===============================================================");
        return compressed;
    }
    
    private void setCompressionRatio() {//посчитать идеализированный коэффициент 
        double sumA = 0, sumB = 0;//A-the original sum
        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() {//final compression
        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;
    }
    //---------------------------------------end of compression----------------------------------------------------------------
    //------------------------------------------------------------extract-----------------------------------------------------
    public String extract(String compressed, String[] newEncodingArray) {
        String decompressed = "";
        String current = "";
        String delta = "";
        encodingArray = newEncodingArray;
        
        //displayEncodingArray();
        //получить кол-во вставленных нулей
        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, т.к. первым байтом у нас идет кол-во вставленных нулей
            current += compressed.charAt(i);
            for (int j = 0; j < ENCODING_TABLE_SIZE; j++) {
                if (current.equals(encodingArray[j])) {//если совпало
                    decompressed += (char)j;//то добавляем элемент
                    current = "";//и обнуляем текущую строку
                }
            }
        }

        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() {//для отладки
        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("========================================================");
    }
    }

Trieda, ktorá uľahčuje zapisovanie do súboru:

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("Неверный путь, или такого файла не существует!");
    	}
    }

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

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

Trieda, ktorá uľahčuje čítanie zo súboru:

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)//если закончился файл
    		throw new EOFException();
    	return (byte)cur;
    }
    
    public String readLine() throws IOException {
    	return fileBufferedReader.readLine();
    }
    
    @Override
    public void close() throws IOException{
    	fileInputStream.close();
    }
}

No a hlavná trieda:

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 {//указываем инструкцию с помощью аргументов командной строки
            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("Неверный формат ввода аргументов ");
            System.out.println("Читайте Readme.txt");
            e.printStackTrace();
        }
    }

	public static void compress(String stringPath) throws IOException {
        List<String> 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("Неверный путь, или такого файла не существует!");
            return;
        } catch (MalformedInputException e) {
        	System.out.println("Текущая кодировка файла не поддерживается");
        	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("Путь к сжатому файлу: " + compressedFile.getAbsolutePath());
        System.out.println("Путь к кодировочной таблице " + table.getAbsolutePath());
        System.out.println("Без таблицы файл будет невозможно извлечь!");
        
        double idealRatio = Math.round(operator.getCompressionRatio() * 100) / (double) 100;//идеализированный коэффициент
        double realRatio = Math.round((double) inputFile.length() 
        		/ ((double) compressedFile.length() + (double) table.length()) * 100) / (double)100;//настоящий коэффициент
        
        System.out.println("Идеализированный коэффициент сжатия равен " + idealRatio);
        System.out.println("Коэффициент сжатия с учетом кодировочной таблицы " + 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("Путь к распакованному файлу " + extractedFile.getAbsolutePath());
    }
}

Súbor readme.txt si budete musieť napísať sami :)

Záver

Myslím, že to je všetko, čo som chcel povedať. Ak máte čo povedať o mojej neschopnosti vo vylepšovaní kódu, algoritmu alebo všeobecne o akejkoľvek optimalizácii, pokojne napíšte. Ak som nič nevysvetlil, napíšte tiež. Budem rád, ak sa mi ozvete v komentároch!

PS

Áno, áno, stále som tu, pretože som nezabudol na koeficient. Pre reťazec s1 kódovacia tabuľka váži 48 bajtov - oveľa viac ako zdrojový súbor a nezabudli sme ani na dodatočné nuly (počet pridaných núl je 7) => kompresný pomer bude menší ako jedna: 176/ (65 + 48*8 + 7) = 0.38. Ak ste si to všimli aj vy, potom nie je skvelá len vaša tvár. Áno, táto implementácia bude extrémne neefektívna pre malé súbory. Čo sa však stane s veľkými súbormi? Veľkosti súborov sú oveľa väčšie ako veľkosť kódovacej tabuľky. Tu funguje algoritmus tak, ako má! Napríklad pre Faustov monológ Archivátor vytvára skutočný (nie idealizovaný) koeficient 1.46 - takmer jeden a pol násobok! A áno, súbor mal byť v angličtine.

Zdroj: hab.com

Pridať komentár