Tietojen pakkaus Huffman-algoritmilla

Merkintä

Tässä artikkelissa puhun tunnetusta Huffman-algoritmista sekä sen soveltamisesta tietojen pakkaamiseen.

Tämän seurauksena kirjoitamme yksinkertaisen arkistaattorin. Tämä on jo ollut artikkeli Habresta, mutta ilman käytännön toteutusta. Tämän postauksen teoreettinen materiaali on otettu koulun tietojenkäsittelyn tunneista ja Robert Laforetin kirjasta "Data Structures and Algorithms in Java". Joten kaikki on haitan alla!

Pientä pohdintaa

Normaalissa tekstitiedostossa yksi merkki on koodattu 8 bitillä (ASCII-koodaus) tai 16 bitillä (Unicode-koodaus). Seuraavaksi tarkastelemme ASCII-koodausta. Otetaan esimerkiksi merkkijono s1 = "SUSIE SAYS SE IS EASYn". Yhteensä rivillä on tietysti 22 merkkiä, mukaan lukien välilyönnit ja rivinvaihtomerkki - 'n'. Tämän rivin sisältävä tiedosto painaa 22*8 = 176 bittiä. Heti herää kysymys: onko järkevää käyttää kaikkia 8 bittiä yhden merkin koodaamiseen? Emme käytä kaikkia ASCII-merkkejä. Vaikka olisikin, olisi järkevämpää antaa yleisimmälle kirjaimelle - S - lyhin mahdollinen koodi, ja harvinaisimmalle kirjaimelle - T (tai U tai 'n') - antaa koodi aitommaksi. Tämä on Huffman-algoritmi: sinun on löydettävä paras koodausvaihtoehto, jossa tiedosto on vähimmäispainoinen. On aivan normaalia, että eri merkeillä on eri koodipituudet - tämä on algoritmin perusta.

Koodaus

Mikset anna S-merkille koodia, esimerkiksi 1 bitin pituinen: 0 tai 1. Olkoon se 1. Sitten toiseksi eniten esiintyvä merkki - ' ' (välilyönti) - annamme 0. Kuvittele, aloit purkaa viestisi - koodattu merkkijono s1 - ja näet, että koodi alkaa 1:llä. Mitä tehdä: onko se merkki S vai jokin muu merkki, kuten A? Siksi syntyy tärkeä sääntö:

Mikään koodi ei saa olla toisen etuliite

Tämä sääntö on avain algoritmiin. Siksi koodin luominen alkaa taajuustaulukolla, joka osoittaa kunkin symbolin taajuuden (esiintymien lukumäärän):

Tietojen pakkaus Huffman-algoritmilla Eniten esiintyviä merkkejä tulee koodata vähiten esiintymillä mahdollinen bittien määrä. Annan esimerkin yhdestä mahdollisesta kooditaulukosta:

Tietojen pakkaus Huffman-algoritmilla Joten koodattu viesti näyttää tältä:

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

Erotin jokaisen merkin koodin välilyönnillä. Tätä ei todellakaan tapahdu pakatussa tiedostossa!
Herää kysymys: kuinka tämä alokas keksi koodin kooditaulukon luomiseen? Tätä käsitellään alla.

Huffman-puun rakentaminen

Tässä binaariset etsintäpuut tulevat apuun. Älä huoli, et tarvitse haku-, lisäys- ja poistomenetelmiä täällä. Tässä on puurakenne javassa:

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

Tämä ei ole täydellinen koodi, vaan koko koodi on alla.

Tässä on algoritmi puun rakentamiseen:

  1. Luo solmuobjekti jokaiselle viestin merkille (rivi s1). Meidän tapauksessamme solmuja (solmuobjekteja) on 9. Jokainen solmu koostuu kahdesta tietokentästä: symbolista ja taajuudesta
  2. Luo puuobjekti (BinaryTree) jokaiselle solmusolmulle. Solmusta tulee puun juuri.
  3. Lisää nämä puut prioriteettijonoon. Mitä pienempi taajuus, sitä korkeampi prioriteetti. Näin ollen poimittaessa valitaan aina alhaisimman taajuuden puu.

Seuraavaksi sinun on suoritettava syklisesti seuraavat:

  1. Hae kaksi puuta prioriteettijonosta ja tee niistä uuden solmun (äskettäin luodun solmun ilman kirjainta) lapsia. Uuden solmun taajuus on yhtä suuri kuin kahden jälkeläisen puun taajuuksien summa.
  2. Luo tälle solmulle puu, jonka juuret ovat tähän solmuun. Lisää tämä puu takaisin prioriteettijonoon. (Koska puulla on uusi taajuus, se todennäköisesti joutuu uuteen paikkaan jonossa)
  3. Jatka vaiheita 1 ja 2, kunnes jonoon jää yksi puu - Huffman-puu

Harkitse tätä algoritmia rivillä s1:

Tietojen pakkaus Huffman-algoritmilla

Tässä symboli "lf" (rivinsiirto) tarkoittaa uutta riviä, "sp" (välilyönti) on välilyönti.

Ja mitä seuraavaksi?

