Huffman 알고리즘을 사용한 데이터 압축

기입

이 기사에서는 잘 알려진 Huffman 알고리즘과 데이터 압축 응용 프로그램에 대해 설명합니다.

결과적으로 간단한 아카이버를 작성합니다. 이것은 이미 하브레에 관한 기사, 그러나 실제 구현은 없습니다. 현재 게시물의 이론적 자료는 학교 컴퓨터 과학 수업과 Robert Laforet의 저서 "Data Structures and Algorithms in Java"에서 가져왔습니다. 그래서 모든 것이 잘립니다!

조금 생각

일반 텍스트 파일에서 하나의 문자는 8비트(ASCII 인코딩) 또는 16(유니코드 인코딩)으로 인코딩됩니다. 다음으로 ASCII 인코딩을 살펴보겠습니다. 예를 들어 문자열 s1 = "SUSIE SAYS IT IS EASYn"을 가져옵니다. 물론 줄에는 공백과 개행 문자인 'n'을 포함하여 총 22개의 문자가 있습니다. 이 줄을 포함하는 파일의 무게는 22*8 = 176비트입니다. 질문이 즉시 발생합니다. 8개의 문자를 인코딩하기 위해 1비트를 모두 사용하는 것이 합리적입니까? 우리는 모든 ASCII 문자를 사용하지 않습니다. 그렇더라도 가장 자주 사용되는 문자인 S에 가능한 가장 짧은 코드를 제공하고 가장 희귀한 문자인 T(또는 U 또는 'n')에 대해 코드에 더 확실한 코드를 제공하는 것이 더 합리적일 것입니다. 이것은 Huffman 알고리즘입니다. 파일의 무게가 최소가 되는 최상의 인코딩 옵션을 찾아야 합니다. 서로 다른 문자가 서로 다른 코드 길이를 갖는 것은 매우 정상입니다. 이것이 알고리즘의 기초입니다.

코딩

문자 '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

각 문자의 코드를 공백으로 구분했습니다. 이것은 압축 파일에서는 실제로 발생하지 않습니다!
질문이 생깁니다. 이 신인은 코드 테이블을 만드는 방법에 대한 코드를 어떻게 생각해 냈습니까? 이에 대해서는 아래에서 설명합니다.

허프만 트리 만들기

이것은 이진 검색 트리가 구출되는 곳입니다. 걱정하지 마십시오. 여기서는 검색, 삽입 및 삭제 방법이 필요하지 않습니다. 다음은 자바의 트리 구조입니다.

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에서 이 알고리즘을 고려하십시오.

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에 기호가 없는 이유는 무엇입니까? 사실, 파일로 출력할 때 사용하는 자바 도구인 줄 바꿈 문자인 '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;//возвращаем удаленный элемент(элемент с наименьшей частотой)
    }
}

Huffman 트리를 생성하는 클래스:

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*XNUMX + XNUMX) = XNUMX. 당신이 이것을 알아 차렸다면 당신이 끝난 얼굴이 아닙니다. 예, 이 구현은 작은 파일에 대해 매우 비효율적입니다. 그러나 대용량 파일은 어떻게 됩니까? 파일 크기는 인코딩 테이블 크기보다 훨씬 큽니다. 이것은 알고리즘이 정상적으로 작동하는 곳입니다! 예를 들어 파우스트의 독백 아카이버는 1.46에 해당하는 실제(이상적이지 않은) 계수를 제공합니다. 거의 XNUMX배입니다! 그리고 예, 파일은 영어로 되어 있어야 했습니다.

출처 : habr.com

코멘트를 추가