Huffman-adatok tömörítése

Bevezetés

Ebben a cikkben a híres Huffman-algoritmusról fogok beszélni, valamint annak alkalmazásáról az adatok tömörítésében.

Ennek eredményeként írni fogunk egy egyszerű archiváló programot. Erről már volt cikk a Habr-on, de gyakorlati megvalósítás nélkül. A jelenlegi bejegyzés elméleti anyagát az iskolai informatika órákból és Robert Lafore "Adatszerkezetek és algoritmusok Java nyelven" című könyvéből vettük. Nos, mindenki, aki érdeklődik, kattintson ide!

Néhány gondolat

Egy szokásos szövegfájlban egy karakter 8 biten (ASCII kódolás) vagy 16 biten (Unicode kódolás) kódolva van. Ezért ezúttal az ASCII kódolást fogjuk figyelembe venni. Például vegyünk egy s1 = "SUSIE SAYS IT IS EASYn" stringet. Összesen 22 karakter van a stringben, természetesen beleértve a szóközöket és az új sorra váltó karaktert — ‘n’. A fájl, amely ezt a stringet tartalmazza, 22*8 = 176 bit méretű lesz. Azonnal felmerül a kérdés: ésszerű-e mind a 8 bitet használni 1 karakter kódolására? Hiszen nem használjuk az ASCII kódolás összes karakterét. Még ha használnánk is, ésszerűbb lenne a leggyakoribb betűnek — S — a lehető legrövidebb kódot adni, míg a legritkább betűnek — T-nek (vagy U-nak, vagy ‘n’-nek) — hosszabb kódot adni. A Huffman-algoritmus lényege éppen ez: meg kell találni az optimális kódolási lehetőséget, amely a fájl méretét minimalizálja. Teljesen normális, hogy különböző karakterek kódhossza eltérő — ez alapján épül fel az algoritmus.

Kódolás

Miért ne lehetne a ‘S’ karakternek például 1 bit hosszúságú kódot adni: 0 vagy 1? Legyen ez 1. Ezután a második leggyakoribb karakternek — ‘ ‘(szóköz) — adjunk 0-t. Képzelje el, hogy elkezdte dekódolni üzenetét — az s1 kódolt stringet — és látja, hogy a kód 1-gyel kezdődik. Na és mit csináljon: ez S karakter, vagy egy másik karakter, például A? Ezért van egy fontos szabály:

Egyetlen kód sem lehet más kód prefixe

Ez a szabály kulcsfontosságú az algoritmusban. Ezért a kód létrehozása a karakterek gyakorisági táblázatával kezdődik, amely megmutatja minden karakter előfordulási gyakoriságát (a megjelenések számát):

Huffman-adatok tömörítése A leggyakoribb karaktereket a lehető legkevesebb bit számban kell kódolni. Például bemutatok egy lehetséges kódok táblázatot:

Huffman-adatok tömörítése Így a kódolt üzenet így fog kinézni:

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

Minden karakter kódját szóközzel elválasztottam. A tömörített fájlban ez valójában nem így lesz!
Felmerül a kérdés: hogyan találta ki ez a srác a kódot a kódok táblázatának létrehozásához? Erről lesz szó az alábbiakban.

Huffman-fa felépítése

Itt jönnek a segítségünkre a bináris keresőfák. Ne aggódjon, itt a keresési, beszúrási és törlési módszerekre nincs szükség. Íme a fa struktúrája Java-ban:

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

Ez nem a teljes kód, a teljes kód lentebb található.

Itt van maga az algoritmus a fa felépítéséhez:

  1. Hozzon létre egy Node objektumot minden karakterhez az üzenetből (s1 sztring). Ebben az esetben 9 csomópont lesz (Node objektumok). Minden csomópont két adatmezőből áll: karakter és frekvencia
  2. Hozzon létre egy Fa (BinaryTree) objektumot minden Node csomópont számára. A csomópont lesz a fa gyökere.
  3. Illessze be ezeket a fákat a prioritási sorba. Minél kisebb a frekvencia, annál nagyobb a prioritás. Így a kinyerés során mindig a legkisebb frekvenciájú fához jön a választás.

