Это действительно зависит от характера двоичных данных и ограничений, которые «текст» накладывает на ваш вывод.
Прежде всего, если ваши двоичные данные не сжаты, попробуйте сжать перед кодированием. Затем можно предположить, что распределение 1/0 или отдельных байтов является более или менее случайным.
Теперь: зачем тебе текст? Как правило, это потому, что канал связи не проходит через все символы одинаково. например вам может потребоваться чистый текст ASCII, чьи печатаемые символы варьируются от 0x20-0x7E. У вас есть 95 персонажей для игры. Каждый символ может теоретически кодировать log2 (95) ~ = 6,57 бит на символ. Легко определить преобразование, которое подходит довольно близко.
Но: что если вам нужен символ-разделитель? Теперь у вас есть только 94 символа и т. Д. Поэтому выбор кодировки действительно зависит от ваших требований.
Возьмем очень глупый пример: если ваш канал передает все 256 символов без проблем и вам не нужны разделители, тогда вы можете написать тривиальное преобразование, которое достигает 100% эффективности. :-) Как это сделать, оставлено в качестве упражнения для читателя.
UTF-8 не подходит для произвольно закодированных двоичных данных. Он может передавать значения 0x01-0x7F только с 14% служебной нагрузки. Я не уверен, является ли 0x00 законным; скорее всего нет. Но все, что выше 0x80, расширяется до нескольких байтов в UTF-8. Я бы рассматривал UTF-8 как ограниченный канал, который передает 0x01-0x7F, или 126 уникальных символов. Если вам не нужны разделители, вы можете передавать 6,98 бит на символ.
Общее решение этой проблемы: предположим, что алфавит состоит из N символов, двоичные кодировки которых равны от 0 до N-1. (Если кодировки не соответствуют предполагаемым, используйте таблицу поиска для перевода между нашим промежуточным представлением 0..N-1 и тем, что вы на самом деле отправляете и получаете.)
Предположим, 95 символов в алфавите. Теперь: некоторые из этих символов будут представлять 6 бит, а некоторые будут представлять 7 бит. Если у нас есть A 6-битные символы и B 7-битные символы, то:
A + B = 95 (общее количество символов)
2A + B = 128 (общее количество 7-битных префиксов, которые можно сделать. Вы можете начать 2 префикса с 6-битного символа или один с 7-битным символом.)
Решая систему, вы получаете: A = 33, B = 62. Теперь вы строите таблицу символов:
Raw Encoded
000000 0000000
000001 0000001
...
100000 0100000
1000010 0100001
1000011 0100010
...
1111110 1011101
1111111 1011110
Для кодирования сначала сдвиньте 6 бит ввода. Если эти шесть битов больше или равны 100001, сдвиньте другой бит. Затем найдите соответствующий 7-битный выходной код, переведите его, чтобы уместить в выходном пространстве, и отправьте. Вы будете сдвигать 6 или 7 бит ввода на каждой итерации.
Чтобы декодировать, примите байт и переведите в необработанный выходной код. Если необработанный код меньше 0100001, сдвиньте соответствующие 6 битов на ваш выход. В противном случае сдвиньте соответствующие 7 бит на ваш выход. Вы будете генерировать 6-7 битов вывода на каждой итерации.
Для равномерно распределенных данных я думаю, что это оптимально. Если вы знаете, что в вашем источнике больше нулей, чем единиц, то вам может потребоваться сопоставить 7-битные коды с началом пробела, чтобы более вероятно, что вы можете использовать 7-битный код.