Самый эффективный способ борьбы с битами для сжатия в JavaScript - PullRequest
0 голосов
/ 13 февраля 2019

Я никогда не делал сжатие, но меня интересует кодировка Хаффмана .Они показывают это в виде простой демонстрационной кодировки для первых нескольких букв:

A     0
E     10
P     110
space 1110
D     11110
T     111110
L     111111

Стандартная кодировка Хаффмана, которую вы видите, в противном случае имеет другой набор кодов, но это не имеет значения для этого вопроса.Мне интересно, как наиболее эффективно манипулировать этими битами в JavaScript.Говорят, что вы должны разбираться с частями по 8, 16 или 32, но на самом деле не с чем, потому что именно так целые числа и значения хранятся в компьютерной архитектуре.Итак, насколько я понимаю, вам, вероятно, следует читать 8-битные порции ввода за раз.Я не совсем уверен, как это сделать, но я думаю, что если бы вы сделали это, это сработало бы:

var bytes = new Uint8Array(array)
var byte1 = bytes[0]
var byte2 = bytes[1]
...

Это кажется наиболее эффективным способом доступа к данным.Но я думаю об альтернативе, которую хотел бы уточнить.Вместо этого вы можете просто преобразовать входные данные в двоичную текстовую строку, то есть строку из 1 и 0, как в

var string = integerOrByteArray.toString(2)

Но, как я понял, преобразование чего-либо в строку является ударом по производительности.Так что, кажется, вам следует избегать преобразования в строки, если это возможно.

Так что, если это так, то у нас остается первый подход с Uint8Array (или Uint32Array и т. Д.).Мне интересно, как бы вы тогда разбили стоимость на составные части эффективно / в идеале.Так что, если бы у нас было это ...

010110
AEP

.... и мы сделали целочисленное действие, то мы могли бы загрузить какое-то 8-битное целое число, например, одно из следующих:

01011000
01011001
00101100
...

Таким образом, нам нужно объединить (потенциально) любые фронтальные данные, которые могут быть частью последнего 8-битного блока, а затем разделить оставшиеся символы.Мой вопрос в основном, что рекомендуемый способ сделать это.Я могу придумать способы сделать это, но все они пока кажутся довольно сложными.

Ответы [ 2 ]

0 голосов
/ 13 февраля 2019

У вас уже есть превосходный ответ для чтения битов.

Для полноты и на случай, если вы хотите также посмотреть на сжатие, вот (непроверенная) функция вывода, которая может помочь с некоторыми идеями:

let remainder = 0; 
let remainderBits = 0;  // number of bits held in remainder

function putHuffman( 
    code,       // non-negative huffman code
    nBits) {    // bit length of code

    if( remainderBits) {
        code = code * (2 ** remainderBits) + remainder;
        nBits += remainderBits;
    }
    while( nBits >= 8) {
        putOctet( code % 256);
        code /= 256;
        nBits -=8;
    }
    remainder = code;
    remainderBits = nBits;
}

function putOctet( byte) {
    // add byte to the output stream
}

Он может быть преобразован для использования операторов сдвига битов, но в настоящее время допускает до 46 битов в коде - если добавлено 7 оставшихся битов, число битов достигает 53, максимальная точность битов значений мантиссы в двойномfloats.

Конечно, JavaScript не очень подходит для интенсивных битовых операций, поскольку в нем отсутствует целочисленный тип данных - использование умножения с плавающей запятой не выглядит значительно медленнее, чем смещение влево, если оно действительно медленнее.

0 голосов
/ 13 февраля 2019

Это фактически взаимодействует с «отдыхом» декомпрессии Хаффмана.Что именно вам здесь нужно, зависит от того, намереваетесь ли вы сделать эффективное декодирование на основе таблиц или пошаговое обход дерева.Вход не может быть разделен без декодирования, потому что вы найдете длину кода только после того, как декодируете, какой символ он представляет.После декодирования нет большого смысла в разбиении, поэтому на самом деле мы получаем только декодер Хаффмана, а не разделитель битовых строк.

Для побитового блуждания по дереву все, что вам нужнонужен какой-то способ доступа к какому-либо конкретному биту (с учетом его индекса) из массива байтов.Вы также можете использовать приведенную ниже методику с размером блока 1 бит.

Для более эффективного декодирования вам необходим буфер, из которого вы можете извлечь блок битов, если ваша заранее заданная максимальная длина кода, скажем,15 бит или около того [1] .Специфика зависит от порядка, в котором ваши коды упакованы в байты, то есть от того, заполнены ли байты lsb-to-msb или msb-to-lsb, и от того, где в вашей переменной буфера вы хотите сохранить биты.Например, здесь я держу биты в буфере рядом с lsb буфера и предполагаю, что если код разделен на два байта, то он находится в lsb первого байта и в msb второго байта [2] (не тестировалось):

var rawindex = 0;
var buffer = 0;
var nbits = 0;
var done = false;
var blockmask = (1 << MAX_CODELEN) - 1;
while (!done) {
    // refill buffer
    while (nbits < MAX_CODELEN && rawindex < data.length) {
        buffer = (buffer << 8) | data[rawindex++];
        nbits += 8;
    }
    if (nbits < MAX_CODELEN) {
        // this can happen at the end of the data
        buffer <<= MAX_CODELEN - nbits;
        nbits = MAX_CODELEN;
    }
    // get block from buffer
    var block = (buffer >> (nbits - MAX_CODELEN)) & blockmask;
    // decode by table lookup
    var sym = table[block];
    // drop only bits that really belong to the symbol
    nbits -= bitlengthOf(sym);
    ...
    // use the symbol somehow
}

Здесь показана простейшая стратегия декодирования на основе таблиц, простой поиск.Пара символ / длина может быть объектом или храниться в двух отдельных массивах Uint8Array или кодироваться в один массив Uint16Array, такого рода вещи.Создать таблицу просто, например, в псевдокоде:

# for each symbol/code do this:
bottomSize = maxCodeLen - codeLen
topBits = code << bottomSize
for bottom in inclusive_range(0, (1 << bottomSize) - 1):
    table[topBits | bottom] = (symbol, codeLen)

Variants

Упаковка кодов в байты от lsb вверх изменяет поток битов.Чтобы собрать код в буфере, биты должны входить с верхней стороны буфера и уходить снизу:

// refill step
buffer |= data[rawindex++] << nbits;
nbits += 8;
...
// get block to decode
var block = buffer & blockmask;
// decode by table lookup
var sym = table[block];
// drop bits
buffer >>= getlengthOf(sym);

Таблица также изменилась, теперь заполнение находится в старших битахиндекса таблицы, распределяя записи, принадлежащие одному символу, вместо того, чтобы помещать их в непрерывный диапазон (не проверялось, показывая битовые записи таблицы с длиной 5-битного кода):

// for each symbol/code:
var paddingCount = MAX_CODELEN - codeLen;
for (var padding = 0; padding < (1 << paddingCount); padding++)
    table[(padding << codelen) | code] = (symbol << 5) + codeLen;

[1]: большая максимальная длина кода делает таблицу декодирования очень большой, а MAX_CODELEN> 25 рискует переполнить буфер.Есть способы обойти это, но супер длинные символы в любом случае не очень полезны.

[2]: это не то, что делает DELFATE.

...