Stiskanje podatkov s Huffmanovim algoritmom

Začetek

V tem članku bom govoril o znanem Huffmanovem algoritmu in njegovi uporabi pri stiskanju podatkov.

Kot rezultat bomo napisali preprost arhivar. To je že bilo članek na Habréju, vendar brez praktične izvedbe. Teoretično gradivo trenutne objave je vzeto iz šolskega pouka računalništva in knjige Roberta Laforeta "Data Structures and Algorithms in Java". Torej, vse je pod rezom!

Malo razmišljanja

V običajni besedilni datoteki je en znak kodiran z 8 biti (kodiranje ASCII) ali 16 (kodiranje Unicode). Nato bomo razmislili o kodiranju ASCII. Na primer, vzemite niz s1 = "SUSIE PRAVI, DA JE ENOSTAVNOn". Skupaj je v vrstici seveda 22 znakov, vključno s presledki in znakom za novo vrstico - 'n'. Datoteka, ki vsebuje to vrstico, bo tehtala 22*8 = 176 bitov. Takoj se pojavi vprašanje: ali je smiselno uporabiti vseh 8 bitov za kodiranje 1 znaka? Ne uporabljamo vseh znakov ASCII. Tudi če bi bili, bi bilo bolj racionalno najpogostejši črki - S - dati najkrajšo možno oznako, za najredkejšo črko - T (ali U, ali 'n') pa dati oznako bolj pristno. To je Huffmanov algoritem: najti morate najboljšo možnost kodiranja, pri kateri bo datoteka minimalne teže. Povsem normalno je, da imajo različni znaki različne dolžine kod - to je osnova algoritma.

Kodiranje

Zakaj ne bi znaku 'S' dali kode, na primer 1 bitne dolžine: 0 ali 1. Naj bo 1. Potem bomo drugemu najpogosteje pojavljajočemu znaku - ' ' (presledek) - dali 0. Predstavljajte si, da ste začeli dekodirajte svoje sporočilo - kodiran niz s1 - in vidite, da se koda začne z 1. Torej, kaj storiti: je to znak S ali kakšen drug znak, na primer A? Zato se pojavi pomembno pravilo:

Nobena koda ne sme biti predpona druge

To pravilo je ključ do algoritma. Zato se ustvarjanje kode začne s frekvenčno tabelo, ki označuje frekvenco (število pojavitev) posameznega simbola:

Stiskanje podatkov s Huffmanovim algoritmom Znaki z največ pojavitvami morajo biti kodirani z najmanj mogoče število bitov. Podal bom primer ene od možnih kodnih tabel:

Stiskanje podatkov s Huffmanovim algoritmom Tako bo kodirano sporočilo videti takole:

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

Kodo vsakega znaka sem ločil s presledkom. V stisnjeni datoteki se to res ne bo zgodilo!
Postavlja se vprašanje: kako je ta novinec prišel do kode, kako ustvariti kodno tabelo? O tem bomo razpravljali v nadaljevanju.

Gradnja Huffmanovega drevesa

Tu na pomoč priskočijo binarna iskalna drevesa. Ne skrbite, tukaj ne boste potrebovali metod iskanja, vstavljanja in brisanja. Tukaj je drevesna struktura v Javi:

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

To ni popolna koda, celotna koda bo spodaj.

Tukaj je algoritem za izdelavo drevesa:

  1. Ustvarite objekt Node za vsak znak iz sporočila (vrstica s1). V našem primeru bo 9 vozlišč (Node objects). Vsako vozlišče je sestavljeno iz dveh podatkovnih polj: simbola in frekvence
  2. Ustvarite predmet Tree (BinaryTree) za vsako od vozlišč Node. Vozlišče postane koren drevesa.
  3. Ta drevesa vstavite v prednostno čakalno vrsto. Nižja kot je frekvenca, višja je prioriteta. Tako se pri ekstrakciji vedno izbere drevo z najnižjo frekvenco.

Nato morate ciklično narediti naslednje:

  1. Pridobite dve drevesi iz prednostne čakalne vrste in ju naredite za otroka novega vozlišča (novo ustvarjeno vozlišče brez črke). Frekvenca novega vozlišča je enaka vsoti frekvenc obeh dreves potomcev.
  2. Za to vozlišče ustvarite drevo s koreninami v tem vozlišču. Vstavite to drevo nazaj v prednostno čakalno vrsto. (Ker ima drevo novo frekvenco, bo najverjetneje prišlo na novo mesto v čakalni vrsti)
  3. Nadaljujte s korakoma 1 in 2, dokler v čakalni vrsti ne ostane eno drevo – Huffmanovo drevo

Razmislite o tem algoritmu v vrstici s1:

Stiskanje podatkov s Huffmanovim algoritmom