Meillä on Huffman-puu. OK. Ja mitä tehdä sen kanssa? He eivät ota sitä ilmaiseksi, ja sitten sinun on jäljitettävä kaikki mahdolliset polut juuresta puun lehtiin. Suostumme merkitsemään reunan 0, jos se johtaa vasempaan lapseen ja 1, jos se johtaa oikeaan. Tarkkaan ottaen näissä merkinnöissä merkkikoodi on polku puun juuresta saman merkin sisältävään lehtiin.

Tietojen pakkaus Huffman-algoritmilla

Siten kooditaulukko osoittautui. Huomaa, että jos tarkastelemme tätä taulukkoa, voimme päätellä kunkin merkin "painosta" - tämä on sen koodin pituus. Sitten pakattuna lähdetiedoston paino on: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bittiä . Aluksi se painoi 176 bittiä. Siksi pienensimme sitä jopa 176/65 = 2.7 kertaa! Mutta tämä on utopiaa. Tällaista suhdetta ei todennäköisesti saada. Miksi? Tästä keskustellaan hieman myöhemmin.

Dekoodaus

No, ehkä yksinkertaisin jäljellä oleva asia on dekoodaus. Luulen, että monet teistä ovat ajatelleet, että on mahdotonta luoda yksinkertaisesti pakattua tiedostoa ilman vihjeitä sen koodauksesta - emme voi purkaa sitä! Kyllä, kyllä, minun oli vaikea ymmärtää tätä, mutta minun on luotava tekstitiedosto table.txt, jossa on pakkaustaulukko:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Taulukon merkintä muodossa 'merkki'"merkkikoodi". Miksi 01110 on ilman symbolia? Itse asiassa se on symbolilla, vain Java-työkaluilla, joita käytän tiedostoon tulostaessani, rivinvaihtomerkki - "n" - muunnetaan rivinvaihdoksi (riippumatta siitä kuinka tyhmältä se kuulostaa). Siksi yllä oleva tyhjä rivi on koodin 01110 merkki. Koodissa 00 merkki on välilyönti rivin alussa. Minun on sanottava heti, että tämä taulukon tallennusmenetelmä voi väittää olevansa irrationaalisin khan-kertoimemme kannalta. Mutta se on helppo ymmärtää ja toteuttaa. Kuulen mielelläni suosituksiasi optimointia koskevissa kommenteissa.

Tämän taulukon avulla se on erittäin helppo purkaa. Muistakaamme, mitä sääntöä ohjasimme koodauksen luomisessa:

Mikään koodi ei saa olla toisen etuliite

Tässä se on edistävä rooli. Luemme bitti kerrallaan peräkkäin, ja heti kun tuloksena oleva lukubiteistä koostuva merkkijono d osuu merkin merkkiä vastaavaan koodaukseen, tiedämme heti, että merkkimerkki (ja vain se!) on koodattu. Seuraavaksi kirjoitamme merkin dekoodausjonoon (merkkijono, joka sisältää dekoodatun viestin), nollaamme d-merkkijonon ja luemme koodattua tiedostoa edelleen.

Реализация

On aika nöyryyttää koodiani kirjoittamalla arkistointi. Kutsutaan sitä kompressoriksi.

Aloittaa alusta. Ensinnäkin kirjoitamme Node-luokan:

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

Nyt puu:

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

Prioriteettijono:

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

Luokka, joka luo Huffman-puun:

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

Luokka, joka koodaa/dekoodaa:

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

Luokka, joka helpottaa tiedostoon kirjoittamista:

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

Luokka, joka helpottaa tiedostosta lukemista:

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

No, ja pääluokka:

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

Sinun on kirjoitettava tiedosto readme.txt-ohjeilla itse 🙂

Johtopäätös

Luulen, että se on kaikki, mitä halusin sanoa. Jos sinulla on jotain sanottavaa minun epäpätevyydestäni parantaa koodia, algoritmia, yleensä mitään optimointia, kirjoita rohkeasti. Jos en ole selittänyt jotain, kirjoita myös. Haluaisin kuulla sinusta kommenteissa!

PS.

Kyllä, kyllä, olen edelleen täällä, koska en unohtanut kerrointa. Merkkijonolle s1 koodaustaulukko painaa 48 tavua - paljon enemmän kuin alkuperäinen tiedosto, eivätkä he unohtaneet ylimääräisiä nollia (lisättyjen nollien määrä on 7) => pakkaussuhde on pienempi kuin yksi: 176 /(65 + 48*8 + 7) = 0.38. Jos olet myös huomannut tämän, et ole valmis. Kyllä, tämä toteutus on erittäin tehoton pienille tiedostoille. Mutta mitä tapahtuu suurille tiedostoille? Tiedostokoot ovat paljon suurempia kuin koodaustaulukon koko. Tässä algoritmi toimii niin kuin pitääkin! Esimerkiksi varten Faustin monologi arkistaattori antaa todellisen (ei idealisoidun) kertoimen, joka on 1.46 - melkein puolitoista kertaa! Ja kyllä, tiedoston piti olla englanniksi.

Lähde: will.com

Lisää kommentti