Ezután folyamatosan a következőt kell végrehajtani:

  1. Kinyerni két fát a prioritási sorból és a leszármazottjaivá tenni egy új csomópontnak (az éppen létrehozott csomópontnak, amelynek nincs betűje). Az új csomópont frekvenciája a két leszármazott fa frekvenciáinak összege.
  2. Ebből a csomópontból fa készítése a megadott csomópont gyökerével. Ezt a fát vissza kell illeszteni a prioritási sorba. (Mivel a fának új frekvenciája van, valószínűleg új helyet fog kapni a sorban)
  3. Folytassa az 1. és 2. lépések végrehajtását, amíg a sorban csak egy fa marad - a Huffman-fa

Vizsgáljuk meg ezt az algoritmust az s1 sztring alapján:

Huffman-adatok tömörítése

Itt a »lf« (sorvége) jelzi az új sorra való átállást, a »sp« (szóköz) pedig a szóközt jelenti.

Mi jön ezután?

Megkaptuk a Huffman fát. Rendben van. És mit kezdjünk vele? Még ingyen sem kérik el. Ezután nyomon kell követni az összes lehetséges utat a gyökértől a fa leveleihez. Azt fogjuk feltételezni, hogy a bal utódhoz vezető élt 0-val, a jobb utódhoz vezetőt pedig 1-gyel jelöljük. Szigorúan véve, ezekben a jelölésekben a karakter kódja a fa gyökerétől a levelet tartalmazó levélhez vezető út.

Huffman-adatok tömörítése

Így alakult ki a kódok táblázata. Észrevehetjük, hogy ha megvizsgáljuk ezt a táblázatot, levonhatjuk a következtetést minden egyes karakter «súlyáról» — ez a kódjának hossza. Így tömörített formában az eredeti fájl súlya: 2 * 3 + 2*4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bit. Eleinte 176 bit volt. Következésképpen 176/65 = 2,7-szer csökkentettük! De ez utópia. Ilyen arány valószínűleg nem fog előállni. Miért? Erről lesz szó később.

Dekódolás

Nos, talán a legegyszerűbb feladat maradt — a dekódolás. Azt hiszem, sokan közülük rájöttek, hogy nem lehet egyszerűen létrehozni egy tömörített fájlt anélkül, hogy bármilyen nyomot hagynánk arról, hogyan kódoltuk — nem tudjuk dekódolni! Igen, nehéz volt ezt elfogadni, de létre kell hoznunk egy text fájlt table.txt a tömörítési táblázattal:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

A táblázat rögzítése a ‘karakter’«karakter kódja» formátumban. Miért 01110 karakter nélkül? Valójában van egy karaktere, csak a Java eszközök, amelyeket használtam a fájlba való kiíráskor, a sortörést – ‘n’ — konvertálják sorvágásra (bárhogy is tűnik ez bolondnak). Így az üres sor a fent a 01110 kódhoz tartozó karaktere. A 00 kódhoz a szóköz a sor elején a karakter. Azonnal elmondom, hogy ennek a tárolási módnak annyi, hogy ez a módszer a legkevésbé gazdaságos. De könnyen érthető és megvalósítható. Szívesen várom javaslataikat a hozzászólásokban az optimalizálással kapcsolatban.

Ezzel a táblázattal nagyon egyszerű a dekódolás. Emlékezzünk, milyen szabályt követtünk a kódolás létrehozásakor:

Egyetlen kód sem lehet másik kód előtagja

Itt játszik könnyítő szerepet. Folyamatosan olvassuk bitről bitre, és amint az így kapott d sztring, amely az olvasott bitekből áll, megegyezik a karakterhez tartozó kódolással, azonnal tudjuk, hogy a karaktert kódoltuk (és csak azt!). Ezután a karaktert rögzítjük a dekódoló sztringbe (amely a dekódolt üzenetet tartalmazza), nullázzuk az d sztringet, és tovább olvassuk a tömörített fájlt.

Megvalósítás

Elérkezett az idő, hogy végigjárjuk a kódom, és írjunk egy tömörítőt. Hívjuk ezt Compressor-nak.

Kezdjük az elején. Először írjuk meg a Node osztályt:

public class Node {
    private int frequence; // frekvencia
    private char letter; // betű
    private Node leftChild; // bal gyermek
    private Node rightChild; // jobb gyermek

   