Tu simbol "lf" (pomik vrstice) označuje novo vrstico, "sp" (presledek) je presledek.

In kaj je naslednje?

Imamo Huffmanovo drevo. V REDU. In kaj storiti z njim? Ne bodo ga vzeli zastonj In potem morate slediti vsem možnim potem od korenine do listov drevesa. Strinjamo se, da rob označimo z 0, če vodi do levega otroka, in z 1, če vodi do desnega. Strogo gledano je v teh zapisih koda znaka pot od korena drevesa do lista, ki vsebuje ta isti znak.

Stiskanje podatkov s Huffmanovim algoritmom

Tako se je izkazala tabela kod. Upoštevajte, da če upoštevamo to tabelo, lahko sklepamo o "teži" vsakega znaka - to je dolžina njegove kode. Nato bo v stisnjeni obliki izvorna datoteka tehtala: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bitov . Sprva je tehtal 176 bitov. Zato smo ga zmanjšali za kar 176/65 = 2.7-krat! Ampak to je utopija. Takšno razmerje verjetno ne bo doseženo. Zakaj? O tem bomo razpravljali malo kasneje.

Dekodiranje

No, morda je najenostavnejša stvar dekodiranje. Mislim, da ste mnogi uganili, da je nemogoče preprosto ustvariti stisnjeno datoteko brez namigov o tem, kako je bila kodirana - ne bomo je mogli dekodirati! Da, da, težko sem se tega zavedal, vendar moram ustvariti besedilno datoteko table.txt s tabelo za stiskanje:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Vnos tabele v obliki 'znak'"koda znaka". Zakaj je 01110 brez simbola? Pravzaprav je s simbolom, samo orodja Java, ki jih uporabljam pri izpisu v datoteko, znak za novo vrstico - 'n' - se pretvori v novo vrstico (ne glede na to, kako neumno se sliši). Zato je zgornja prazna vrstica znak za kodo 01110. Za kodo 00 je znak presledek na začetku vrstice. Takoj moram reči, da lahko ta metoda shranjevanja tabele trdi, da je najbolj iracionalna za naš khanov koeficient. Vendar je enostavno razumeti in izvajati. Vesel bom vaših priporočil v komentarjih glede optimizacije.

S to tabelo je zelo enostavno dekodirati. Spomnimo se, katero pravilo smo vodili pri ustvarjanju kodiranja:

Nobena koda ne sme biti predpona druge

Tu igra vlogo olajšanja. Zaporedoma beremo bit za bitom in takoj ko se nastali niz d, sestavljen iz prebranih bitov, ujema s kodiranjem, ki ustreza znaku znaka, takoj vemo, da je bil znak (in samo on!) kodiran. Nato zapišemo znak v niz za dekodiranje (niz, ki vsebuje dekodirano sporočilo), ponastavimo niz d in nadaljujemo branje kodirane datoteke.

Реализация

Čas je, da ponižam svojo kodo s pisanjem arhivarja. Imenujmo ga kompresor.

Začeti znova. Najprej napišemo razred 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;
    }
}

Zdaj pa drevo:

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

Prednostna čakalna vrsta:

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;//возвращаем удаленный элемент(элемент с наименьшей частотой)
    }
}

Razred, ki ustvari Huffmanovo drevo:

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

Razred, ki vsebuje, kar kodira/dekodira:

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("========================================================");
    }
    }

Razred, ki olajša pisanje v datoteko:

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

Razred, ki olajša branje iz datoteke:

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, in glavni razred:

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

Datoteko z navodili readme.txt boste morali napisati sami 🙂

Zaključek

Mislim, da je to vse, kar sem hotel povedati. Če imate kaj za povedati o moji nesposobnosti izboljšav kode, algoritma, na splošno kakršne koli optimizacije, potem vas prosimo, da pišete. Če česa nisem pojasnil, prosim tudi napišite. Rad bi slišal vaše komentarje!

PS

Ja, ja, še vedno sem tukaj, ker nisem pozabil na koeficient. Za niz s1 je kodirna tabela težka 48 bajtov - veliko več kot originalna datoteka, pozabili pa niso niti na dodatne ničle (število dodanih ničel je 7) => kompresijsko razmerje bo manjše od ena: 176 /(65 + 48*8 + 7) = 0.38. Če ste tudi vi opazili to, potem samo ne v obraz ste dobro opravljeni. Da, ta izvedba bo izjemno neučinkovita za majhne datoteke. Toda kaj se zgodi z velikimi datotekami? Velikosti datotek so veliko večje od velikosti kodirne tabele. Tukaj algoritem deluje kot mora! Na primer za Faustov monolog arhivar poda realni (ne idealiziran) koeficient, ki je enak 1.46 - skoraj enkrat in pol! In ja, datoteka naj bi bila v angleščini.

Vir: www.habr.com

Dodaj komentar