Datekompressioun mam Huffman Algorithmus

Element

An dësem Artikel wäert ech iwwer de berühmte Huffman Algorithmus schwätzen, wéi och seng Applikatioun an Datekompressioun.

Als Resultat wäerte mir en einfachen Archiver schreiwen. Dëst ass schonn diskutéiert ginn Artikel iwwer Habré, awer ouni praktesch Ëmsetzung. D'theoretesch Material vun der aktueller Post ass aus Schoul Computer Wëssenschaft Lektioune geholl an Robert Laforet Buch "Data Structures an Algorithmen am Java". Also, alles ass ofgeschnidden!

E puer Gedanken

An enger regulärer Textdatei gëtt ee Charakter mat 8 Bits (ASCII Kodéierung) oder 16 (Unicode Kodéierung) kodéiert. Als nächst wäerte mir d'ASCII Kodéierung berücksichtegen. Zum Beispill, huelt d'Linn s1 = "SUSIE SAYS IT IS EASYn". Et sinn am Ganzen 22 Zeeche an der Linn, natierlech, dorënner Plaze an déi nei Zeil Charakter - 'n'. De Fichier mat dëser Linn wäert 22 * ​​8 = 176 Bits weien. D'Fro stellt sech direkt: Ass et rational all 8 Bits ze benotzen fir 1 Charakter ze codéieren? Mir benotzen net all ASCII Zeechen. Och wa se et gemaach hunn, wier et méi rational fir dee meescht übleche Buschtaf - S - dee kuerste méigleche Code ze kréien, a fir de rareste Buschtaf - T (oder U, oder 'n') - e méi laange Code kritt. Dëst ass wat den Huffman Algorithmus besteet aus: et ass néideg fir déi optimal Kodéierungsoptioun ze fannen, an där d'Datei e Minimum Gewiicht huet. Et ass ganz normal datt d'Codelängt fir verschidde Symboler ënnerscheeden - dat ass op wat den Algorithmus baséiert.

Kodéierung

Firwat gitt de Charakter 'S' net e Code, zum Beispill 1 Bit laang: 0 oder 1. Loosst et 1 sinn. Dann deen zweet am meeschte verbreet Charakter - ' ' (Space) - gitt 0. Stellt Iech vir datt Dir ugefaang hutt Äre Message ze decodéieren - der encoded String s1 - an Dir gesitt, datt de Code fänkt mat 1. Also, wat maacht Dir: ass dëst de Charakter S, oder ass et en anere Charakter, zum Beispill A? Dofir entsteet eng wichteg Regel:

Weder Code soll e Präfix vun engem aneren sinn

Dës Regel ass Schlëssel am Algorithmus. Dofir fänkt e Code ze kreéieren mat enger Frequenztabell un, déi d'Frequenz (Zuel vun Optriede) vun all Symbol uginn:

Datekompressioun mam Huffman Algorithmus Charaktere mat de meeschten Optriede sollten am mannsten kodéiert ginn méiglech Zuel vu Stécker. Ech ginn e Beispill vun engem vun de méigleche Code Dëscher:

Datekompressioun mam Huffman Algorithmus Also de kodéierte Message wäert esou ausgesinn:

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

Ech hunn de Code vun all Charakter mat engem Raum getrennt. Dëst wäert net an enger wierklech kompriméierter Datei geschéien!
D'Fro stellt sech: Wéi ass dëse jonke Guy mam Code komm fir en Dësch mat Coden ze kreéieren? Dëst wäert ënnert diskutéiert ginn.

Konstruktioun vun engem Huffman Bam

Dëst ass wou binär Sichbeem zur Rettung kommen. Maacht Iech keng Suergen, Dir braucht hei keng Sich-, Insert- oder Läschmethoden. Hei ass d'Baumstruktur am 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;
    }
    ...
}

Dëst ass net de komplette Code, de komplette Code wäert hei drënner sinn.

Hei ass den Algorithmus fir de Bam ze bauen:

  1. Schafen eng Node Objet fir all Charakter aus dem Message (Linn s1). An eisem Fall ginn et 9 Wirbelen (Node Objete). All Node besteet aus zwee Datefelder: Symbol an Frequenz
  2. Erstellt e Tree-Objet (BinaryTree) fir all Node. De Node gëtt d'Wurzel vum Bam.
  3. Gitt dës Beem an d'Prioritéitschlaang. Wat méi niddereg d'Frequenz ass, wat d'Prioritéit méi héich ass. Also, beim Extrait, gëtt den Dervo mat der niddregster Frequenz ëmmer gewielt.

Als nächst musst Dir déi folgend zyklesch maachen:

  1. Ewechzehuelen zwee Beem aus der Prioritéit Schlaang a maachen hinnen Kanner vun der neier Node (den nei geschaf Node ouni Bréif). D'Frequenz vum neie Node ass gläich wéi d'Zomm vun den Frequenzen vun den zwee Nokommen Beem.
  2. Fir dësen Node erstellt e Bam mat der Wuerzel op dësem Node. Setzt dëse Bam zréck an d'Prioritéitschlaang. (Well de Bam eng nei Frequenz huet, erschéngt en héchstwahrscheinlech op enger neier Plaz an der Schlaang)
  3. Fuert Schrëtt 1 an 2 weider bis et nëmmen ee Bam an der Schlaang bleift - den Huffman Bam

Betruecht dësen Algorithmus op der Linn s1:

Datekompressioun mam Huffman Algorithmus

Hei heescht d'Symbol "lf" (Linefeed) eng nei Linn, "sp" (Raum) ass e Raum.

A wat ass dat nächst?

Mir hunn en Huffman Bam. OK. A wat maache mat deem? Si huelen et net mol gratis.An dann musst Dir all méiglech Weeër vun der Wuerzel op d'Blieder vum Bam verfollegen. Loosst eis averstane sinn e Rand 0 ze bezeechnen wann et zum lénksen Kand féiert an 1 wann et zum richtege féiert. Streng geschwat, an dëser Notatioun, ass de Code vun engem Symbol de Wee vun der Wuerzel vum Bam op d'Blat mat dësem Symbol.

Datekompressioun mam Huffman Algorithmus

Dëst ass wéi den Dësch vun de Coden erausgeet. Bedenkt datt wa mir dës Tabell betruechten, kënne mir iwwer d'"Gewiicht" vun all Symbol ofschléissen - dat ass d'Längt vu sengem Code. Dann, a kompriméierter Form, wäert d'Original Datei weien: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 Bits . Am Ufank war et 176 Bits. Dofir hu mir et ëm sou vill wéi 176/65 = 2.7 Mol reduzéiert! Awer dëst ass eng Utopie. Esou e Koeffizient ass onwahrscheinlech ze kréien. Firwat? Dëst wäert e bësse méi spéit diskutéiert ginn.

Decodéieren

Gutt, vläicht déi einfachst Saach lénks ass Decodéierung. Ech denken, datt vill vun iech gegleeft hunn datt mir net einfach eng kompriméiert Datei kënnen erstellen ouni en Hiweis wéi se kodéiert gouf - mir kënnen et net decodéieren! Jo, jo, et war schwéier fir mech dëst ze realiséieren, awer ech muss en Textdatei table.txt mat enger Kompressiounstabell erstellen:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Tabellentrée an der Form 'Symbol' 'Zeechencode'. Firwat ass 01110 ouni Symbol? Tatsächlech ass et mat engem Symbol, et ass just datt d'Java Tools déi ech benotze wann ech op eng Datei erausginn, den Newline Charakter - 'n' - gëtt an eng Newline ëmgewandelt (egal wéi domm et kléngt). Dofir ass déi eidel Linn uewen de Charakter fir Code 01110. Fir Code 00 ass de Charakter e Raum um Ufank vun der Zeil. Ech wäert direkt soen datt fir eise Khan Koeffizient dës Method fir en Dësch ze späicheren kann behaapten déi irrationalst ze sinn. Awer et ass einfach ze verstoen an ëmzesetzen. Ech wäert frou Är Empfehlungen an de Kommentaren iwwer Optimisatioun ze héieren.

Dësen Dësch ze hunn mécht et ganz einfach ze decodéieren. Loosst eis erënneren wéi eng Regel mir gefollegt hunn beim Erstellen vun der Kodéierung:

Weder Code soll e Präfix vun engem aneren sinn

Dëst ass wou et eng erliichtert Roll spillt. Mir liesen sequenziell Stéck fir Bit a soubal déi resultéierend String d, déi aus de Liesbits besteet, mat der Kodéierung entsprécht dem Charakter Charakter entsprécht, wësse mir direkt datt de Charakter Charakter (an nëmmen et!) Kodéiert gouf. Als nächst schreiwen mir Charakter an d'Dekodéierungslinn (d'Linn déi de dekodéierte Message enthält), setzt d'Linn d zréck, a liest dann déi kodéiert Datei.

Ëmsetzung

Et ass Zäit mäi Code ze humiliéieren an en Archiver ze schreiwen. Loosst eis et Kompressor nennen.

Nei ufänken. Als éischt schreiwen mir d'Node Klass:

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

Elo de Bam:

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

Prioritéit Queue:

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

D'Klass déi den Huffman Bam erstellt:

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

Klass déi enthält déi codéiert / decodéiert:

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

Eng Klass déi et méi einfach mécht an eng Datei ze schreiwen:

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

Eng Klass déi et méi einfach mécht aus enger Datei ze liesen:

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

Gutt, an d'Haaptklass:

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

Dir musst d'readme.txt Datei selwer schreiwen :)

Konklusioun

Ech mengen, dat ass alles wat ech wollt soen. Wann Dir eppes iwwer meng Inkompetenz ze soen hutt fir de Code, Algorithmus oder all Optimisatioun am Allgemengen ze verbesseren, da schreiwt Iech gratis. Wann ech näischt erklärt hunn, schreiw och. Ech géif gär vun Iech an de Kommentaren héieren!

PS

Jo, jo, ech sinn nach ëmmer hei, well ech de Koeffizient net vergiess hunn. Fir String s1 weegt d'Kodéierungstabelle 48 Bytes - vill méi grouss wéi d'Quelldatei, a mir hunn déi zousätzlech Nullen net vergiess (d'Zuel vun den addéierten Nullen ass 7) => de Kompressiounsverhältnis wäert manner wéi ee sinn: 176/ (65 + 48 * 8 + 7) = 0.38. Wann Dir dat och gemierkt hutt, dann ass et net nëmmen Äert Gesiicht dat super ass. Jo, dës Implementatioun wäert extrem ineffizient sinn fir kleng Dateien. Awer wat geschitt mat grousse Dateien? D'Dateigréissten si vill méi grouss wéi d'Gréisst vun der Kodéierungstabell. Dëst ass wou den Algorithmus funktionnéiert wéi et soll! Zum Beispill, fir Fausts Monolog Den Archiver produzéiert e reellen (net idealiséierte) Koeffizient vun 1.46 - bal annerhallefmol! An jo, den Dossier sollt op Englesch sinn.

Source: will.com

Setzt e Commentaire