ضغط البيانات باستخدام خوارزمية هوفمان

دخول

في هذه المقالة سوف أتحدث عن خوارزمية هوفمان المعروفة ، وكذلك تطبيقها في ضغط البيانات.

نتيجة لذلك ، سوف نكتب أرشيفي بسيط. لقد كان هذا بالفعل مقال عن حبري، ولكن دون تنفيذ عملي. المادة النظرية للوظيفة الحالية مأخوذة من دروس علوم الكمبيوتر المدرسية وكتاب روبرت لافوريت "هياكل البيانات والخوارزميات في جافا". لذلك ، كل شيء تحت الخفض!

القليل من التفكير

في ملف نصي عادي ، يتم ترميز حرف واحد بـ 8 بت (ترميز ASCII) أو 16 (ترميز Unicode). بعد ذلك ، سننظر في ترميز ASCII. على سبيل المثال ، خذ السلسلة s1 = "SUSIE SAYS IT IS EASYn". في المجموع ، هناك 22 حرفًا في السطر ، بالطبع ، بما في ذلك المسافات وحرف السطر الجديد - 'n'. الملف الذي يحتوي على هذا الخط سوف يزن 22 * ​​8 = 176 بت. السؤال الذي يطرح نفسه على الفور: هل من المنطقي استخدام جميع البتات الثمانية لتشفير حرف واحد؟ نحن لا نستخدم جميع أحرف ASCII. حتى لو كان الأمر كذلك ، فسيكون من المنطقي أكثر إعطاء الحرف الأكثر شيوعًا - S - أقصر رمز ممكن ، وللحرف الأكثر ندرة - T (أو U ، أو 'n') - أعط الكود أكثر أصالة. هذه هي خوارزمية هوفمان: تحتاج إلى العثور على أفضل خيار ترميز ، يكون فيه الملف ذو وزن ضئيل. من الطبيعي تمامًا أن يكون للأحرف المختلفة أطوال رمز مختلفة - وهذا هو أساس الخوارزمية.

الترميز

لماذا لا نمنح الحرف 'S' رمزًا ، على سبيل المثال ، طوله 1 بت: 0 أو 1. لنكن 1. ثم ثاني أكثر الحرف تواجدًا - "(مسافة) - سنقدم 0. تخيل ، لقد بدأت في فك شفرة رسالتك - سلسلة مشفرة s1 - وسترى أن الكود يبدأ بـ 1. إذن ، ما العمل: هل هو الحرف S ، أم هو حرف آخر ، مثل A؟ لذلك ، تنشأ قاعدة مهمة:

لا يجب أن يكون أي رمز بادئة أخرى

هذه القاعدة هي مفتاح الخوارزمية. لذلك ، يبدأ إنشاء الكود بجدول تكراري يشير إلى تكرار (عدد التكرارات) لكل رمز:

ضغط البيانات باستخدام خوارزمية هوفمان يجب ترميز الأحرف الأكثر تكرارا بأقل عدد ممكن عدد البتات. سأقدم مثالاً على أحد جداول الكود الممكنة:

ضغط البيانات باستخدام خوارزمية هوفمان لذلك ستبدو الرسالة المشفرة كما يلي:

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

لقد فصلت رمز كل حرف بمسافة. هذا لن يحدث بالفعل في ملف مضغوط!
السؤال الذي يطرح نفسه: كيف توصل هذا المبتدئ إلى رمز كيفية إنشاء جدول رمز؟ سيتم مناقشة هذا أدناه.

بناء شجرة هوفمان

هذا هو المكان الذي تأتي فيه أشجار البحث الثنائية إلى الإنقاذ. لا تقلق ، فلن تحتاج إلى طرق البحث والإدراج والحذف هنا. هنا هيكل الشجرة في جافا:

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. قم بإنشاء كائن عقدة لكل حرف من الرسالة (السطر s1). في حالتنا ، سيكون هناك 9 عقد (كائنات العقدة). تتكون كل عقدة من حقلي بيانات: الرمز والتردد
  2. قم بإنشاء كائن شجرة (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 بدون رمز؟ في الواقع ، إنه برمز ، فقط أدوات جافا التي أستخدمها عند الإخراج إلى ملف ، يتم تحويل حرف السطر الجديد - '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

إضافة تعليق