Տվյալների սեղմում Huffman ալգորիթմի միջոցով

Մուտք

Այս հոդվածում ես կխոսեմ հայտնի Հաֆմանի ալգորիթմի և դրա կիրառման մասին տվյալների սեղմման մեջ։

Արդյունքում, մենք կգրենք պարզ արխիվացնող։ Սա արդեն քննարկվել է։ հոդված Habré-ի մասին, բայց առանց գործնական իրականացման։ Այս գրառման տեսական նյութը վերցված է դպրոցական ինֆորմատիկայի դասերից և Ռոբերտ Լաֆորեի «Տվյալների կառուցվածքներ և ալգորիթմներ Java-ում» գրքից։ Այսպիսով, ամեն ինչ կտրվածքի տակ է։

Որոշ մտքեր

Սովորական տեքստային ֆայլում մեկ նիշը կոդավորվում է 8 բիթով (ASCII կոդավորում) կամ 16-ով (Unicode կոդավորում): Ստորև կքննարկենք ASCII կոդավորումը: Օրինակ՝ վերցնենք s1 = «ՍՅՈՒԶԻՆ ԱՍՈՒՄ Է, ՈՐ ՀԵՇՏ Է» տողը: Տողում, իհարկե, կա 22 նիշ, ներառյալ բացատները և տողի ընդհատման 'n' նիշը: Այս տողը պարունակող ֆայլը կկշռի 22 * ​​​​8 = 176 բիթ: Անմիջապես հարց է առաջանում. արդյո՞ք ռացիոնալ է օգտագործել բոլոր 8 բիթերը 1 նիշ կոդավորելու համար: Ի վերջո, մենք չենք օգտագործում ASCII կոդավորման բոլոր նիշերը: Նույնիսկ եթե մենք դա անեինք, ավելի ռացիոնալ կլիներ ամենահաճախ հանդիպող տառին՝ S-ին տալ հնարավորինս կարճ կոդ, իսկ ամենահազվագյուտ տառին՝ T-ին (կամ U-ին կամ 'n'-ին)՝ ավելի երկար կոդ: Ահա թե ինչի մասին է Հաֆմանի ալգորիթմը. անհրաժեշտ է գտնել կոդավորման օպտիմալ տարբերակը, որը ֆայլին կտա նվազագույն քաշ: Բավականին նորմալ է, որ կոդի երկարությունները տարբերվեն տարբեր նիշերի համար. սա է ալգորիթմի հիմքը։

Կոդավորում

Ինչո՞ւ չտալ «S» սիմվոլը, օրինակ, 1 բիթ երկարությամբ կոդ՝ 0 կամ 1: Ենթադրենք՝ 1: Այնուհետև տանք երկրորդ ամենատարածված սիմվոլը՝ « » (բացատ), 0: Պատկերացրեք, որ սկսել եք վերծանել ձեր հաղորդագրությունը՝ կոդավորված s1 տողը, և տեսնում եք, որ կոդը սկսվում է 1-ով: Այսպիսով, ի՞նչ պետք է անեք. դա S սիմվոլն է, թե՞ որևէ այլ սիմվոլ, օրինակ՝ A: Այսպիսով, առաջանում է կարևոր կանոն.

Ոչ մի կոդ չպետք է լինի մեկ այլ կոդի նախածանց։

Այս կանոնը ալգորիթմի բանալին է։ Հետևաբար, կոդի ստեղծումը սկսվում է հաճախականության աղյուսակից, որը ցույց է տալիս յուրաքանչյուր սիմվոլի հաճախականությունը (կրկնությունների քանակը)։

Տվյալների սեղմում Huffman ալգորիթմի միջոցով Ամենաշատ հանդիպող նշանները պետք է կոդավորվեն ամենաքիչ թվով հնարավոր բիթերի քանակը։ Ես կբերեմ հնարավոր կոդային աղյուսակներից մեկի օրինակ՝

Տվյալների սեղմում Huffman ալգորիթմի միջոցով Այսպիսով, կոդավորված հաղորդագրությունը կունենա հետևյալ տեսքը՝

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 հանգույց (Node օբյեկտներ): Յուրաքանչյուր հանգույց բաղկացած է երկու տվյալների դաշտերից՝ սիմվոլ և հաճախականություն:
  2. Ստեղծեք BinaryTree օբյեկտ յուրաքանչյուր հանգույցի համար։ Հանգույցը դառնում է ծառի արմատը։
  3. Տեղադրեք այս ծառերը առաջնահերթության հերթում։ Որքան ցածր է հաճախականությունը, այնքան բարձր է առաջնահերթությունը։ Այսպիսով, արդյունահանելիս միշտ ընտրվում է ամենացածր հաճախականություն ունեցող ծառը։

Հաջորդը, ցիկլիկորեն պետք է անել հետևյալը.

  1. Առաջնահերթության հերթից հանեք երկու ծառ և դարձրեք դրանք նոր հանգույցի (նոր ստեղծված հանգույց՝ առանց տառի) զավակներ։ Նոր հանգույցի հաճախականությունը հավասար է երկու զավակ ծառերի հաճախականությունների գումարին։
  2. Այս հանգույցի համար ստեղծեք ծառ, որի արմատը կտեղադրվի այս հանգույցում: Այս ծառը վերադարձրեք առաջնահերթության հերթ: (Քանի որ ծառն ունի նոր հաճախականություն, այն, ամենայն հավանականությամբ, կլինի հերթի նոր տեղում):
  3. Շարունակեք 1-ին և 2-րդ քայլերը, մինչև հերթում մնա միայն մեկ ծառ՝ Հաֆմանի ծառը։

Եկեք դիտարկենք այս ալգորիթմը s1 տողի վրա։

Տվյալների սեղմում Huffman ալգորիթմի միջոցով

Այստեղ «lf» (տողի թարմացում) նշանը նշանակում է նոր տող, «sp» (բացատ)՝ բացատ։

Ի՞նչ է հաջորդը:

Մենք Հաֆմանի ծառ ունենք։ Լավ։ Ի՞նչ անել դրանով։ Նրանք այն անվճար չեն վերցնի։ Եվ հետո, մենք պետք է հետևենք բոլոր հնարավոր ուղիներին՝ արմատից մինչև ծառի տերևները։ Եկեք համաձայնվենք, որ եզրը նշանակենք 0, եթե այն տանում է դեպի ձախ ժառանգորդ, և 1, եթե այն տանում է դեպի աջ։ Խստորեն ասած, այս նշումներում սիմվոլի կոդը ծառի արմատից մինչև հենց այս սիմվոլը պարունակող տերևը տանող ուղին է։

Տվյալների սեղմում Huffman ալգորիթմի միջոցով

Ահա թե ինչպես ստացանք կոդերի աղյուսակը։ Նկատի ունեցեք, որ եթե նայենք այս աղյուսակին, կարող ենք եզրակացություն անել յուրաքանչյուր սիմվոլի «քաշի» մասին՝ սա նրա կոդի երկարությունն է։ Այնուհետև, սեղմված տեսքով, սկզբնական ֆայլը կկշռի՝ 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-ի՝ գրեթե մեկուկես անգամ։ Եվ այո, ենթադրվում էր, որ ֆայլը կլինի անգլերեն լեզվով։

Source: www.habr.com

Գնեք հուսալի հոստինգ DDoS պաշտպանությամբ կայքերի, VPS VDS սերվերների համար 🔥 Գնեք հուսալի կայքերի հոսթինգ՝ DDoS պաշտպանությամբ, VPS VDS սերվերներով | ProHoster