    public Node(char letter, int frequence) { // a konstruktor
        this.letter = letter;
        this.frequence = frequence;
    }

    public Node() {} // a névtelen csomópontokhoz való konstruktor túlterhelése (lásd fent a Huffman-fa építésének részében)
    public void addChild(Node newNode) { // gyermek hozzáadása
        if (leftChild == null) // ha a bal üres => a jobb is üres => a balhoz adjuk hozzá
            leftChild = newNode;
        else {
            if (leftChild.getFrequence() <= newNode.getFrequence()) // a bal gyermek lesz a kisebb frekvenciájú
                rightChild = newNode; // az lesz, akinél kisebb a frekvencia
            else {
                rightChild = leftChild;
                leftChild = newNode;
            }
        }

        frequence += newNode.getFrequence(); // összesített frekvencia
    }

    public Node getLeftChild() {
        return leftChild;
    }

    public Node getRightChild() {
        return rightChild;
    }

    public int getFrequence() {
        return frequence;
    }

    public char getLetter() {
        return letter;
    }

    public boolean isLeaf() { // levél ellenőrzése
        return leftChild == null && rightChild == null;
    }
}

Most a fára:

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

Prioritási sor:

import java.util.ArrayList; // igen, a sor lista alapú lesz

class PriorityQueue {
    private ArrayList data; // a sor listája
    private int nElems; // az elemek száma a sorban

    public PriorityQueue() {
        data = new ArrayList();
        nElems = 0;
    }

    public void insert(BinaryTree newTree) { // beszúrás
        if (nElems == 0)
            data.add(newTree);
        else {
            for (int i = 0; i  newTree.getFrequence()) { // ha a beszúrandó fa frekvenciája kisebb
                    data.add(i, newTree); // mint a jelenlegié, akkor jobbra tolva beszúrjuk
                    break; // majd a jelenlegi helyére tesszük az új fát
                }
                if (i == nElems - 1) 
                    data.add(newTree);
            }
        }
        nElems++; // az elemek számát növeljük 1-el
    }

    public BinaryTree remove() { // eltávolítás a sorból
        BinaryTree tmp = data.get(0); // másoljuk az eltávolítandó elemet
        data.remove(0); // tulajdonképpen eltávolítjuk
        nElems--; // csökkentjük az elemek számát 1-el
        return tmp; // visszaadjuk az eltávolított elemet (a legkisebb frekvenciájú elemet)
    }
}

A Huffman-fát létrehozó osztály:

public class HuffmanTree {
    private final byte ENCODING_TABLE_SIZE = 127; // a kódoló táblázat hossza
    private String myString; // üzenet
    private BinaryTree huffmanTree; // Huffman fa
    private int[] freqArray; // frekvencia táblázat
    private String[] encodingArray; // kódoló táblázat


    // ----------------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();
        // az algoritmus fent van leírva
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            if (freqArray[i] != 0) { // ha a karakter létezik a stringben
                Node newNode = new Node((char) i, freqArray[i]); // Node-ot kell létrehozni
                BinaryTree newTree = new BinaryTree(newNode); // és a Node-hoz BinaryTree-t
                pq.insert(newTree); // beszúrás a sorba
            }
        }

        while (true) {
            BinaryTree tree1 = pq.remove(); // az első fa eltávolítása a sorból.

            try {
                BinaryTree tree2 = pq.remove(); // a második fa eltávolítása a sorból

                Node newNode = new Node(); // új Node létrehozása
                newNode.addChild(tree1.getRoot()); // az eltávolított fák leszármazottaiként való hozzáadása
                newNode.addChild(tree2.getRoot());

                pq.insert(new BinaryTree(newNode));
            } catch (IndexOutOfBoundsException e) { // egy fa maradt a sorban
                return tree1;
            }
        }
    }

    public BinaryTree getTree() {
        return huffmanTree;
    }

    // -------------------encoding array------------------
    void fillEncodingArray(Node node, String codeBefore, String direction) { // a kódoló táblázat kitöltése
        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() { // a hibakereséshez
        fillEncodingArray(huffmanTree.getRoot(), "", "");

        System.out.println("======================Kódoló táblázat====================");
        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;
    }
}

