Adattömörítés Huffman algoritmussal

Belépés

Ebben a cikkben a híres Huffman algoritmusról, valamint annak adattömörítésben való alkalmazásáról fogok beszélni.

Ennek eredményeként egy egyszerű archiválót fogunk írni. Erről már volt szó cikk Habréról, de gyakorlati megvalósítás nélkül. A mostani bejegyzés elméleti anyaga az iskolai informatika leckékből és Robert Laforet „Adatstruktúrák és algoritmusok Java-ban” című könyvéből származik. Szóval minden el van vágva!

Egy kis elmélkedés

Egy normál szövegfájlban egy karakter 8 bittel (ASCII kódolás) vagy 16 bittel (Unicode kódolás) van kódolva. Ezután az ASCII kódolást fogjuk megvizsgálni. Vegyük például az s1 = „SUSIE SAYS IT IS EASYn” sort. A sorban természetesen összesen 22 karakter található, beleértve a szóközöket és az új sorkaraktert - 'n'. Az ezt a sort tartalmazó fájl súlya 22*8 = 176 bit. Rögtön felmerül a kérdés: racionális-e mind a 8 bitet felhasználni 1 karakter kódolására? Nem használunk minden ASCII karaktert. Még ha meg is tennék, ésszerűbb lenne, ha a leggyakoribb S betű a lehető legrövidebb kódot kapná, a legritkább betű pedig - T (vagy U, vagy 'n') - hosszabb kódot kapna. Ebből áll a Huffman-algoritmus: meg kell találni az optimális kódolási lehetőséget, amelyben a fájl minimális súlyú lesz. Teljesen normális, hogy a kód hossza különbözik a különböző szimbólumoknál – erre épül az algoritmus.

Kódolás

Miért ne adjunk az 'S' karakternek például 1 bites kódot: 0 vagy 1. Legyen 1. Ezután a második leggyakoribb karakter - ' ' (szóköz) - adjon 0-t. Képzelje el, hogy elkezdte dekódolni az üzenetet - a kódolt sztring s1 - és látod, hogy a kód 1-gyel kezdődik. Tehát mit csinálsz: ez az S karakter, vagy valami más karakter, például A? Ezért egy fontos szabály merül fel:

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

Ez a szabály kulcsfontosságú az algoritmusban. Ezért a kód létrehozása egy gyakorisági táblázattal kezdődik, amely jelzi az egyes szimbólumok gyakoriságát (előfordulások számát):

Adattömörítés Huffman algoritmussal A legtöbbször előforduló karaktereket kell a legkevesebbet kódolni lehetséges bitek száma. Mondok egy példát az egyik lehetséges kódtáblázatra:

Adattömörítés Huffman algoritmussal Tehát 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 választottam el. Ez nem fog megtörténni egy valóban tömörített fájlban!
Felmerül a kérdés: hogyan találta ki ez a fiatal srác a kódot egy kódtáblázat létrehozásához? Erről az alábbiakban lesz szó.

Huffman fa építése

Itt jönnek segítségül a bináris keresőfák. Ne aggódjon, itt nem lesz szüksége a keresési, beszúrási vagy törlési módszerekre. Íme a java fa szerkezete:

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 lent lesz.

Íme a fa felépítésének algoritmusa:

  1. Hozzon létre egy csomópont objektumot az üzenet minden karakteréhez (s1 sor). Esetünkben 9 csomópont (Node objektumok) lesz. Minden csomópont két adatmezőből áll: szimbólumból és frekvenciából
  2. Hozzon létre egy fa objektumot (BinaryTree) minden csomóponthoz. A csomópont a fa gyökerévé válik.
  3. Illessze be ezeket a fákat a prioritási sorba. Minél alacsonyabb a frekvencia, annál nagyobb a prioritás. Így kivonáskor mindig a legalacsonyabb frekvenciájú dervo kerül kiválasztásra.

Ezután a következőket kell tennie ciklikusan:

  1. Távolítson el két fát a prioritási sorból, és tegye őket az új csomópont gyermekeivé (az újonnan létrehozott csomópont betű nélkül). Az új csomópont frekvenciája megegyezik a két leszármazott fa frekvenciájának összegével.
  2. Ehhez a csomóponthoz hozzon létre egy fát a gyökérrel ebben a csomópontban. Illessze vissza ezt a fát a prioritási sorba. (Mivel a fának új frekvenciája van, nagy valószínűséggel új helyen fog megjelenni a sorban)
  3. Folytassa az 1. és 2. lépést, amíg csak egy fa marad a sorban - a Huffman-fa

Tekintsük ezt az algoritmust az s1 sorban:

Adattömörítés Huffman algoritmussal

Itt az „lf” (soremelés) szimbólum új sort jelent, az „sp” (szóköz) pedig szóközt jelent.

És mi a következő?

Van egy Huffman-fánk. RENDBEN. És mit kell vele csinálni? Még csak nem is veszik el ingyen. Aztán minden lehetséges utat követnie kell a fa gyökerétől a levelekig. Állapodjunk meg abban, hogy egy élt 0-val jelölünk, ha a bal oldali gyermekhez vezet, és 1-et, ha a jobb oldalihoz vezet. Szigorúan véve ebben a jelölésben a szimbólum kódja a fa gyökerétől az ezt a szimbólumot tartalmazó levélig vezető út.

Adattömörítés Huffman algoritmussal

Így alakult a kódtáblázat. Vegye figyelembe, hogy ha figyelembe vesszük ezt a táblázatot, akkor következtethetünk az egyes szimbólumok „súlyára” - ez a kód hossza. Ezután 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 bitet nyomott. Következésképpen 176/65 = 2.7-szeresére csökkentettük! De ez utópia. Nem valószínű, hogy ilyen együtthatót kapunk. Miért? Erről egy kicsit később lesz szó.

Dekódolás

Nos, talán a legegyszerűbb dolog maradt a dekódolás. Azt hiszem, sokan sejtitek, hogy nem tudunk egyszerűen tömörített fájlt létrehozni anélkül, hogy utalnánk a kódolásra – nem fogjuk tudni dekódolni! Igen, igen, ezt nehéz volt felismernem, de létre kell hoznom egy table.txt szöveges fájlt tömörítési táblával:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Táblázatbevitel 'szimbólum' 'karakterkód' formában. A 01110 miért nincs szimbólum nélkül? Valójában egy szimbólummal van, csak arról van szó, hogy a fájl kimeneténél használt java eszközökben az újsor karakter - 'n' - újsorrá alakul (bármilyen hülyén hangzik is). Ezért a tetején lévő üres sor a 01110 kód karaktere. 00 kód esetén a karakter egy szóköz a sor elején. Azonnal elmondom, hogy a Khan-együtthatónk szempontjából ez a táblázattárolási módszer a legirracionálisabbnak mondható. De könnyű megérteni és megvalósítani. Örömmel hallom javaslatait az optimalizálással kapcsolatos megjegyzésekben.

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

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

Ez itt segítő szerepet játszik. Bitról bitre szekvenciálisan olvasunk, és amint az olvasási bitekből álló d karakterlánc megegyezik a karakter karakterének megfelelő kódolással, azonnal tudjuk, hogy a karakterkarakter (és csak az!) kódolva volt. Ezután írunk karaktert a dekódoló sorba (a dekódolt üzenetet tartalmazó sorba), alaphelyzetbe állítjuk a d sort, majd beolvassuk a kódolt fájlt.

Реализация

Ideje megalázni a kódomat, és írni egy archiválót. Nevezzük kompresszornak.

Elölről kezdeni. Először is írjuk a Node osztályt:

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

Most a fa:

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

Elsőbbségi sor:

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;//возвращаем удаленный элемент(элемент с наименьшей частотой)
    }
}

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

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

Osztály, amely kódolja/dekódolja:

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

Egy 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("Неверный путь, или такого файла не существует!");
    	}
    }

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

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

Egy 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)//если закончился файл
    		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 {//указываем инструкцию с помощью аргументов командной строки
            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());
    }
}

A readme.txt fájlt neked kell megírnod ​​:)

Következtetés

Azt hiszem, ennyit akartam mondani. Ha van valami hozzáértésem a kód, az algoritmus vagy általában az optimalizálás terén, akkor írjon nyugodtan. Ha nem magyaráztam el valamit, kérlek írd meg. Szeretnék hallani rólad a megjegyzésekben!

PS

Igen, igen, még mindig itt vagyok, mert nem feledkeztem meg az együtthatóról. Az s1 karakterlánc esetén a kódolótábla 48 bájtot nyom - jóval nagyobb, mint a forrásfájl, és nem feledkeztünk meg a további nullákról (a hozzáadott nullák száma 7) => a tömörítési arány egynél kisebb lesz: 176/ (65 + 48*8 + 7) = 0.38. Ha ezt te is észrevetted, akkor nem csak az arcod nagyszerű. Igen, ez a megvalósítás rendkívül hatástalan lesz kis fájlok esetén. De mi történik a nagy fájlokkal? A fájlméretek sokkal nagyobbak, mint a kódolótábla mérete. Itt működik az algoritmus, ahogy kell! Például azért Faust monológja Az archiváló valós (nem idealizált) 1.46-os együtthatót állít elő - majdnem másfélszeresét! És igen, a fájlnak angolul kellett volna lennie.

Forrás: will.com

Hozzászólás