Kompresja danych przy użyciu algorytmu Huffmana

Wejście

W tym artykule omówię słynny algorytm Huffmana, a także jego zastosowanie w kompresji danych.

W rezultacie napiszemy prosty archiwizator. Zostało to już omówione artykuł o Habré, ale bez praktycznego wdrożenia. Materiał teoretyczny niniejszego wpisu został zaczerpnięty ze szkolnych lekcji informatyki oraz z książki Roberta Laforeta „Struktury danych i algorytmy w Javie”. Więc wszystko wycięte!

Trochę refleksji

W zwykłym pliku tekstowym jeden znak jest kodowany za pomocą 8 bitów (kodowanie ASCII) lub 16 (kodowanie Unicode). Następnie rozważymy kodowanie ASCII. Weźmy na przykład wiersz s1 = „SUSIE MÓWI, ŻE TO ŁATWE”. W linii znajdują się oczywiście łącznie 22 znaki, łącznie ze spacjami i znakiem nowej linii - „n”. Plik zawierający tę linię będzie ważył 22*8 = 176 bitów. Od razu pojawia się pytanie: czy racjonalne jest użycie wszystkich 8 bitów do zakodowania 1 znaku? Nie używamy wszystkich znaków ASCII. Nawet gdyby tak było, bardziej racjonalne byłoby, gdyby najczęstsza litera – S – otrzymała możliwie najkrótszy kod, a najrzadsza litera – T (lub U, lub „n”) – dłuższy kod. Na tym polega algorytm Huffmana: należy znaleźć optymalną opcję kodowania, w której plik będzie miał minimalną wagę. To całkiem normalne, że długości kodów będą się różnić dla różnych symboli - na tym opiera się algorytm.

Kodowanie

Dlaczego nie nadać znakowi „S” kodu, na przykład o długości 1 bitu: 0 lub 1. Niech to będzie 1. Następnie drugi najczęściej spotykany znak - „” (spacja) - podaj 0. Wyobraź sobie, że zacząłeś dekodować swoją wiadomość - zakodowany ciąg s1 - i widzisz, że kod zaczyna się od 1. Co więc robisz: czy jest to znak S, czy może jest to jakiś inny znak, na przykład A? Dlatego powstaje ważna zasada:

Żaden kod nie powinien być przedrostkiem innego

Ta zasada jest kluczowa w algorytmie. Dlatego tworzenie kodu rozpoczyna się od tabeli częstotliwości, która wskazuje częstotliwość (liczbę wystąpień) każdego symbolu:

Kompresja danych przy użyciu algorytmu Huffmana Znaki z największą liczbą wystąpień powinny być kodowane najmniej możliwy liczba bitów. Podam przykład jednej z możliwych tabel kodów:

Kompresja danych przy użyciu algorytmu Huffmana Zatem zakodowana wiadomość będzie wyglądać następująco:

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

Kod każdego znaku oddzieliłem spacją. Nie stanie się to w naprawdę skompresowanym pliku!
Powstaje pytanie: jak ten młody chłopak wpadł na kod do stworzenia tabeli kodów? Zostanie to omówione poniżej.

Konstruowanie drzewa Huffmana

Tutaj na ratunek przychodzą drzewa wyszukiwania binarnego. Nie martw się, nie będziesz potrzebować tutaj metod wyszukiwania, wstawiania ani usuwania. Oto struktura drzewa w Javie:

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 nie jest cały kod, pełny kod będzie poniżej.

Oto algorytm konstruowania drzewa:

  1. Utwórz obiekt Node dla każdego znaku wiadomości (linia s1). W naszym przypadku będzie 9 węzłów (obiektów Node). Każdy węzeł składa się z dwóch pól danych: symbolu i częstotliwości
  2. Utwórz obiekt Tree (BinaryTree) dla każdego węzła. Węzeł staje się korzeniem drzewa.
  3. Wstaw te drzewa do kolejki priorytetowej. Im niższa częstotliwość, tym wyższy priorytet. Zatem podczas ekstrakcji zawsze wybierany jest dervo o najniższej częstotliwości.

Następnie musisz cyklicznie wykonać następujące czynności:

  1. Usuń dwa drzewa z kolejki priorytetowej i uczyń je dziećmi nowego węzła (nowo utworzony węzeł bez litery). Częstotliwość nowego węzła jest równa sumie częstotliwości dwóch drzew potomnych.
  2. Dla tego węzła utwórz drzewo z korzeniem w tym węźle. Wstaw to drzewo z powrotem do kolejki priorytetowej. (Ponieważ drzewo ma nową częstotliwość, najprawdopodobniej pojawi się w nowym miejscu w kolejce)
  3. Kontynuuj kroki 1 i 2, aż w kolejce pozostanie tylko jedno drzewo - drzewo Huffmana

Rozważmy ten algorytm na linii s1:

Kompresja danych przy użyciu algorytmu Huffmana

Tutaj symbol „lf” (przesunięcie wiersza) oznacza nową linię, „sp” (spacja) to spacja.

I co wtedy?

Mamy drzewo Huffmana. OK. I co z tym zrobić? Za darmo nawet tego nie wezmą, a potem trzeba prześledzić wszystkie możliwe ścieżki od korzenia do liści drzewa. Zgódźmy się oznaczać krawędź 0, jeśli prowadzi do lewego dziecka i 1, jeśli prowadzi do prawego. Ściśle rzecz ujmując, w tym zapisie kodem symbolu jest droga od korzenia drzewa do liścia zawierającego ten właśnie symbol.

Kompresja danych przy użyciu algorytmu Huffmana

Tak powstała tabela kodów. Zauważ, że jeśli weźmiemy pod uwagę tę tabelę, możemy wyciągnąć wnioski dotyczące „wagi” każdego symbolu - jest to długość jego kodu. Następnie w formie skompresowanej oryginalny plik będzie ważył: 2 * 3 + 2*4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bitów . Początkowo ważył 176 bitów. W efekcie zmniejszyliśmy go aż o 176/65 = 2.7 razy! Ale to jest utopia. Uzyskanie takiego współczynnika jest mało prawdopodobne. Dlaczego? Zostanie to omówione nieco później.

Rozszyfrowanie

Cóż, być może najprostszą rzeczą, jaką pozostało, jest dekodowanie. Myślę, że wielu z Was domyśliło się, że nie możemy po prostu utworzyć skompresowanego pliku bez podpowiedzi, w jaki sposób został on zakodowany - nie będziemy w stanie go odszyfrować! Tak, tak, ciężko było mi to sobie uświadomić, ale będę musiał stworzyć plik tekstowy table.txt z tabelą kompresji:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Wpis do tabeli w postaci „symbol” „kod znaku”. Dlaczego numer 01110 nie ma symbolu? W rzeczywistości jest to symbol, po prostu narzędzia Java, których używam podczas wyprowadzania do pliku, znak nowej linii - „n” - jest konwertowany na znak nowej linii (nieważne, jak głupio to może brzmieć). Dlatego pusta linia na górze jest znakiem dla kodu 01110. Dla kodu 00 znakiem jest spacja na początku linii. Od razu powiem, że dla naszego współczynnika Khana tę metodę przechowywania tabeli można uznać za najbardziej irracjonalną. Ale jest to łatwe do zrozumienia i wdrożenia. Chętnie wysłucham Twoich rekomendacji w komentarzach dotyczących optymalizacji.

Posiadanie tej tabeli bardzo ułatwia dekodowanie. Przypomnijmy, jaką zasadą kierowaliśmy się tworząc kodowanie:

Żaden kod nie powinien być przedrostkiem innego

Tutaj pełni rolę ułatwiającą. Czytamy sekwencyjnie bit po bicie i gdy tylko powstały ciąg d, składający się z przeczytanych bitów, pasuje do kodowania odpowiadającego znakowi znaku, od razu wiemy, że znak znaku (i tylko on!) został zakodowany. Następnie zapisujemy znak w linii dekodującej (linii zawierającej zdekodowaną wiadomość), resetujemy linię d, a następnie czytamy zakodowany plik.

realizacja

Czas upokorzyć swój kod i napisać archiwizator. Nazwijmy to kompresorem.

Zacząć od nowa. Na początek piszemy klasę 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 drzewo:

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

Kolejka priorytetowa:

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

Klasa tworząca drzewo Huffmana:

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

Klasa zawierająca kodowanie/dekodowanie:

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

Klasa ułatwiająca zapis do pliku:

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

Klasa ułatwiająca odczyt z pliku:

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

Cóż, i klasa główna:

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

Będziesz musiał sam napisać plik readme.txt :)

wniosek

To chyba wszystko, co chciałem powiedzieć. Jeśli masz coś do powiedzenia na temat mojej niekompetencji w ulepszaniu kodu, algorytmu lub ogólnie jakiejkolwiek optymalizacji, to śmiało pisz. Jeżeli czegoś nie wyjaśniłem to też proszę napisz. Bardzo chciałbym usłyszeć od Ciebie w komentarzach!

PS

Tak, tak, nadal tu jestem, bo nie zapomniałem o współczynniku. Dla ciągu s1 tablica kodująca waży 48 bajtów - znacznie więcej niż plik źródłowy, nie zapomnieliśmy też o dodatkowych zerach (ilość dodanych zer wynosi 7) => stopień kompresji będzie mniejszy niż jeden: 176/ (65 + 48*8 + 7) = 0.38. Jeśli też to zauważyłaś, oznacza to, że nie tylko Twoja twarz jest świetna. Tak, ta implementacja będzie wyjątkowo nieefektywna w przypadku małych plików. Ale co dzieje się z dużymi plikami? Rozmiary plików są znacznie większe niż rozmiar tabeli kodowania. Tutaj algorytm działa jak należy! Na przykład dla Monolog Fausta Archiwizator generuje rzeczywisty (nie wyidealizowany) współczynnik 1.46 - prawie półtora raza! I tak, plik miał być w języku angielskim.

Źródło: www.habr.com

Dodaj komentarz