Osztály, amely kódol vagy dekódol:

public class HuffmanOperator {
    private final byte ENCODING_TABLE_SIZE = 127; // táblázat hossza
    private HuffmanTree mainHuffmanTree; // Huffman-fa (csak tömörítéshez használva)
    private String myString; // eredeti üzenet
    private int[] freqArray; // frekvencia-tábla
    private String[] encodingArray; // kódolási tábla
    private double ratio; // tömörítési arány

    public HuffmanOperator(HuffmanTree MainHuffmanTree) { // tömörítéshez
        this.mainHuffmanTree = MainHuffmanTree;

        myString = mainHuffmanTree.getOriginalString();

        encodingArray = mainHuffmanTree.getEncodingArray();

        freqArray = mainHuffmanTree.getFrequenceArray();
    }

    public HuffmanOperator() {} // kinyeréshez;

    // ---------------------------------------tömörítés-----------------------------------------------------------
    private String getCompressedString() {
        String compressed = "";
        String intermidiate = ""; // közbülső szöveg (hozzáadott nullák nélkül)
        // System.out.println("=============================Tömörítés=======================");
        // displayEncodingArray();
        for (int i = 0; i 
        // nullákat kell hozzáadni a végéhez (lehet 1, nincs különbség)
        byte counter = 0; // a végéhez hozzáadott nullák száma (byte elég: 0 <= counter < 8 < 127)
        for (int length = intermidiate.length(), delta = 8 - length % 8; 
                counter < delta; counter++) { // delta - a hozzáadott nullák száma
            intermidiate += "0";
        }

        // a hozzáadott nullák számát összekapcsolni a bináris ábrázolásával és a közbülső szöveggel
        compressed = String.format("%8s", Integer.toBinaryString(counter & 0xff)).replace(" ", "0") + intermidiate;

        // idealizált arány
        setCompressionRatio();
        // System.out.println("===============================================================");
        return compressed;
    }

    private void setCompressionRatio() { // idealizált arány számítása
        double sumA = 0, sumB = 0; // A - az eredeti összeg
        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() { // végső tömörítés
        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;
    }
    // ---------------------------------------tömörítés vége----------------------------------------------------------------
    // ------------------------------------------------------------kinyerés-----------------------------------------------------
    public String extract(String compressed, String[] newEncodingArray) {
        String decompressed = "";
        String current = "";
        String delta = "";
        encodingArray = newEncodingArray;

        // displayEncodingArray();
        // a hozzáadott nullák számának megkapása
        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, mivel az első byte a hozzáadott nullák számát tartalmazza
            current += compressed.charAt(i);
            for (int j = 0; j < ENCODING_TABLE_SIZE; j++) {
                if (current.equals(encodingArray[j])) { // ha egyezik
                    decompressed += (char) j; // akkor hozzáadjuk az elemet
                    current = ""; // és nullázzuk a jelenlegi szöveget
                }
            }
        }

        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() { // a hibakereséshez
        System.out.println("======================Kódolási tábla====================");
        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("========================================================");
    }
}

Osztály, amely megkönnyíti a fájlba írást:

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("Érvénytelen útvonal, vagy az a fájl nem létezik!");
    	}
    }

    @Override
    public void close() throws IOException {
        fileOutputStream.close();
    }

    public void finalize() throws IOException {
        close();
    }
}

Osztály, amely megkönnyíti a fájlból való olvasást:

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) // ha a fájl véget ért
    		throw new EOFException();
    	return (byte)cur;
    }
    
    public String readLine() throws IOException {
    	return fileBufferedReader.readLine();
    }
    
    @Override
    public void close() throws IOException{
    	fileInputStream.close();
    }
}

