Хаффман алгоритмаар өгөгдлийг шахах

нэвтрэх

Энэ нийтлэлд би сайн мэддэг Хаффманы алгоритм, түүнчлэн өгөгдлийг шахах ажилд ашиглах талаар ярих болно.

Үүний үр дүнд бид энгийн архивлагч бичих болно. Энэ нь аль хэдийн болсон Хабрегийн тухай нийтлэл, гэхдээ практик хэрэгжилтгүйгээр. Энэ нийтлэлийн онолын материалыг сургуулийн компьютерийн шинжлэх ухааны хичээлүүд болон Роберт Лафоретын "Java дахь өгөгдлийн бүтэц ба алгоритмууд" номноос авсан болно. Тиймээс, бүх зүйл тасалдсан байна!

Бага зэрэг тусгал

Энгийн текст файлд нэг тэмдэгтийг 8 бит (ASCII кодчилол) эсвэл 16 (Юникод кодчилол)-оор кодлодог. Дараа нь бид ASCII кодчиллыг авч үзэх болно. Жишээ нь, s1 = "SUSIE SAYS IT IS EASYn" мөрийг ав. Мэдээжийн хэрэг мөрөнд нийтдээ 22 тэмдэгт байна, үүнд хоосон зай, шинэ мөрийн тэмдэгт - 'n' орно. Энэ мөрийг агуулсан файл нь 22*8 = 176 бит жинтэй болно. Тэр даруй асуулт гарч ирнэ: 8 тэмдэгтийг кодлохын тулд бүх 1 битийг ашиглах нь оновчтой юу? Бид бүх ASCII тэмдэгтүүдийг ашигладаггүй. Байсан байсан ч гэсэн хамгийн олон давтамжтай үсэг болох S - хамгийн богино код, ховор үсэг - T (эсвэл U, эсвэл 'n') - кодыг илүү жинхэнэ болгох нь илүү оновчтой байх болно. Энэ бол Хаффманы алгоритм юм: та хамгийн бага жинтэй файл байх хамгийн сайн кодчилолын сонголтыг олох хэрэгтэй. Өөр өөр тэмдэгтүүд өөр өөр кодын урттай байх нь хэвийн үзэгдэл юм - энэ бол алгоритмын үндэс юм.

Кодлох

Яагаад 'S' тэмдэгтэнд код өгч болохгүй гэж, жишээ нь 1 бит урт: 0 эсвэл 1. Энэ нь 1 байх болтугай. Дараа нь хамгийн их тохиолддог хоёр дахь тэмдэгт - ' ' (зай) - бид 0-ийг өгөх болно. Та төсөөлөөд үз дээ. мессежээ тайл - кодлогдсон мөр s1 - мөн та код 1-ээр эхэлж байгааг харж байна. Тэгэхээр, юу хийх вэ: энэ нь S тэмдэгт үү, эсвэл А гэх мэт өөр тэмдэгт үү? Тиймээс нэг чухал дүрэм гарч ирнэ:

Ямар ч код өөр код байх ёсгүй

Энэ дүрэм нь алгоритмын түлхүүр юм. Тиймээс кодыг үүсгэх нь давтамжийн хүснэгтээс эхэлдэг бөгөөд энэ нь тэмдэг бүрийн давтамжийг (тохиолдлын тоо) зааж өгдөг.

Хаффман алгоритмаар өгөгдлийг шахах Хамгийн их тохиолдсон тэмдэгтүүдийг хамгийн цөөн тоогоор кодлох ёстой боломжтой битийн тоо. Би боломжит кодын хүснэгтүүдийн нэг жишээг өгөх болно.

Хаффман алгоритмаар өгөгдлийг шахах Тиймээс кодлогдсон мессеж дараах байдлаар харагдах болно.

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

Би тэмдэгт бүрийн кодыг зайгаар тусгаарласан. Шахсан файлд энэ нь үнэхээр тохиолдохгүй!
Асуулт гарч ирнэ: энэ шинэ залуу хэрхэн кодын хүснэгтийг хэрхэн үүсгэх кодтой болсон бэ? Үүнийг доор хэлэлцэх болно.

Хаффман модыг барих

