Huffman algoritmi bilan ma'lumotlarni siqish

kirish

Ushbu maqolada men taniqli Huffman algoritmi, shuningdek, ma'lumotlarni siqishda qo'llanilishi haqida gapiraman.

Natijada, biz oddiy arxivchi yozamiz. Bu allaqachon bo'lgan Habré haqidagi maqola, lekin amaliy amalga oshirilmasdan. Joriy postning nazariy materiali maktab informatika darslaridan va Robert Laforetning "Java'da ma'lumotlar tuzilmalari va algoritmlari" kitobidan olingan. Shunday qilib, hamma narsa kesish ostida!

Bir oz aks ettirish

Oddiy matn faylida bitta belgi 8 bit (ASCII kodlash) yoki 16 (Unicode kodlash) bilan kodlangan. Keyinchalik, ASCII kodlashni ko'rib chiqamiz. Masalan, s1 = "SUSIE SAYS IT IS IS EASYn" qatorini oling. Hammasi bo'lib, qatorda 22 ta belgi bor, albatta, bo'shliqlar va yangi qator belgisi - 'n'. Ushbu qatorni o'z ichiga olgan faylning og'irligi 22 * ​​8 = 176 bit bo'ladi. Darhol savol tug'iladi: 8 ta belgini kodlash uchun barcha 1 bitdan foydalanish oqilonami? Biz barcha ASCII belgilaridan foydalanmaymiz. Agar ular shunday bo'lsa ham, eng tez-tez uchraydigan harfni - S - mumkin bo'lgan eng qisqa kodni va eng kam uchraydigan harf uchun - T (yoki U yoki "n") - kodni yanada haqiqiyroq berish oqilona bo'lar edi. Bu Huffman algoritmi: siz eng yaxshi kodlash variantini topishingiz kerak, unda fayl minimal og'irlikda bo'ladi. Turli belgilar turli xil kod uzunliklariga ega bo'lishi juda normaldir - bu algoritmning asosidir.

Kodlash

