Datuen konpresioa Huffman algoritmoarekin

Sarrera

Artikulu honetan, Huffman algoritmo ezagunari buruz hitz egingo dut, baita datuen konpresioan duen aplikazioaz ere.

Ondorioz, artxibo sinple bat idatziko dugu. Hau dagoeneko izan da Habréri buruzko artikulua, baina ezarpen praktikorik gabe. Oraingo postaren material teorikoa eskolako informatika ikasgaietatik eta Robert Laforeten "Data Structures and Algorithms in Java" liburutik hartua da. Beraz, dena mozketa azpian dago!

Hausnarketa txiki bat

Testu-fitxategi arrunt batean, karaktere bat 8 ​​bitekin (ASCII kodeketa) edo 16 (Unicode kodeketa) kodetzen da. Ondoren, ASCII kodeketa kontuan hartuko dugu. Adibidez, har itzazu s1 = "SUSIEK DIO ERRAZA DELAn". Guztira, 22 karaktere daude lerroan, noski, zuriuneak eta lerro berriko karakterea - 'n' barne. Lerro hau daukan fitxategiak 22*8 = 176 bit pisatuko ditu. Galdera berehala sortzen da: arrazionala al da karaktere bat kodetzeko 8 bit guztiak erabiltzea? Ez ditugu ASCII karaktere guztiak erabiltzen. Izango balira ere, arrazionalagoa litzateke letra maizenari -S- ahalik eta koderik laburrena ematea, eta letra arraroenari -T (edo U, edo 'n')- kodea benetakoagoa ematea. Hau da Huffman algoritmoa: kodetze aukerarik onena aurkitu behar duzu, fitxategiak pisu minimoa izango duen. Oso normala da karaktere ezberdinek kode luzera desberdinak izatea - hau da algoritmoaren oinarria.

Kodetzea

Zergatik ez eman 'S' karaktereari kode bat, adibidez, 1 bit luzea: 0 edo 1. Izan bedi 1. Orduan gehien gertatzen den bigarren karakterea - ' ' (espazioa) - 0 emango dizugu. Imajinatu, hasi zara. deskodetu zure mezua - kodetutako s1 katea - eta kodea 1etik hasten dela ikusten duzu. Beraz, zer egin: S karakterea al da, edo beste karaktereren bat, A adibidez? Horregatik, arau garrantzitsu bat sortzen da:

Koderik ez da beste baten aurrizkia izan behar

Arau hau algoritmoaren gakoa da. Hori dela eta, kodea sortzea maiztasun-taula batekin hasten da, zeinak ikur bakoitzaren maiztasuna (gerraldi kopurua) adierazten duen:

Datuen konpresioa Huffman algoritmoarekin Agerraldi gehien dituzten karaktereak gutxienekin kodetu behar dira posible bit kopurua. Kode taula posibleetako baten adibide bat emango dut:

Datuen konpresioa Huffman algoritmoarekin Beraz, kodetutako mezua honela izango da:

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

Karaktere bakoitzaren kodea zuriune batekin bereizi dut. Hau ez da benetan gertatuko fitxategi konprimitu batean!
Galdera sortzen da: nola asmatu zuen hasiberri honek kode bat nola sortu kode taula bat? Jarraian eztabaidatuko da.

Huffman zuhaitz bat eraikitzen

Hona hemen bilaketa-zuhaitz bitarrak erreskatatzeko. Ez kezkatu, ez dituzu hemen bilatu, txertatu eta ezabatu metodorik beharko. Hona hemen zuhaitz-egitura java-n:

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

Hau ez da kode osoa, kode osoa behean egongo da.

Hona hemen zuhaitz bat eraikitzeko algoritmoa:

  1. Sortu Node objektu bat mezuko karaktere bakoitzeko (s1 lerroa). Gure kasuan, 9 nodo egongo dira (Nodo objektuak). Nodo bakoitzak bi datu-eremu ditu: sinboloa eta maiztasuna
  2. Sortu zuhaitz objektu bat (BinaryTree) Nodo nodo bakoitzeko. Nodoa zuhaitzaren erro bihurtzen da.
  3. Txertatu zuhaitz hauek lehentasun-ilaran. Zenbat eta maiztasun txikiagoa izan, orduan eta lehentasun handiagoa. Horrela, ateratzerakoan, maiztasun txikiena duen zuhaitza hautatzen da beti.

Ondoren, ziklikoki honako hau egin behar duzu:

  1. Berreskuratu bi zuhaitz lehentasun-ilaratik eta bihurtu nodo berri baten seme-alaba (letrarik gabeko nodo sortu berri bat). Nodo berriaren maiztasuna ondorengo bi zuhaitzen maiztasunen baturaren berdina da.
  2. Nodo honetarako, sortu nodo honetan errotutako zuhaitz bat. Sartu zuhaitz hau berriro lehentasun-ilaran. (Zuhaitzak maiztasun berria duenez, ziurrenik ilaran leku berri batean sartuko da)
  3. Jarraitu 1. eta 2. urratsak zuhaitz bat ilaran utzi arte - Huffman zuhaitza

Demagun algoritmo hau s1 lerroan:

Datuen konpresioa Huffman algoritmoarekin

Hemen "lf" (lerroaren aurrerapena) sinboloak lerro berri bat adierazten du, "sp" (espazioa) espazio bat da.