Эндээс хоёртын хайлтын мод аврах ажилд ирдэг. Санаа зоволтгүй, энд хайх, оруулах, устгах аргууд хэрэггүй болно. 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;
    }
    ...
}

Энэ нь бүрэн код биш, бүрэн код нь доор байх болно.

Мод барих алгоритмыг энд харуулав.

  1. Мессежийн тэмдэгт бүрт Node объект үүсгэнэ үү (мөр s1). Манай тохиолдолд 9 зангилаа (Зангилааны объект) байх болно. Зангилаа бүр нь тэмдэг ба давтамж гэсэн хоёр өгөгдлийн талбараас бүрдэнэ
  2. Зангилааны зангилаа тус бүрт Tree объект (BinaryTree) үүсгэ. Зангилаа нь модны үндэс болдог.
  3. Эдгээр модыг тэргүүлэх дараалалд оруулна уу. Давтамж бага байх тусам тэргүүлэх ач холбогдол өндөр байна. Тиймээс олборлохдоо хамгийн бага давтамжтай модыг үргэлж сонгодог.

Дараа нь та дараахь зүйлийг мөчлөгөөр хийх хэрэгтэй.

  1. Тэргүүлэх дарааллаас хоёр модыг гаргаж аваад шинэ зангилааны хүүхэд (үсэггүй шинээр үүсгэсэн зангилаа) болго. Шинэ зангилааны давтамж нь хоёр удамшлын модны давтамжийн нийлбэртэй тэнцүү байна.
  2. Энэ зангилааны хувьд энэ зангилаанд үндэслэсэн модыг үүсгэ. Энэ модыг тэргүүлэх дараалалд буцааж оруулна уу. (Мод нь шинэ давтамжтай тул дараалалд шинэ газар орох магадлалтай)
  3. Дараалалд нэг мод үлдэх хүртэл 1 ба 2-р алхамуудыг үргэлжлүүлээрэй - Хаффман мод

Энэ алгоритмыг s1 мөрөнд авч үзье.

Хаффман алгоритмаар өгөгдлийг шахах

Энд "lf" (мөр дамжуулах) тэмдэг нь шинэ мөрийг илэрхийлдэг бол "sp" (зай) нь хоосон зай юм.

Дараа нь юу юм?

Бид Хаффманы модыг авсан. БОЛЖ БАЙНА УУ. Тэгээд юу хийх вэ? Тэд үүнийг үнэ төлбөргүй авахгүй, дараа нь та модны үндэснээс навч хүртэлх бүх боломжит замыг хайж олох хэрэгтэй. Бид ирмэгийг зүүн тийш чиглүүлж байгаа бол 0, баруун тийш нь хүргэж байвал 1 гэж тэмдэглэхийг зөвшөөрч байна. Хатуухан хэлэхэд эдгээр тэмдэглэгээнд тэмдэгтийн код нь модны үндэсээс ижил тэмдэгт агуулсан навч хүртэлх зам юм.

Хаффман алгоритмаар өгөгдлийг шахах

Тиймээс кодын хүснэгт гарч ирэв. Хэрэв бид энэ хүснэгтийг авч үзвэл тэмдэгт бүрийн "жингийн" талаар дүгнэлт хийж болно гэдгийг анхаарна уу - энэ бол түүний кодын урт юм. Дараа нь шахсан хэлбэрээр эх файл нь 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 бит жинтэй болно. . Эхлээд энэ нь 176 бит жинтэй байсан. Тиймээс бид үүнийг 176/65 = 2.7 дахин бууруулсан! Гэхдээ энэ бол утопи юм. Ийм харьцааг олж авах магадлал багатай. Яагаад? Үүнийг бага зэрэг дараа хэлэлцэх болно.

Код тайлах

За, магадгүй хамгийн энгийн зүйл бол код тайлах явдал юм. Та нарын олонх нь түүнийг хэрхэн кодлосон талаар ямар ч зөвлөгөөгүйгээр шахсан файл үүсгэх боломжгүй гэдгийг таасан байх гэж бодож байна - бид үүнийг тайлах боломжгүй болно! Тийм ээ, тийм ээ, надад үүнийг ойлгоход хэцүү байсан, гэхдээ би шахалтын хүснэгттэй table.txt текст файлыг үүсгэх ёстой:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