Nima uchun 'S' belgisiga kod bermaslik kerak, masalan, 1 bit uzunlikdagi: 0 yoki 1. U 1 bo'lsin. Keyin ikkinchi eng ko'p uchraydigan belgi - ' ' (bo'shliq) - biz 0 ni beramiz. Tasavvur qiling, siz xabaringizni dekodlang - kodlangan s1 string - va siz kod 1 dan boshlanganini ko'rasiz. Xo'sh, nima qilish kerak: bu S belgisimi yoki bu A kabi boshqa belgimi? Shunday qilib, muhim qoida paydo bo'ladi:

Hech qanday kod boshqasining prefiksi bo'lmasligi kerak

Bu qoida algoritmning kalitidir. Shuning uchun kodni yaratish chastotalar jadvalidan boshlanadi, bu har bir belgining chastotasini (voqea soni) ko'rsatadi:

Huffman algoritmi bilan ma'lumotlarni siqish Eng ko'p uchraydigan belgilar eng kam belgilar bilan kodlanishi kerak mumkin bitlar soni. Men mumkin bo'lgan kod jadvallaridan biriga misol keltiraman:

Huffman algoritmi bilan ma'lumotlarni siqish Shunday qilib, kodlangan xabar quyidagicha ko'rinadi:

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

Men har bir belgi kodini bo'sh joy bilan ajratdim. Bu, albatta, siqilgan faylda sodir bo'lmaydi!
Savol tug'iladi: bu yangi boshlovchi qanday qilib kod jadvalini yaratish kodini o'ylab topdi? Bu quyida muhokama qilinadi.

Huffman daraxtini qurish

Bu erda ikkilik qidiruv daraxtlari yordamga keladi. Xavotir olmang, bu yerda qidirish, kiritish va oʻchirish usullari kerak boʻlmaydi. Java-da daraxt tuzilishi:

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

Bu to'liq kod emas, to'liq kod quyida bo'ladi.

Mana daraxt qurish algoritmi:

  1. Xabarning har bir belgisi uchun tugun ob'ektini yarating (s1 qator). Bizning holatda, 9 ta tugun bo'ladi (tugun ob'ektlari). Har bir tugun ikkita ma'lumot maydonidan iborat: belgi va chastota
  2. Har bir tugun tugunlari uchun Tree ob'ektini (BinaryTree) yarating. Tugun daraxtning ildiziga aylanadi.
  3. Ushbu daraxtlarni ustuvor navbatga qo'ying. Chastota qanchalik past bo'lsa, ustuvorlik shunchalik yuqori bo'ladi. Shunday qilib, qazib olishda har doim eng past chastotali daraxt tanlanadi.

Keyinchalik, tsiklik ravishda quyidagilarni qilishingiz kerak:

  1. Ustuvor navbatdan ikkita daraxtni oling va ularni yangi tugunning farzandlari (harfsiz yangi yaratilgan tugun) qiling. Yangi tugunning chastotasi ikkita avlod daraxtining chastotalari yig'indisiga teng.
  2. Ushbu tugun uchun ushbu tugunga ildiz otgan daraxt yarating. Ushbu daraxtni yana ustuvor navbatga qo'ying. (Daraxt yangi chastotaga ega bo'lganligi sababli, u navbatdagi yangi joyga kirishi mumkin)
  3. Navbatda bitta daraxt - Huffman daraxti qolmaguncha 1 va 2-bosqichlarni davom ettiring

Ushbu algoritmni s1 qatorida ko'rib chiqing:

Huffman algoritmi bilan ma'lumotlarni siqish

Bu erda "lf" (linefeed) belgisi yangi qatorni bildiradi, "sp" (bo'sh joy) bo'sh joy.

Keyingisi nima?

Biz Huffman daraxtini oldik. KELISHDIKMI. Va u bilan nima qilish kerak? Ular buni bepul olishmaydi, keyin esa daraxtning ildizidan barglarigacha bo'lgan barcha mumkin bo'lgan yo'llarni kuzatishingiz kerak. Biz chekka chap bolaga olib borsa, 0 va o'ng tomonga olib borsa, 1 ni belgilashga rozilik beramiz. To'g'rirog'i, bu belgilarda belgilar kodi daraxtning ildizidan xuddi shu belgini o'z ichiga olgan barggacha bo'lgan yo'ldir.

Huffman algoritmi bilan ma'lumotlarni siqish

Shunday qilib, kodlar jadvali paydo bo'ldi. E'tibor bering, agar biz ushbu jadvalni ko'rib chiqsak, har bir belgining "og'irligi" haqida xulosa qilishimiz mumkin - bu uning kodining uzunligi. Keyin, siqilgan shaklda, manba faylining og'irligi: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bit . Dastlab uning og'irligi 176 bit edi. Shuning uchun biz uni 176/65 = 2.7 barobarga kamaytirdik! Ammo bu utopiya. Bunday nisbatni olish dargumon. Nega? Bu biroz keyinroq muhokama qilinadi.

Dekodlash

Xo'sh, ehtimol qolgan eng oddiy narsa - dekodlash. O'ylaymanki, sizlarning ko'pchiligingiz siqilgan faylni qanday kodlanganligi haqida hech qanday maslahatlarsiz oddiygina yaratish mumkin emasligini taxmin qilgansiz - biz uni dekodlay olmaymiz! Ha, ha, buni tushunish men uchun qiyin edi, lekin siqilish jadvali bilan table.txt matn faylini yaratishim kerak:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Jadvalga "belgi" shaklida "belgi kodi" yozuvi. Nima uchun 01110 belgisiz? Aslida, bu belgi bilan, faqat faylga chiqarishda men foydalanadigan java vositalari, yangi qator belgisi - 'n' - yangi qatorga aylantiriladi (qanchalik ahmoqona tuyulmasin). Shuning uchun, yuqoridagi bo'sh qator 01110 kodining belgisidir. 00 kodi uchun belgi satr boshida bo'sh joydir. Darhol aytishim kerakki, stolni saqlashning bu usuli bizning xon koeffitsientimiz uchun eng mantiqsiz deb da'vo qilishi mumkin. Lekin buni tushunish va amalga oshirish oson. Optimallashtirish bo'yicha sharhlarda sizning tavsiyalaringizni eshitishdan xursand bo'laman.

Ushbu jadval yordamida uni dekodlash juda oson. Keling, kodlashni yaratishda qaysi qoidaga amal qilganimizni eslaylik:

Hech qanday kod boshqasining prefiksi bo'lmasligi kerak

Bu erda u osonlashtiruvchi rol o'ynaydi. Biz ketma-ket bitma-bit o'qiymiz va natijada o'qilgan bitlardan tashkil topgan d qatori belgi belgisiga mos keladigan kodlash bilan mos kelishi bilan biz darhol belgi belgisi (va faqat u!) kodlanganligini bilamiz. Keyinchalik, dekodlash satriga (dekodlangan xabarni o'z ichiga olgan satr) belgi yozamiz, d qatorni qayta tiklaymiz va kodlangan faylni qo'shimcha o'qiymiz.

Реализация

Arxiv yozish orqali kodimni kamsitish vaqti keldi. Keling, uni kompressor deb ataymiz.

Boshlamoq. Avvalo, biz Node sinfini yozamiz:

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

Endi daraxt:

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

Ustuvor navbat:

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

Huffman daraxtini yaratuvchi sinf:

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

Quyidagilarni kodlaydigan/dekodlaydigan sinf:

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

Faylga yozishni osonlashtiradigan sinf:

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

Fayldan o'qishni osonlashtiradigan sinf:

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

Xo'sh, va asosiy sinf:

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

Readme.txt ko'rsatmalari bilan faylni o'zingiz yozishingiz kerak bo'ladi 🙂

xulosa

O'ylaymanki, men aytmoqchi bo'lgan narsa shu edi. Agar sizda kodni, algoritmni, umuman, har qanday optimallashtirishni yaxshilashga qodir emasligim haqida aytadigan narsangiz bo'lsa, yozing. Agar biror narsani tushuntirmagan bo'lsam, iltimos ham yozing. Izohlarda sizdan xabar olishni istardim!

PS

Ha, ha, men hali ham shu erdaman, chunki men koeffitsientni unutmadim. s1 satri uchun kodlash jadvalining og'irligi 48 baytni tashkil qiladi - bu asl faylga qaraganda ancha ko'p va ular qo'shimcha nollarni unutishmadi (qo'shilgan nollar soni 7) => siqish nisbati birdan kam bo'ladi: 176 /(65 + 48*8 + 7) = 0.38. Agar siz ham buni payqagan bo'lsangiz, demak, siz shunchaki yuzingizda emas. Ha, bu dastur kichik fayllar uchun juda samarasiz bo'ladi. Ammo katta fayllar bilan nima sodir bo'ladi? Fayl o'lchamlari kodlash jadvali hajmidan ancha katta. Bu erda algoritm kerak bo'lganda ishlaydi! Masalan, uchun Faust monologi arxivchi 1.46 ga teng haqiqiy (ideallashtirilmagan) koeffitsientni beradi - deyarli bir yarim baravar! Ha, fayl ingliz tilida bo'lishi kerak edi.

Manba: www.habr.com

a Izoh qo'shish