Nos, és a fő osztály:

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 {\/\/megadjuk a parancssori argumentumokkal a műveletet
            if (args[0].equals("--compress") || args[0].equals("-c"))
                compress(args[1]);
            else if ((args[0].equals("--extract") || args[0].equals("-x"))
            \t\t&& (args[2].equals("--table") || args[2].equals("-t"))) {
            \textract(args[1], args[3]);
            }
            else
                throw new IllegalArgumentException();
        } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException e) {
            System.out.println("Érvénytelen argumentumok formátuma");
            System.out.println("Olvassa el a Readme.txt fájlt");
            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("Érvénytelen útvonal, vagy a fájl nem létezik!");
            return;
        } catch (MalformedInputException e) {
        \tSystem.out.println("A fájl aktuális kódolása nem támogatott");
        \treturn;
        }

        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)) {
        \tfo.writeBytes(operator.getBytedMsg());
        }
        \/\/kódolási táblázat fájl létrehozása:
        
        table = new File(inputFile.getAbsolutePath() + ".table.txt");
        table.createNewFile();
        try (FileOutputHelper fo = new FileOutputHelper(table)) {
        \tfo.writeString(operator.getEncodingTable());
        }
        
        System.out.println("A tömörített fájl útvonala: " + compressedFile.getAbsolutePath());
        System.out.println("A kódolási táblázat útvonala " + table.getAbsolutePath());
        System.out.println("A fájl táblázat nélkül nem lesz kinyerhető!");
        
        double idealRatio = Math.round(operator.getCompressionRatio() * 100) \/ (double) 100;\/\/ideális arány
        double realRatio = Math.round((double) inputFile.length() 
        \t\t\/ ((double) compressedFile.length() + (double) table.length()) * 100) \/ (double)100;\/\/valós arány
        
        System.out.println("Az ideális tömörítési arány: " + idealRatio);
        System.out.println("A kódolási táblázat figyelembevételével a tömörítési arány: " + realRatio);
    }

    public static void extract(String filePath, String tablePath) throws FileNotFoundException, IOException {
        HuffmanOperator operator = new HuffmanOperator();
        File compressedFile = new File(filePath),
        \t tableFile = new File(tablePath),
        \t extractedFile = new File(filePath + ".xtr");
        String compressed = "";
        String[] encodingArray = new String[ENCODING_TABLE_SIZE];
        \/\/tömörített fájl beolvasása
        \/\/!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ellenőrizze itt:
        try (FileInputHelper fi = new FileInputHelper(compressedFile)) {
        \tbyte b;
        \twhile (true) {
        \t\tb = fi.readByte();\/\/a metódus EOFException-t ad vissza
        \t\tcompressed += String.format("%8s", Integer.toBinaryString(b & 0xff)).replace(" ", "0");
        \t}
        } catch (EOFException e) {
        \t\n        }
        
        \/\/--------------------
        
        \/\/kódolási táblázat beolvasása:
        try (FileInputHelper fi = new FileInputHelper(tableFile)) {
        \tfi.readLine();\/\/a első üres sor átugrása
        \tencodingArray[(byte)'n'] = fi.readLine();\/\/a 'n' kód beolvasása
        \twhile (true) {
        \t\tString s = fi.readLine();
        \t\tif (s == null)
        \t\t\tthrow new EOFException();
        \t\tencodingArray[(byte)s.charAt(0)] = s.substring(1, s.length());        \t\t\n        \t}
        } catch (EOFException ignore) {}
        
        extractedFile.createNewFile();
        \/\/kibővítés:
\t\ttry (FileOutputHelper fo = new FileOutputHelper(extractedFile)) {
\t\t\tfo.writeString(operator.extract(compressed, encodingArray));
\t\t}
\t\t
\t\tSystem.out.println("A kibővített fájl útvonala " + extractedFile.getAbsolutePath());
    }
}

A readme.txt file with instructions must be written by you 🙂

Zárás

I guess that's all I wanted to say. If you have anything to say about my incompetence in code improvements, algorithms, or any optimization, feel free to write. If I missed something in my explanation, let me know as well. I would be glad to hear from you in the comments!

P.S.

Yes, yes, I'm still here, because I haven't forgotten about the coefficient. For the string s1, the encoding table is 48 bytes — much larger than the original file, and don’t forget about the added zeros (the number of added zeros is 7) => the compression ratio will be less than one: 176/(65 + 48*8 + 7)=0.38. If you noticed this too, then good on you. Yes, this implementation will be extremely inefficient for small files. But what happens with large files? The file sizes greatly exceed the size of the encoding table. It is here that the algorithm works as intended! For example, for Faust's monologue the compressor gives a real (non-idealized) ratio of 1.46 — almost one and a half times! And yes, it was assumed that the file would be in English.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster