ከሃፍማን ስልተ ቀመር ጋር የውሂብ መጨመቅ

ግቤት

በዚህ ጽሑፍ ውስጥ ስለ ታዋቂው የሃፍማን አልጎሪዝም እና እንዲሁም በመረጃ መጨናነቅ ውስጥ ስላለው አተገባበር እናገራለሁ ።

በውጤቱም, ቀላል መዝገብ ቤት እንጽፋለን. ይህ አስቀድሞ ነበር ስለ Habré መጣጥፍነገር ግን ያለ ተግባራዊ ትግበራ. የአሁኑ ልጥፍ ቲዎሬቲካል ቁሳቁስ ከትምህርት ቤት የኮምፒዩተር ሳይንስ ትምህርቶች እና ከሮበርት ላፎርት መጽሐፍ "የውሂብ መዋቅሮች እና አልጎሪዝም በጃቫ" የተወሰደ ነው. ስለዚህ, ሁሉም ነገር በቆራጩ ስር ነው!

ትንሽ ነጸብራቅ

በመደበኛ የጽሑፍ ፋይል ውስጥ አንድ ቁምፊ በ 8 ቢት (ASCII ኢንኮዲንግ) ወይም 16 (ዩኒኮድ ኢንኮዲንግ) ተቀምጧል። በመቀጠል, የ ASCII ኢንኮዲንግ እንመለከታለን. ለምሳሌ፣ string s1 = "SUSIE SAYS IT IS EASYn" ይውሰዱ። በአጠቃላይ በመስመሩ ውስጥ 22 ቁምፊዎች አሉ, በእርግጥ, ክፍተቶችን እና የአዲሱ መስመር ገጸ-ባህሪን - 'n'ን ጨምሮ. ይህን መስመር የያዘው ፋይል 22*8 = 176 ቢት ይመዝናል። ጥያቄው ወዲያውኑ ይነሳል: 8 ቁምፊን ለመመስረት ሁሉንም 1 ቢት መጠቀም ምክንያታዊ ነው? ሁሉንም የ ASCII ቁምፊዎችን አንጠቀምም። ምንም እንኳን እነሱ ቢሆኑም ፣ በጣም ተደጋጋሚውን ፊደል - S - በጣም አጭር ኮድ ፣ እና ለትንሽ ፊደል - T (ወይም U ፣ ወይም 'n') - ኮዱን የበለጠ ትክክለኛ መስጠት የበለጠ ምክንያታዊ ይሆናል። ይህ የሃፍማን አልጎሪዝም ነው፡ ፋይሉ አነስተኛ ክብደት ያለውበትን ምርጥ የኢኮዲንግ አማራጭ ማግኘት አለቦት። የተለያዩ ቁምፊዎች የተለያዩ የኮድ ርዝመቶች መኖራቸው በጣም የተለመደ ነው - ይህ የአልጎሪዝም መሠረት ነው።

Додирование

ለምን ለቁምፊ 'S' ኮድ አይሰጡትም, ለምሳሌ, 1 ቢት ርዝመት: 0 ወይም 1. ይሁን 1. ከዚያም ሁለተኛው በጣም የሚከሰቱ ገጸ-ባህሪያት - '' (ቦታ) - እንሰጣለን 0. አስቡት, እርስዎ ማድረግ ጀመሩ. መልእክትህን ዲኮድ - encoded string 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

የእያንዳንዱን ቁምፊ ኮድ ከቦታ ጋር ለይቻለሁ። ይህ በእውነቱ በተጨመቀ ፋይል ውስጥ አይሆንም!
ጥያቄው የሚነሳው ይህ ጀማሪ እንዴት የኮድ ሠንጠረዥን መፍጠር እንደሚቻል ኮድ ይዞ መጣ? ይህ ከዚህ በታች ይብራራል.

የሃፍማን ዛፍ መገንባት

ይህ ሁለትዮሽ ፍለጋ ዛፎች ለማዳን የሚመጡበት ነው. አይጨነቁ፣ እዚህ መፈለግ፣ ማስገባት እና መሰረዝ ዘዴዎች አያስፈልጉዎትም። በጃቫ ውስጥ ያለው የዛፍ መዋቅር ይህ ነው-

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" (linefeed) የሚለው ምልክት አዲስ መስመርን ያመለክታል, "sp" (space) ቦታ ነው.

