Kompresimi i të dhënave duke përdorur algoritmin Huffman

Hyrje

Në këtë artikull do të flas për algoritmin e famshëm Huffman, si dhe aplikimin e tij në kompresimin e të dhënave.

Si rezultat, ne do të shkruajmë një arkivues të thjeshtë. Kjo tashmë është diskutuar artikull në Habré, por pa zbatim praktik. Materiali teorik i postimit aktual është marrë nga mësimet e shkencave kompjuterike të shkollës dhe nga libri i Robert Laforet "Strukturat e të dhënave dhe algoritmet në Java". Pra, gjithçka është e prerë!

Disa mendime

Në një skedar teksti të rregullt, një karakter është i koduar me 8 bit (kodimi ASCII) ose 16 (kodimi Unicode). Më pas do të shqyrtojmë kodimin ASCII. Për shembull, merrni rreshtin s1 = "SUSIE THOTË ËSHTË E LEHTË". Ka gjithsej 22 karaktere në rresht, natyrisht, duke përfshirë hapësirat dhe karakterin e linjës së re - 'n'. Skedari që përmban këtë rresht do të peshojë 22*8 = 176 bit. Menjëherë lind pyetja: a është racionale të përdoren të 8 bitet për të koduar 1 karakter? Ne nuk i përdorim të gjithë karakteret ASCII. Edhe nëse do ta bënin, do të ishte më racionale që shkronjës më të zakonshme - S - t'i jepej kodi më i shkurtër i mundshëm, dhe shkronjës më të rrallë - T (ose U, ose 'n') - t'i jepej një kod më i gjatë. Kjo është ajo që përbëhet nga algoritmi Huffman: është e nevojshme të gjendet opsioni optimal i kodimit në të cilin skedari do të ketë peshën minimale. Është mjaft normale që gjatësia e kodit të ndryshojë për simbole të ndryshme - kjo është ajo në të cilën bazohet algoritmi.

Kodimi

Pse të mos i jepni karakterit 'S' një kod, për shembull, 1 bit i gjatë: 0 ose 1. Le të jetë 1. Pastaj karakteri i dytë më i zakonshëm - ' ' (hapësirë) - jepni 0. Imagjinoni se keni filluar të deshifroni mesazhin tuaj - vargu i koduar s1 - dhe shihni se kodi fillon me 1. Pra, çfarë bëni: a është ky karakteri S, apo është ndonjë karakter tjetër, për shembull A? Prandaj, lind një rregull i rëndësishëm:

Asnjë kod nuk duhet të jetë prefiks i një tjetri

Ky rregull është kyç në algoritëm. Prandaj, krijimi i një kodi fillon me një tabelë frekuence, e cila tregon frekuencën (numrin e dukurive) të secilit simbol:

Kompresimi i të dhënave duke përdorur algoritmin Huffman Personazhet me më shumë dukuri duhet të kodohen më së paku e mundshme numri i biteve. Unë do të jap një shembull të një prej tabelave të mundshme të kodit:

Kompresimi i të dhënave duke përdorur algoritmin Huffman Pra, mesazhi i koduar do të duket si ky:

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

E ndava kodin e çdo karakteri me një hapësirë. Kjo nuk do të ndodhë në një skedar vërtet të ngjeshur!
Lind pyetja: si i ka dalë ky djalosh kodin për të krijuar një tabelë kodesh? Kjo do të diskutohet më poshtë.

Ndërtimi i një peme Huffman

Këtu vijnë në shpëtim pemët e kërkimit binar. Mos u shqetësoni, nuk do të keni nevojë për metodat e kërkimit, futjes ose fshirjes këtu. Këtu është struktura e pemës në 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;
    }
    ...
}

Ky nuk është kodi i plotë, kodi i plotë do të jetë më poshtë.

Këtu është algoritmi për ndërtimin e pemës:

  1. Krijoni një objekt Node për çdo karakter nga mesazhi (rreshti s1). Në rastin tonë do të ketë 9 nyje (objekte Node). Çdo nyje përbëhet nga dy fusha të dhënash: simboli dhe frekuenca
  2. Krijoni një objekt Tree (BinaryTree) për çdo Nyje. Nyja bëhet rrënja e pemës.
  3. Futni këto pemë në radhën prioritare. Sa më e ulët të jetë frekuenca, aq më i lartë është prioriteti. Kështu, gjatë nxjerrjes, zgjidhet gjithmonë dervo me frekuencën më të ulët.

Më pas duhet të bëni sa më poshtë në mënyrë ciklike:

  1. Hiqni dy pemë nga radha prioritare dhe bëni ato fëmijë të nyjës së re (nyja e krijuar rishtazi pa shkronjën). Frekuenca e nyjës së re është e barabartë me shumën e frekuencave të dy pemëve pasardhëse.
  2. Për këtë nyje, krijoni një pemë me rrënjë në këtë nyje. Futeni këtë pemë përsëri në radhën e përparësisë. (Meqenëse pema ka një frekuencë të re, ka shumë të ngjarë të shfaqet në një vend të ri në radhë)
  3. Vazhdoni hapat 1 dhe 2 derisa të mbetet vetëm një pemë në radhë - pema Huffman

Konsideroni këtë algoritëm në linjën s1:

Kompresimi i të dhënave duke përdorur algoritmin Huffman

Këtu simboli "lf" (linefeed) do të thotë një rresht i ri, "sp" (hapësirë) është një hapësirë.

Ç'pritet më tej?

Ne morëm një pemë Huffman. NE RREGULL. Dhe çfarë të bëni me të? Ata as nuk do ta marrin falas. Dhe pastaj, ju duhet të gjurmoni të gjitha shtigjet e mundshme nga rrënja deri te gjethet e pemës. Le të biem dakord të shënojmë një skaj 0 nëse të çon te fëmija i majtë dhe 1 nëse të çon në të djathtën. Në mënyrë të rreptë, në këtë shënim, kodi i një simboli është shtegu nga rrënja e pemës deri te fleta që përmban pikërisht këtë simbol.

Kompresimi i të dhënave duke përdorur algoritmin Huffman

Kështu doli tabela e kodeve. Vini re se nëse marrim parasysh këtë tabelë, mund të konkludojmë për "peshën" e secilit simbol - kjo është gjatësia e kodit të tij. Pastaj, në formë të ngjeshur, skedari origjinal do të peshojë: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bit . Në fillim peshonte 176 bit. Rrjedhimisht, e reduktuam me sa 176/65 = 2.7 herë! Por kjo është një utopi. Një koeficient i tillë nuk ka gjasa të merret. Pse? Kjo do të diskutohet pak më vonë.

Dekodimi

Epo, ndoshta gjëja më e thjeshtë që ka mbetur është deshifrimi. Unë mendoj se shumë prej jush kanë menduar se ne nuk mund të krijojmë thjesht një skedar të ngjeshur pa asnjë aluzion se si është koduar - nuk do të jemi në gjendje ta deshifrojmë atë! Po, po, ishte e vështirë për mua ta kuptoja këtë, por do të më duhet të krijoj një skedar teksti table.txt me një tabelë kompresimi:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Hyrja e tabelës në formën 'simbol' 'kodi i karakterit'. Pse 01110 është pa simbol? Në fakt, është me një simbol, thjesht mjetet java që përdor kur nxjerr në një skedar, karakteri i linjës së re - 'n' - konvertohet në një linjë të re (pa marrë parasysh sa budalla mund të duket). Prandaj, rreshti bosh në krye është karakteri për kodin 01110. Për kodin 00, karakteri është një hapësirë ​​në fillim të rreshtit. Unë do të them menjëherë se për koeficientin tonë Khan, kjo metodë e ruajtjes së një tabele mund të pretendojë të jetë më e paarsyeshme. Por është e lehtë për t'u kuptuar dhe zbatuar. Do të jem i lumtur të dëgjoj rekomandimet tuaja në komentet në lidhje me optimizimin.

Pasja e kësaj tabele e bën shumë të lehtë deshifrimin. Le të kujtojmë se çfarë rregulli kemi ndjekur gjatë krijimit të kodimit:

Asnjë kod nuk duhet të jetë prefiks i një tjetri

Këtu ai luan një rol lehtësues. Ne lexojmë në mënyrë sekuenciale pak nga pak dhe, sapo vargu që rezulton d, i përbërë nga pjesët e lexuara, përputhet me kodimin që korrespondon me karakterin e karakterit, ne e dimë menjëherë se karakteri i karakterit (dhe vetëm ai!) është koduar. Më pas, ne shkruajmë karakter në linjën e dekodimit (rreshti që përmban mesazhin e dekoduar), rivendosim rreshtin d dhe më pas lexojmë skedarin e koduar.

Zbatimi

Është koha për të poshtëruar kodin tim dhe për të shkruar një arkivues. Le ta quajmë Kompresor.

Filloje nga e para. Para së gjithash, ne shkruajmë klasën 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;
    }
}

Tani pema:

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

Radha me përparësi:

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 që krijon pemën Huffman:

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 që përmban e cila kodon/dekodon:

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

Një klasë që e bën më të lehtë shkrimin në një skedar:

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

Një klasë që e bën më të lehtë leximin nga një skedar:

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

Epo, dhe klasa kryesore:

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

Do të duhet ta shkruani vetë skedarin readme.txt :)

Përfundim

Mendoj se kjo është gjithçka që doja të them. Nëse keni ndonjë gjë për të thënë për paaftësinë time në përmirësimin e kodit, algoritmit ose ndonjë optimizimi në përgjithësi, atëherë mos ngurroni të shkruani. Nëse nuk kam shpjeguar asgjë, ju lutemi shkruani gjithashtu. Do të doja të dëgjoja nga ju në komente!

PS

Po, po, jam akoma këtu, sepse nuk e harrova koeficientin. Për vargun s1, tabela e kodimit peshon 48 bajt - shumë më e madhe se skedari burimor, dhe nuk harruam zerat shtesë (numri i zerave të shtuara është 7) => raporti i kompresimit do të jetë më i vogël se një: 176/ (65 + 48*8 + 7) = 0.38. Nëse edhe ju e keni vënë re këtë, atëherë nuk është vetëm fytyra juaj ajo që është e mrekullueshme. Po, ky zbatim do të jetë jashtëzakonisht joefikas për skedarët e vegjël. Por çfarë ndodh me skedarët e mëdhenj? Madhësitë e skedarëve janë shumë më të mëdha se madhësia e tabelës së kodimit. Këtu algoritmi funksionon ashtu siç duhet! Për shembull, për monologu i Faustit Arkivi prodhon një koeficient real (jo të idealizuar) 1.46 - pothuajse një herë e gjysmë! Dhe po, dosja duhej të ishte në anglisht.

Burimi: www.habr.com

Shto një koment