Eta zer gertatuko da?

Huffman zuhaitza lortu dugu. ADOS. Eta zer egin horrekin? Ez dute dohainik hartuko.Eta gero, bide posible guztiak trazatu behar dituzu sustraitik zuhaitzaren hostoetaraino. Ertz bati 0 etiketatzea onartzen dugu ezkerreko haurra eramaten badu eta 1 eskuinera eramaten badu. Zorrotz esanda, notazio hauetan, karaktere-kodea zuhaitzaren errotik karaktere hori bera duen hostorainoko bidea da.

Datuen konpresioa Huffman algoritmoarekin

Horrela, kodeen taula atera zen. Kontuan izan taula hau kontuan hartzen badugu, karaktere bakoitzaren "pisuari buruz" ondoriozta dezakegula - hau da bere kodearen luzera. Ondoren, forma konprimituan, iturburu-fitxategiak pisua izango du: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bit . Hasieran 176 bit pisatzen zituen. Hori dela eta, 176/65 = 2.7 aldiz murriztu dugu! Baina hau utopia bat da. Nekez lortuko da proportzio hori. Zergatik? Hori pixka bat geroago eztabaidatuko da.

Deskodetzea

Tira, agian geratzen den gauzarik errazena deskodetzea da. Uste dut zuetako askok asmatu duzula ezinezkoa dela fitxategi konprimitu bat sortzea nola kodetu den argibiderik gabe; ezin izango dugu deskodetu! Bai, bai, zaila egin zitzaidan honetaz jabetzea, baina konpresio taula batekin testu-fitxategi bat sortu behar dut table.txt:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Taularen sarrera "karaktere" "karaktere kodea" forman. Zergatik dago 01110 ikurrik gabe? Izan ere, ikur batekin dago, fitxategi batera irteteko erabiltzen ditudan java tresnak soilik, lerro berriko karakterea - 'n' - lerro berri batean bihurtzen da (ez du axola zein ergela den). Beraz, goiko lerro hutsa 01110 kodearen karakterea da. 00 kodearentzat, karakterea lerroaren hasieran dagoen zuriune bat da. Berehala esan behar dut taula gordetzeko metodo honek gure khan koefizientearentzat irrazionalena dela esan dezakeela. Baina erraza da ulertzea eta ezartzea. Pozik entzungo ditut zure gomendioak optimizazioari buruzko iruzkinetan.

Taula honekin, oso erraza da deskodetzea. Gogora dezagun kodeketa sortzean zer arautan gidatu gintuen:

Koderik ez da beste baten aurrizkia izan behar

Hau da, non erraztasun-eginkizuna betetzen du. Pixkanaka irakurtzen dugu sekuentzialki, eta ondoriozko d katea, irakurritako bitez osatua, karaktere-karaktereari dagokion kodifikazioarekin bat datorren bezain laster, berehala dakigu karaktere-karakterea (eta hori bakarrik!) kodetu zela. Ondoren, karakterea idazten dugu dekode-katean (deskodetutako mezua duen katea), d katea berrezarri eta kodetutako fitxategia irakurtzen dugu.

Inplementazioa

Artxibatzaile bat idatziz nire kodea umiliatzeko garaia da. Dei diezaiogun Compressor.

Berriro hasi. Lehenik eta behin, Node klasea idatziko dugu:

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

Orain zuhaitza:

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

Lehentasun ilara:

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 zuhaitza sortzen duen klasea:

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

Zein kodetu/deskodetzen duen klasea:

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

Fitxategi batean idaztea errazten duen klasea:

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

Fitxategi batetik irakurtzea errazten duen klasea:

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

Beno, eta klase nagusia:

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

Zuk zeuk idatzi beharko duzu fitxategia readme.txt argibideekin 🙂

Ondorioa

Uste dut hori dela esan nahi nuen guztia. Kodearen, algoritmoaren, oro har, edozein optimizazioren hobekuntzei buruz nire gaitasun eza esateko zerbait baduzu, idatzi lasai. Zerbait azaldu ez badut, idatzi ere mesedez. Gustatuko litzaidake zure berri entzutea iruzkinetan!

PS

Bai, bai, hemen jarraitzen dut, ez bainintzen koefizienteaz ahaztu. s1 kateari dagokionez, kodetze-taulak 48 byte pisatzen ditu - jatorrizko fitxategiak baino askoz gehiago, eta ez zituzten aparteko zeroez ahaztu (gehitutako zeroen kopurua 7 da) => konpresio-erlazioa bat baino txikiagoa izango da: 176 /(65 + 48*8 + 7) = 0.38. Zuk ere konturatu bazenuen, aurpegian ez duzu amaitu. Bai, inplementazio hau oso eraginkorra izango da fitxategi txikientzat. Baina zer gertatzen da fitxategi handiekin? Fitxategien tamainak kodetze-taularen tamaina baino askoz handiagoak dira. Hemen da algoritmoak behar bezala funtzionatzen duena! Adibidez, for Faustoren bakarrizketa artxibatzaileak 1.46ko koefiziente erreala (ez idealizatua) ematen du - ia aldiz eta erdi! Eta bai, fitxategia ingelesez egon behar zen.

Iturria: www.habr.com

Gehitu iruzkin berria