кіру
Бұл мақалада мен әйгілі Хаффман алгоритмі, сондай-ақ оның деректерді қысудағы қолданылуы туралы айтатын боламын.
Нәтижесінде біз қарапайым архиваторды жазамыз. Бұл қазірдің өзінде талқыланды , бірақ практикалық іске асырусыз. Ағымдағы жазбаның теориялық материалы мектептегі информатика сабақтарынан және Роберт Лафореттің «Java тіліндегі деректер құрылымдары мен алгоритмдері» кітабынан алынды. Сонымен, бәрі кесілді!
Бірнеше ой
Кәдімгі мәтіндік файлда бір таңба 8 битпен (ASCII кодтау) немесе 16 (Юникод кодтау) кодталған. Әрі қарай ASCII кодтауын қарастырамыз. Мысалы, s1 = «SUSIE ОСЫ ОҢАЙ ДЕЙДІ» жолын алыңыз. Жолда жалпы 22 таңба бар, бос орындар мен жаңа жол таңбасы - 'n' қоса алғанда. Бұл жолды қамтитын файлдың салмағы 22*8 = 176 бит болады. Бірден сұрақ туындайды: 8 таңбаны кодтау үшін барлық 1 битті пайдалану ұтымды ма? Біз барлық ASCII таңбаларын пайдаланбаймыз. Тіпті егер олар солай болса да, ең көп таралған әріп – S – мүмкіндігінше қысқа код, ал сирек кездесетін әріп – T (немесе U немесе «n») үшін ұзағырақ код берілгені ұтымдырақ болар еді. Хаффман алгоритмі мыналардан тұрады: файлдың ең аз салмағы болатын оңтайлы кодтау опциясын табу қажет. Әртүрлі таңбалар үшін код ұзындығының әртүрлі болуы әбден қалыпты жағдай – алгоритм осыған негізделген.
Кодтау
Неліктен 'S' таңбасына код бермеске, мысалы, ұзындығы 1 бит: 0 немесе 1. Ол 1 болсын. Содан кейін екінші жиі кездесетін таңба - ' ' (бос орын) - 0 береді. Хабарламаны декодтауды бастадыңыз деп елестетіңіз - кодталған s1 жолы - және сіз код 1-ден басталатынын көресіз. Сонымен, сіз не істейсіз: бұл S таңбасы ма, әлде басқа таңба ма, мысалы, А? Сондықтан маңызды ереже туындайды:
Ешбір код басқаның префиксі болмауы керек
Бұл ереже алгоритмде негізгі болып табылады. Сондықтан кодты құру жиілік кестесінен басталады, ол әрбір символдың жиілігін (қайталанулар санын) көрсетеді:
Ең көп кездесетін таңбалар ең аз кодталуы керек мүмкін бит саны. Мен ықтимал код кестелерінің біріне мысал келтіремін:
Осылайша кодталған хабарлама келесідей болады:
10 01111 10 110 1111 00 10 010 1110 10 00 110 0110 00 110 10 00 1111 010 10 1110 01110 Мен әр таңбаның кодын бос орынмен бөлдім. Бұл шынымен қысылған файлда болмайды!
Сұрақ туындайды: бұл жас жігіт кодтар кестесін жасау үшін кодты қалай ойлап тапты? Бұл төменде талқыланады.
Хаффман ағашын салу
Бұл жерде екілік іздеу ағаштары көмекке келеді. Уайымдамаңыз, мұнда іздеу, кірістіру немесе жою әдістері қажет емес. Міне, java-дағы ағаш құрылымы:
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;
}
...
}
Бұл толық код емес, толық код төменде болады.
Міне, ағашты құру алгоритмі:
- Хабарламадағы әрбір таңба үшін Түйін нысанын жасаңыз (s1 жолы). Біздің жағдайда 9 түйін болады (Түйін нысандары). Әрбір түйін екі деректер өрісінен тұрады: символ және жиілік
- Әрбір Түйін үшін Tree нысанын (BinaryTree) жасаңыз. Түйін ағаштың тамырына айналады.
- Бұл ағаштарды басым кезекке қойыңыз. Жиілік неғұрлым төмен болса, басымдық соғұрлым жоғары болады. Осылайша, алу кезінде ең төменгі жиіліктегі дерво әрқашан таңдалады.
Әрі қарай циклдік түрде келесі әрекеттерді орындау керек:
- Басымдық кезектен екі ағашты алып тастаңыз және оларды жаңа түйіннің (әріпсіз жаңадан жасалған түйін) еншілестері етіңіз. Жаңа түйіннің жиілігі екі ұрпақ ағашының жиіліктерінің қосындысына тең.
- Осы түйін үшін осы түйінде түбірі бар ағаш жасаңыз. Бұл ағашты қайтадан басым кезекке енгізіңіз. (Ағаштың жаңа жиілігі болғандықтан, ол кезектегі жаңа жерде пайда болуы мүмкін)
- Кезекте бір ғана ағаш қалғанша 1 және 2 қадамдарды жалғастырыңыз - Хаффман ағашы
Бұл алгоритмді s1 жолында қарастырыңыз:

Мұндағы «lf» (сызық беру) таңбасы жаңа жолды білдіреді, «sp» (бос орын) - бос орын.
Ал келесі не?
Бізде Хаффман ағашы бар. ЖАРАЙДЫ МА. Ал онымен не істеу керек? Олар оны тіпті тегін қабылдамайды, содан кейін ағаштың тамырынан жапырақтарына дейін барлық мүмкін жолдарды іздеу керек. Егер сол жақ балаға апаратын болса, 0 жиегін, оң жаққа апаратын болса, 1-ді белгілеуге келісейік. Дәлірек айтқанда, бұл белгілерде таңбаның коды ағаштың тамырынан осы таңбаны қамтитын жапыраққа дейінгі жол болып табылады.

Кодтар кестесі осылай шықты. Назар аударыңыз, егер біз осы кестені қарастырсақ, әрбір таңбаның «салмағы» туралы қорытынды жасауға болады - бұл оның кодының ұзындығы. Содан кейін сығылған пішінде түпнұсқа файлдың салмағы болады: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 бит . Алдымен оның салмағы 176 бит болды. Демек, біз оны 176/65 = 2.7 есеге азайттық! Бірақ бұл утопия. Мұндай коэффициентті алу екіталай. Неліктен? Бұл сәл кейінірек талқыланады.
Декодтау
Мүмкін, қалған ең қарапайым нәрсе - декодтау. Менің ойымша, сіздердің көпшілігіңіз қысылған файлды оның қалай кодталғаны туралы анықтамасыз жай ғана жасай алмайтынымызды түсіндіңіз - біз оны декодтау мүмкін емеспіз! Иә, иә, мен үшін мұны түсіну қиын болды, бірақ мен сығу кестесі бар table.txt мәтіндік файлын жасауым керек:
01110
00
A010
E1111
I110
S10
T0110
U01111
Y1110
«Символ» «таңба коды» пішініндегі кесте жазбасы. Неліктен 01110 таңбасыз? Шын мәнінде, бұл таңбамен, мен файлға шығару кезінде қолданатын java құралдары, жаңа жол таңбасы - 'n' - жаңа жолға түрлендіріледі (ол қаншалықты ақымақ естілсе де). Сондықтан жоғарғы жағындағы бос жол 01110 кодының таңбасы болып табылады. 00 коды үшін таңба жолдың басындағы бос орын болып табылады. Мен бірден айтайын, біздің Хан коэффициенті үшін кестені сақтаудың бұл әдісі ең қисынсыз деп айтуға болады. Бірақ оны түсіну және жүзеге асыру оңай. Оңтайландыруға қатысты түсініктемелерде сіздің ұсыныстарыңызды естуге қуаныштымын.
Бұл кестенің болуы декодтауды өте оңай етеді. Кодтауды жасау кезінде қандай ережені ұстанғанымызды еске түсірейік:
Ешбір код басқаның префиксі болмауы керек
Бұл жерде ол жеңілдетуші рөл атқарады. Біз дәйекті түрде биттік оқимыз және оқылатын биттерден тұратын нәтиже d жолы символдық таңбаға сәйкес кодтауға сәйкес келе салысымен, символ символының (және тек ол!) кодталғанын бірден білеміз. Әрі қарай, кодты шешу жолына (декодталған хабарлама бар жол) таңбаны жазамыз, d жолын қалпына келтіреміз, содан кейін кодталған файлды оқимыз.
Реализация
Менің кодымды қорлап, архиваторды жазатын кез келді. Оны компрессор деп атаймыз.
Қайтадан бастаңыз. Ең алдымен біз Node класын жазамыз:
public class Node {
private int frequence;//частота
private char letter;//буква
private Node leftChild;//левый потомок
private Node rightChild;//правый потомок
public Node(char letter, int frequence) { //собственно, конструктор
this.letter = letter;
this.frequence = frequence;
}
public Node() {}//перегрузка конструтора для безымянных узлов(см. выше в разделе о построении дерева Хаффмана)
public void addChild(Node newNode) {//добавить потомка
if (leftChild == null)//если левый пустой=> правый тоже=> добавляем в левый
leftChild = newNode;
else {
if (leftChild.getFrequence() <= newNode.getFrequence()) //в общем, левым потомком
rightChild = newNode;//станет тот, у кого меньше частота
else {
rightChild = leftChild;
leftChild = newNode;
}
}
frequence += newNode.getFrequence();//итоговая частота
}
public Node getLeftChild() {
return leftChild;
}
public Node getRightChild() {
return rightChild;
}
public int getFrequence() {
return frequence;
}
public char getLetter() {
return letter;
}
public boolean isLeaf() {//проверка на лист
return leftChild == null && rightChild == null;
}
}
Енді ағаш:
class BinaryTree {
private Node root;
public BinaryTree() {
root = new Node();
}
public BinaryTree(Node root) {
this.root = root;
}
public int getFrequence() {
return root.getFrequence();
}
public Node getRoot() {
return root;
}
}
Басымдық кезек:
import java.util.ArrayList;//да-да, очередь будет на базе списка
class PriorityQueue {
private ArrayList<BinaryTree> data;//список очереди
private int nElems;//кол-во элементов в очереди
public PriorityQueue() {
data = new ArrayList<BinaryTree>();
nElems = 0;
}
public void insert(BinaryTree newTree) {//вставка
if (nElems == 0)
data.add(newTree);
else {
for (int i = 0; i < nElems; i++) {
if (data.get(i).getFrequence() > newTree.getFrequence()) {//если частота вставляемого дерева меньше
data.add(i, newTree);//чем част. текущего, то cдвигаем все деревья на позициях справа на 1 ячейку
break;//затем ставим новое дерево на позицию текущего
}
if (i == nElems - 1)
data.add(newTree);
}
}
nElems++;//увеличиваем кол-во элементов на 1
}
public BinaryTree remove() {//удаление из очереди
BinaryTree tmp = data.get(0);//копируем удаляемый элемент
data.remove(0);//собственно, удаляем
nElems--;//уменьшаем кол-во элементов на 1
return tmp;//возвращаем удаленный элемент(элемент с наименьшей частотой)
}
}
Хаффман ағашын жасайтын сынып:
public class HuffmanTree {
private final byte ENCODING_TABLE_SIZE = 127;//длина кодировочной таблицы
private String myString;//сообщение
private BinaryTree huffmanTree;//дерево Хаффмана
private int[] freqArray;//частотная таблица
private String[] encodingArray;//кодировочная таблица
//----------------constructor----------------------
public HuffmanTree(String newString) {
myString = newString;
freqArray = new int[ENCODING_TABLE_SIZE];
fillFrequenceArray();
huffmanTree = getHuffmanTree();
encodingArray = new String[ENCODING_TABLE_SIZE];
fillEncodingArray(huffmanTree.getRoot(), "", "");
}
//--------------------frequence array------------------------
private void fillFrequenceArray() {
for (int i = 0; i < myString.length(); i++) {
freqArray[(int)myString.charAt(i)]++;
}
}
public int[] getFrequenceArray() {
return freqArray;
}
//------------------------huffman tree creation------------------
private BinaryTree getHuffmanTree() {
PriorityQueue pq = new PriorityQueue();
//алгоритм описан выше
for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
if (freqArray[i] != 0) {//если символ существует в строке
Node newNode = new Node((char) i, freqArray[i]);//то создать для него Node
BinaryTree newTree = new BinaryTree(newNode);//а для Node создать BinaryTree
pq.insert(newTree);//вставить в очередь
}
}
while (true) {
BinaryTree tree1 = pq.remove();//извлечь из очереди первое дерево.
try {
BinaryTree tree2 = pq.remove();//извлечь из очереди второе дерево
Node newNode = new Node();//создать новый Node
newNode.addChild(tree1.getRoot());//сделать его потомками два извлеченных дерева
newNode.addChild(tree2.getRoot());
pq.insert(new BinaryTree(newNode);
} catch (IndexOutOfBoundsException e) {//осталось одно дерево в очереди
return tree1;
}
}
}
public BinaryTree getTree() {
return huffmanTree;
}
//-------------------encoding array------------------
void fillEncodingArray(Node node, String codeBefore, String direction) {//заполнить кодировочную таблицу
if (node.isLeaf()) {
encodingArray[(int)node.getLetter()] = codeBefore + direction;
} else {
fillEncodingArray(node.getLeftChild(), codeBefore + direction, "0");
fillEncodingArray(node.getRightChild(), codeBefore + direction, "1");
}
}
String[] getEncodingArray() {
return encodingArray;
}
public void displayEncodingArray() {//для отладки
fillEncodingArray(huffmanTree.getRoot(), "", "");
System.out.println("======================Encoding table====================");
for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
if (freqArray[i] != 0) {
System.out.print((char)i + " ");
System.out.println(encodingArray[i]);
}
}
System.out.println("========================================================");
}
//-----------------------------------------------------
String getOriginalString() {
return myString;
}
}
Кодталатын/декодталатын сынып:
public class HuffmanOperator {
private final byte ENCODING_TABLE_SIZE = 127;//длина таблицы
private HuffmanTree mainHuffmanTree;//дерево Хаффмана (используется только для сжатия)
private String myString;//исходное сообщение
private int[] freqArray;//частотаная таблица
private String[] encodingArray;//кодировочная таблица
private double ratio;//коэффициент сжатия
public HuffmanOperator(HuffmanTree MainHuffmanTree) {//for compress
this.mainHuffmanTree = MainHuffmanTree;
myString = mainHuffmanTree.getOriginalString();
encodingArray = mainHuffmanTree.getEncodingArray();
freqArray = mainHuffmanTree.getFrequenceArray();
}
public HuffmanOperator() {}//for extract;
//---------------------------------------compression-----------------------------------------------------------
private String getCompressedString() {
String compressed = "";
String intermidiate = "";//промежуточная строка(без добавочных нулей)
//System.out.println("=============================Compression=======================");
//displayEncodingArray();
for (int i = 0; i < myString.length(); i++) {
intermidiate += encodingArray[myString.charAt(i)];
}
//Мы не можем писать бит в файл. Поэтому нужно сделать длину сообщения кратной 8=>
//нужно добавить нули в конец(можно 1, нет разницы)
byte counter = 0;//количество добавленных в конец нулей (байта в полне хватит: 0<=counter<8<127)
for (int length = intermidiate.length(), delta = 8 - length % 8;
counter < delta ; counter++) {//delta - количество добавленных нулей
intermidiate += "0";
}
//склеить кол-во добавочных нулей в бинарном предаствлении и промежуточную строку
compressed = String.format("%8s", Integer.toBinaryString(counter & 0xff)).replace(" ", "0") + intermidiate;
//идеализированный коэффициент
setCompressionRatio();
//System.out.println("===============================================================");
return compressed;
}
private void setCompressionRatio() {//посчитать идеализированный коэффициент
double sumA = 0, sumB = 0;//A-the original sum
for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
if (freqArray[i] != 0) {
sumA += 8 * freqArray[i];
sumB += encodingArray[i].length() * freqArray[i];
}
}
ratio = sumA / sumB;
}
public byte[] getBytedMsg() {//final compression
StringBuilder compressedString = new StringBuilder(getCompressedString());
byte[] compressedBytes = new byte[compressedString.length() / 8];
for (int i = 0; i < compressedBytes.length; i++) {
compressedBytes[i] = (byte) Integer.parseInt(compressedString.substring(i * 8, (i + 1) * 8), 2);
}
return compressedBytes;
}
//---------------------------------------end of compression----------------------------------------------------------------
//------------------------------------------------------------extract-----------------------------------------------------
public String extract(String compressed, String[] newEncodingArray) {
String decompressed = "";
String current = "";
String delta = "";
encodingArray = newEncodingArray;
//displayEncodingArray();
//получить кол-во вставленных нулей
for (int i = 0; i < 8; i++)
delta += compressed.charAt(i);
int ADDED_ZEROES = Integer.parseInt(delta, 2);
for (int i = 8, l = compressed.length() - ADDED_ZEROES; i < l; i++) {
//i = 8, т.к. первым байтом у нас идет кол-во вставленных нулей
current += compressed.charAt(i);
for (int j = 0; j < ENCODING_TABLE_SIZE; j++) {
if (current.equals(encodingArray[j])) {//если совпало
decompressed += (char)j;//то добавляем элемент
current = "";//и обнуляем текущую строку
}
}
}
return decompressed;
}
public String getEncodingTable() {
String enc = "";
for (int i = 0; i < encodingArray.length; i++) {
if (freqArray[i] != 0)
enc += (char)i + encodingArray[i] + 'n';
}
return enc;
}
public double getCompressionRatio() {
return ratio;
}
public void displayEncodingArray() {//для отладки
System.out.println("======================Encoding table====================");
for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
//if (freqArray[i] != 0) {
System.out.print((char)i + " ");
System.out.println(encodingArray[i]);
//}
}
System.out.println("========================================================");
}
}
Файлға жазуды жеңілдететін класс:
import java.io.File;
import java.io.PrintWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Closeable;
public class FileOutputHelper implements Closeable {
private File outputFile;
private FileOutputStream fileOutputStream;
public FileOutputHelper(File file) throws FileNotFoundException {
outputFile = file;
fileOutputStream = new FileOutputStream(outputFile);
}
public void writeByte(byte msg) throws IOException {
fileOutputStream.write(msg);
}
public void writeBytes(byte[] msg) throws IOException {
fileOutputStream.write(msg);
}
public void writeString(String msg) {
try (PrintWriter pw = new PrintWriter(outputFile)) {
pw.write(msg);
} catch (FileNotFoundException e) {
System.out.println("Неверный путь, или такого файла не существует!");
}
}
@Override
public void close() throws IOException {
fileOutputStream.close();
}
public void finalize() throws IOException {
close();
}
}
Файлдан оқуды жеңілдететін сынып:
import java.io.FileInputStream;
import java.io.EOFException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
public class FileInputHelper implements Closeable {
private FileInputStream fileInputStream;
private BufferedReader fileBufferedReader;
public FileInputHelper(File file) throws IOException {
fileInputStream = new FileInputStream(file);
fileBufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
}
public byte readByte() throws IOException {
int cur = fileInputStream.read();
if (cur == -1)//если закончился файл
throw new EOFException();
return (byte)cur;
}
public String readLine() throws IOException {
return fileBufferedReader.readLine();
}
@Override
public void close() throws IOException{
fileInputStream.close();
}
}
Ал, және негізгі сынып:
import java.io.File;
import java.nio.charset.MalformedInputException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import java.util.List;
import java.io.EOFException;
public class Main {
private static final byte ENCODING_TABLE_SIZE = 127;
public static void main(String[] args) throws IOException {
try {//указываем инструкцию с помощью аргументов командной строки
if (args[0].equals("--compress") || args[0].equals("-c"))
compress(args[1]);
else if ((args[0].equals("--extract") || args[0].equals("-x"))
&& (args[2].equals("--table") || args[2].equals("-t"))) {
extract(args[1], args[3]);
}
else
throw new IllegalArgumentException();
} catch (ArrayIndexOutOfBoundsException | IllegalArgumentException e) {
System.out.println("Неверный формат ввода аргументов ");
System.out.println("Читайте Readme.txt");
e.printStackTrace();
}
}
public static void compress(String stringPath) throws IOException {
List<String> stringList;
File inputFile = new File(stringPath);
String s = "";
File compressedFile, table;
try {
stringList = Files.readAllLines(Paths.get(inputFile.getAbsolutePath()));
} catch (NoSuchFileException e) {
System.out.println("Неверный путь, или такого файла не существует!");
return;
} catch (MalformedInputException e) {
System.out.println("Текущая кодировка файла не поддерживается");
return;
}
for (String item : stringList) {
s += item;
s += 'n';
}
HuffmanOperator operator = new HuffmanOperator(new HuffmanTree(s));
compressedFile = new File(inputFile.getAbsolutePath() + ".cpr");
compressedFile.createNewFile();
try (FileOutputHelper fo = new FileOutputHelper(compressedFile)) {
fo.writeBytes(operator.getBytedMsg());
}
//create file with encoding table:
table = new File(inputFile.getAbsolutePath() + ".table.txt");
table.createNewFile();
try (FileOutputHelper fo = new FileOutputHelper(table)) {
fo.writeString(operator.getEncodingTable());
}
System.out.println("Путь к сжатому файлу: " + compressedFile.getAbsolutePath());
System.out.println("Путь к кодировочной таблице " + table.getAbsolutePath());
System.out.println("Без таблицы файл будет невозможно извлечь!");
double idealRatio = Math.round(operator.getCompressionRatio() * 100) / (double) 100;//идеализированный коэффициент
double realRatio = Math.round((double) inputFile.length()
/ ((double) compressedFile.length() + (double) table.length()) * 100) / (double)100;//настоящий коэффициент
System.out.println("Идеализированный коэффициент сжатия равен " + idealRatio);
System.out.println("Коэффициент сжатия с учетом кодировочной таблицы " + realRatio);
}
public static void extract(String filePath, String tablePath) throws FileNotFoundException, IOException {
HuffmanOperator operator = new HuffmanOperator();
File compressedFile = new File(filePath),
tableFile = new File(tablePath),
extractedFile = new File(filePath + ".xtr");
String compressed = "";
String[] encodingArray = new String[ENCODING_TABLE_SIZE];
//read compressed file
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!check here:
try (FileInputHelper fi = new FileInputHelper(compressedFile)) {
byte b;
while (true) {
b = fi.readByte();//method returns EOFException
compressed += String.format("%8s", Integer.toBinaryString(b & 0xff)).replace(" ", "0");
}
} catch (EOFException e) {
}
//--------------------
//read encoding table:
try (FileInputHelper fi = new FileInputHelper(tableFile)) {
fi.readLine();//skip first empty string
encodingArray[(byte)'n'] = fi.readLine();//read code for 'n'
while (true) {
String s = fi.readLine();
if (s == null)
throw new EOFException();
encodingArray[(byte)s.charAt(0)] = s.substring(1, s.length());
}
} catch (EOFException ignore) {}
extractedFile.createNewFile();
//extract:
try (FileOutputHelper fo = new FileOutputHelper(extractedFile)) {
fo.writeString(operator.extract(compressed, encodingArray));
}
System.out.println("Путь к распакованному файлу " + extractedFile.getAbsolutePath());
}
}
Readme.txt файлын өзіңіз жазуыңыз керек :)
қорытынды
Менің айтқым келгені осы болды деп ойлаймын. Егер сізде кодты, алгоритмді немесе жалпы оңтайландыруды жақсартудағы менің дәрменсіздігім туралы айтатын нәрсеңіз болса, жазыңыз. Егер мен ештеңе түсіндірмеген болсам, сіз де жазыңыз. Түсініктемелерде сізден жауап алғым келеді!
PS
Иә, иә, мен әлі де осындамын, өйткені мен коэффициентті ұмытпадым. s1 жолы үшін кодтау кестесінің салмағы 48 байт - бастапқы файлдан әлдеқайда үлкен және біз қосымша нөлдерді ұмытпадық (қосылған нөлдер саны 7) => қысу коэффициенті біреуден аз болады: 176/ (65 + 48*8 + 7) = 0.38. Егер сіз де мұны байқасаңыз, бұл тек сіздің бетіңіз ғана емес. Иә, бұл іске асыру шағын файлдар үшін өте тиімсіз болады. Бірақ үлкен файлдармен не болады? Файл өлшемдері кодтау кестесінің өлшемінен әлдеқайда үлкен. Бұл жерде алгоритм қажетінше жұмыс істейді! Мысалы, үшін Мұрағаттаушы нақты (идеализацияланбаған) 1.46 коэффициентін шығарады - бір жарым есе дерлік! Иә, файл ағылшын тілінде болуы керек еді.
Ақпарат көзі: www.habr.com
