Gegevenskompresje mei it Huffman-algoritme

Ynlieding

Yn dit artikel sil ik prate oer it bekende Huffman-algoritme, lykas har tapassing yn gegevenskompresje.

As gefolch, wy sille skriuwe in ienfâldige archiver. Dit hat al west artikel oer Habré, mar sûnder praktyske ymplemintaasje. It teoretyske materiaal fan 'e hjoeddeistige post is nommen út lessen yn kompjûterwittenskip op skoalle en Robert Laforet's boek "Data Structures and Algorithms in Java". Dus, alles is ûnder de besuniging!

In bytsje refleksje

Yn in gewoane teksttriem is ien karakter kodearre mei 8 bits (ASCII-kodearring) of 16 (Unicode-kodearring). Folgjende sille wy de ASCII-kodearring beskôgje. Nim bygelyks de tekenrige s1 = "SUSIE SAYS IT IS EASYn". Yn totaal binne d'r 22 tekens yn 'e rigel, fansels, ynklusyf spaasjes en it newline-karakter - 'n'. It bestân mei dizze rigel sil 22 * ​​8 = 176 bits weagje. De fraach ûntstiet fuortendaliks: is it rasjoneel om alle 8 bits te brûken om 1 karakter te kodearjen? Wy brûke net alle ASCII-tekens. Sels as se wiene, soe it rationaler wêze om de meast foarkommende letter - S - de koartst mooglike koade te jaan, en foar de seldsumste letter - T (of U, of 'n') - de koade mear autentyk te jaan. Dit is it Huffman-algoritme: jo moatte de bêste kodearingsopsje fine, wêryn it bestân minimaal gewicht sil hawwe. It is hiel normaal dat ferskate karakters ferskillende koadelingten hawwe - dit is de basis fan it algoritme.

Kodearring

Wêrom net jou it karakter 'S' in koade, bygelyks 1 bit lang: 0 of 1. Lit it 1 wêze. Dan is it twadde meast foarkommende karakter - ' ' (spaasje) - wy jouwe 0. Stel jo foar, jo binne begûn mei dekodearje jo berjocht - kodearre tekenrige s1 - en jo sjogge dat de koade begjint mei 1. Dus, wat te dwaan: is it it karakter S, of is it in oar karakter, lykas A? Dêrom ûntstiet in wichtige regel:

Gjin koade moat in foarheaksel wêze fan in oar

Dizze regel is de kaai foar it algoritme. Dêrom begjint it oanmeitsjen fan 'e koade mei in frekwinsjetabel, dy't de frekwinsje (oantal foarfallen) fan elk symboal oanjout:

Gegevenskompresje mei it Huffman-algoritme De tekens mei de measte foarfallen moatte kodearre wurde mei de minste mooglik it oantal bits. Ik sil in foarbyld jaan fan ien fan 'e mooglike koadetabellen:

Gegevenskompresje mei it Huffman-algoritme Dat it kodearre berjocht sil der sa útsjen:

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

Ik skieden de koade fan elk karakter mei in spaasje. Dit sil net echt barre yn in komprimearre triem!
De fraach ûntstiet: hoe kaam dizze rookie mei in koade hoe't jo in koadetabel meitsje kinne? Dit sil hjirûnder besprutsen wurde.

Bouwe in Huffman Tree

Dit is wêr't binêre sykbeammen ta de rêding komme. Sit gjin soargen, jo sille de metoaden foar sykjen, ynfoegje en wiskje hjir net nedich wêze. Hjir is de beamstruktuer yn 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;
    }
    ...
}

Dit is net de folsleine koade, de folsleine koade sil hjirûnder wêze.

Hjir is it algoritme foar it bouwen fan in beam:

  1. Meitsje in Node foarwerp foar elk karakter út it berjocht (line s1). Yn ús gefal sille d'r 9 knopen wêze (Node-objekten). Elke node bestiet út twa gegevensfjilden: symboal en frekwinsje
  2. Meitsje in Tree-objekt (BinaryTree) foar elk fan 'e Node-knooppunten. De knoop wurdt de woartel fan 'e beam.
  3. Foegje dizze beammen yn 'e prioriteitswachtrige. Hoe leger de frekwinsje, hoe heger de prioriteit. Sa wurdt by it útlûken altyd de beam mei de leechste frekwinsje selektearre.

Folgjende moatte jo it folgjende fytslik dwaan:

  1. Nim twa beammen út 'e prioriteitswachtrige en meitsje se bern fan in nij knooppunt (in nij oanmakke knooppunt sûnder letter). De frekwinsje fan it nije knooppunt is lyk oan de som fan 'e frekwinsjes fan 'e twa neikommende beammen.
  2. Foar dit knooppunt meitsje in beam woartele op dit knooppunt. Foegje dizze beam werom yn 'e prioriteitswachtrige. (Om't de beam in nije frekwinsje hat, sil er nei alle gedachten in nij plak yn 'e wachtrige komme)
  3. Trochgean mei stappen 1 en 2 oant ien beam is oerbleaun yn 'e wachtrige - de Huffman beam

Beskôgje dit algoritme op line s1:

Gegevenskompresje mei it Huffman-algoritme

Hjir it symboal "lf" (linefeed) jout in nije rigel, "sp" (romte) is in spaasje.

En wat is neist?

Wy krigen de Huffman-beam. OK. En wat te dwaan mei it? Se sille it net fergees nimme En dan moatte jo alle mooglike paden fan 'e woartel nei de blêden fan 'e beam opspoare. Wy geane akkoard mei in label in râne 0 as it liedt ta de linker bern en 1 as it liedt ta de rjochter. Strikt sjoen, yn dizze notaasjes, is de karakterkoade it paad fan 'e woartel fan' e beam nei it blêd dat itselde karakter befettet.

Gegevenskompresje mei it Huffman-algoritme

Sa, de tabel fan koades die bliken. Tink derom dat as wy dizze tabel beskôgje, kinne wy ​​konkludearje oer it "gewicht" fan elk karakter - dit is de lingte fan syn koade. Dan, yn komprimearre foarm, sil de boarne triem weagje: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bits . Earst woech it 176 bits. Dêrom fermindere wy it mei safolle as 176/65 = 2.7 kear! Mar dit is in utopia. Sa'n ferhâlding is net wierskynlik te krijen. Wêrom? Dit sil wurde besprutsen in bytsje letter.

Dekodearjen

No, miskien is it ienfâldichste ding dat oerbleaun is dekodearjen. Ik tink dat in protte fan jimme hawwe rieden dat it ûnmooglik is om gewoan in komprimearre bestân te meitsjen sûnder oanwizings oer hoe't it is kodearre - wy sille it net kinne ûntsiferje! Ja, ja, it wie dreech foar my om dit te realisearjen, mar ik moat in teksttriem table.txt meitsje mei in kompresjetabel:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Tabelynfier yn 'e foarm 'karakter'"karakterkoade". Wêrom is 01110 sûnder symboal? Yn feite is it mei in symboal, allinich de java-ark dy't ik brûk by it útfieren nei in bestân, it nijeline-karakter - 'n' - wurdt omboud ta in nije rigel (hoe dom it ek klinkt). Dêrom is de lege rigel hjirboppe it karakter foar koade 01110. Foar koade 00 is it karakter in spaasje oan it begjin fan 'e rigel. Ik moat fuortdaliks sizze dat dizze metoade foar it opslaan fan 'e tafel kin beweare dat it de meast irrasjonele is foar ús khan-koëffisjint. Mar it is maklik te begripen en te realisearjen. Ik sil bliid wêze om jo oanbefellings te hearren yn 'e opmerkingen oer optimalisaasje.

Mei dizze tabel is it heul maklik te ûntsiferjen. Lit ús ûnthâlde troch hokker regel wy waarden liede by it meitsjen fan de kodearring:

Gjin koade moat in foarheaksel wêze fan in oar

Dit is wêr't it in fasilitearjende rol spilet. Wy lêze bytsje foar bytsje opienfolgjende, en sa gau as de resultearjende tekenrige d, besteande út de lêzen bits, oerienkomt mei de kodearring dy't oerienkomt mei it karakter karakter, wy witte fuortendaliks dat it karakter karakter (en allinnich it!) waard kodearre. Folgjende skriuwe wy karakter nei de dekodearje tekenrige (de tekenrige mei it dekodearre berjocht), reset de d tekenrige, en lês de kodearre triem fierder.

Ymplemintaasje

It is tiid om myn koade te fernederjen troch in argivator te skriuwen. Litte wy it Compressor neame.

Oernij begjinne. Earst skriuwe wy de Node-klasse:

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

No de beam:

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

Prioriteitswachtrige:

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

De klasse dy't de Huffman-beam makket:

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

De klasse dy't befettet hokker kodearret/dekodearret:

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

In klasse dy't it skriuwen nei in bestân fasilitearret:

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

In klasse dy't it lêzen fan in bestân fasilitearret:

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, en de haadklasse:

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

Jo moatte sels skriuwe de triem mei readme.txt ynstruksjes 🙂

konklúzje

Ik tink dat it alles is wat ik sizze woe. As jo ​​wat te sizzen hawwe oer myn inkompetinsje fan ferbetteringen yn 'e koade, algoritme, yn' t algemien, elke optimalisaasje, fiel jo dan frij om te skriuwen. As ik wat net útlein haw, skriuw dan ek. Ik hear graach fan jo yn 'e kommentaren!

PS

Ja, ja, ik bin hjir noch, om't ik de koëffisjint net fergetten bin. Foar de tekenrige s1 weegt de kodearringtabel 48 bytes - folle mear as it orizjinele bestân, en se ferjitte de ekstra nullen net (it oantal tafoege nullen is 7) => de kompresjeferhâlding sil minder wêze as ien: 176 /(65 + 48*8 + 7) = 0.38. As jo ​​dit ek opfallen, dan gewoan net yn it gesicht binne jo dien. Ja, dizze ymplemintaasje sil ekstreem yneffisjint wêze foar lytse bestannen. Mar wat bart der mei grutte bestannen? De triemgrutte is folle grutter dan de kodearringtabelgrutte. Dit is wêr't it algoritme wurket sa't it moat! Bygelyks, foar Faust syn monolooch de archiver jout in echte (net idealisearre) koëffisjint gelyk oan 1.46 - hast oardel kear! En ja, it bestân soe yn it Ingelsk wêze moatte.

Boarne: www.habr.com

Add a comment