እና ቀጥሎ ምንድን ነው?

የሃፍማን ዛፍ አገኘን. እሺ እና ምን ይደረግ? እነሱ በነጻ አይወስዱትም እና ከዚያ ከሥሩ እስከ ቅጠሎቹ ድረስ ያሉትን ሁሉንም መንገዶች መፈለግ ያስፈልግዎታል። ጠርዝ 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, ቁምፊው በመስመሩ መጀመሪያ ላይ ያለ ቦታ ነው. ወዲያውኑ መናገር አለብኝ ይህ ጠረጴዛን የማጠራቀሚያ ዘዴ ለካን ኮፊሸንታችን በጣም ምክንያታዊ ያልሆነ ነው ሊል ይችላል. ግን ለመረዳት እና ለመተግበር ቀላል ነው. ስለ ማመቻቸት በሚሰጡ አስተያየቶች ውስጥ የእርስዎን ምክሮች በመስማቴ ደስተኛ ነኝ።

በዚህ ሠንጠረዥ, ኮድ መፍታት በጣም ቀላል ነው. ኢንኮዲንግ ሲፈጠር በምን አይነት ህግ እንደተመራን እናስታውስ፡-

ምንም ኮድ የሌላ ቅድመ ቅጥያ መሆን የለበትም

የአመቻችነት ሚና የሚጫወተው እዚህ ላይ ነው። በጥቂቱ በቅደም ተከተል እናነባለን፣ እና የተነበበው ቢትስ የያዘው string d ልክ ከገጸ ባህሪው ጋር የሚዛመደውን ኢንኮዲንግ ሲዛመድ የገጸ ባህሪው (እና እሱ ብቻ!) በኮድ መቀመጡን ወዲያው እናውቃለን። በመቀጠል ቁምፊን ወደ ዲኮድ ህብረ ቁምፊ (የዲኮድ መልእክት የያዘው ሕብረቁምፊ) እንጽፋለን, d stringን እንደገና ያስጀምሩት እና የተቀዳውን ፋይል የበለጠ እናነባለን.

ትግበራ

ማህደር በመጻፍ የእኔን ኮድ የማዋረድበት ጊዜ ነው። ኮምፕረር እንበለው.

እንደገና ጀምር. በመጀመሪያ ደረጃ የኖድ ክፍልን እንጽፋለን-

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

አዎ፣ አዎ፣ አሁንም እዚህ ነኝ፣ ምክንያቱም ስለ ኮፊቲፊሽኑ አልረሳሁም። ለ string s1 ፣ የኢንኮዲንግ ጠረጴዛው 48 ባይት ይመዝናል - ከመጀመሪያው ፋይል በጣም የበለጠ ፣ እና ስለ ትርፍ ዜሮዎች አልረሱም (የተጨመሩ ዜሮዎች ብዛት 7 ነው) => የመጭመቂያው ጥምርታ ከአንድ ያነሰ ይሆናል: 176 /(65 + 48*8 + 7) = 0.38. እርስዎም ይህንን ካስተዋሉ ፣ ከዚያ በፊትዎ ላይ ጨርሰዋል ማለት አይደለም ። አዎን, ይህ ትግበራ ለአነስተኛ ፋይሎች እጅግ በጣም ውጤታማ አይሆንም. ግን ትላልቅ ፋይሎች ምን ይሆናሉ? የፋይል መጠኖች ከኢኮዲንግ ሰንጠረዥ መጠን በጣም ትልቅ ናቸው። ይህ ስልተ ቀመር እንደ ሁኔታው ​​የሚሰራበት ነው! ለምሳሌ ለ የፋስት ነጠላ ቃላት ማህደሩ ከ 1.46 - አንድ ጊዜ ተኩል ገደማ ጋር እኩል የሆነ ትክክለኛ (ሃሳባዊ ያልሆነ) ቅንጅት ይሰጣል! እና አዎ፣ ፋይሉ በእንግሊዝኛ መሆን ነበረበት።

ምንጭ: hab.com

አስተያየት ያክሉ