"Тэмдэгтийн код" маягт дахь хүснэгтийн оруулга. 01110 яагаад тэмдэггүй байдаг вэ? Үнэн хэрэгтээ энэ нь тэмдэгттэй байдаг, зүгээр л миний файл руу гаргахдаа ашигладаг java хэрэгслүүд, шинэ мөрийн тэмдэгт - 'n' нь шинэ мөр болж хувирдаг (энэ нь хичнээн тэнэг сонсогдож байсан ч). Иймд дээрх хоосон мөр нь 01110 кодын тэмдэгт байна. 00 кодын хувьд мөрийн эхэнд байгаа зай байна. Хүснэгтийг хадгалах энэ арга нь манай хан коэффициентийн хувьд хамгийн үндэслэлгүй гэж хэлж болно гэдгийг би шууд хэлэх ёстой. Гэхдээ үүнийг ойлгох, хэрэгжүүлэхэд хялбар байдаг. Оновчлолын талаархи санал хүсэлтийг сонсохдоо би баяртай байх болно.

Энэ хүснэгтийн тусламжтайгаар кодыг тайлахад маш хялбар байдаг. Кодчилол үүсгэхдээ ямар дүрмийг баримталж байсныг санацгаая.

Ямар ч код өөр код байх ёсгүй

Энэ нь хөнгөвчлөх үүрэг гүйцэтгэдэг. Бид бага багаар дараалан уншдаг бөгөөд уншсан битүүдээс бүрдэх d тэмдэгтийн тэмдэгтийн кодчилолтой таарч ирмэгц бид тэмдэгтийн тэмдэгт (зөвхөн энэ нь!) кодлогдсон болохыг шууд мэддэг. Дараа нь бид код тайлах мөрөнд (код тайлагдсан мессежийг агуулсан мөр) тэмдэгт бичиж, d мөрийг дахин тохируулж, кодлогдсон файлыг цааш уншина.

Реализация

Архивчин бичээд кодыг минь доромжлох цаг болсон. Үүнийг компрессор гэж нэрлэе.

Эхнээс нь эхлэх. Юуны өмнө бид 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;
    }
}

Одоо мод:

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

Тэргүүлэх дараалал:

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

Хаффманы модыг үүсгэдэг анги:

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

Кодлох/декод тайлах анги:

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

Файл руу бичихийг хөнгөвчлөх анги:

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

Файлаас уншихыг хөнгөвчлөх анги:

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

За тэгээд үндсэн анги:

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 заавартай файлаа бичих хэрэгтэй болно 🙂

дүгнэлт

Үүнийг л хэлэхийг хүссэн байх. Хэрэв танд код, алгоритм, ерөнхийдөө ямар нэгэн оновчлолыг сайжруулах талаар миний чадваргүй байдлын талаар хэлэх зүйл байвал бичээрэй. Хэрэв би тайлбарлаагүй байгаа бол бас бичээрэй. Би та бүхний сэтгэгдлүүдийг сонсохдоо баяртай байх болно!

PS

Тийм ээ, тийм ээ, би энд байгаа, учир нь би коэффициентийн талаар мартаагүй. s1 мөрийн хувьд кодчиллын хүснэгт нь 48 байт жинтэй бөгөөд энэ нь анхны файлаас хамаагүй их бөгөөд нэмэлт тэгүүдийг мартаагүй (нэмсэн тэгийн тоо 7) => шахалтын харьцаа нэгээс бага байх болно: 176 /(65 + 48*8 + 7) = 0.38. Хэрэв та үүнийг бас анзаарсан бол нүүрэн дээр нь хийж дуусгасан гэсэн үг. Тийм ээ, энэ хэрэгжилт нь жижиг файлуудын хувьд туйлын үр ашиггүй байх болно. Гэхдээ том файлуудад юу тохиолдох вэ? Файлын хэмжээ нь кодлох хүснэгтийн хэмжээнээс хамаагүй том байна. Энд алгоритм нь байх ёстой байдлаараа ажилладаг! Жишээ нь, төлөө Фауст монолог архивлагч нь 1.46-тай тэнцэх бодит (идеал биш) коэффициентийг өгдөг - бараг нэг хагас дахин! Тиймээ, файл англи хэл дээр байх ёстой байсан.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх