Встраивание двоичных данных в веб-страницу?

У меня есть структура данных с 6000 элементами, и для каждого элемента мне нужно хранить 7 бит информации. Если я наивно храню его как массив из 6000 элементов, заполненных числами, он занимает около 22 КБ. Я пытаюсь уменьшить размер страницы - каков наилучший способ хранения 6000 * 7 бит информации (должно быть около 5 КБ). Я хочу "битовый поток", как структура данных. Я думал о кодировании его в строку или даже изображение, но не совсем уверен. Причина, по которой я не кодировал как string, потому что я не может математически гарантировать, что ни один из символов не будет одним из непечатаемых символов ASCII (например, ASCII 1-25)

3 ответов


рассмотрим два решения.

базовая 32

для удовольствия, давайте рассмотрим использование базы-32 чисел. Да, вы можете сделать это в JavaScript.

первый пакет четыре 7-битных значений в одно целое число:

function pack(a1,a2,a3,a4){
    return ((a1 << 8 | a2) << 8 | a3) << 8 | a4;
}

теперь конвертируйте в base 32.

function encode(n){
    var str = "000000" + n.toString(32);
    str = str.slice(0,6);
    return str;
}

что должно быть не более шести цифр. Убедимся, что ровно шесть.

идя в другом направлении:

function decode(s){
    return parseInt(s, 32);
}

function unpack(x){
    var a1 = x & 0xff0000>>24, a2 = x & 0x00ff0000>>16, a3 = x & 0x0000ff00>>8, a4 = x & 0x000000ff;
    return [a1, a2, a3, a4];
}

все, что остается, это обернуть логика вокруг этого для обработки 6000 элементов. Для сжатия:

function compress(elts){
    var str = '';
    for(var i = 0; i < elts.length; i+=4){
        str += encode(pack(elts[i], elts[i+1], elts[i+2], elts[i+3])
    }
    return str;
}

и распаковать:

function uncompress(str){
    var elts = [];
    for(var i = 0; i < str.length; i+=6){
        elts = elts.concat(unpack(decode(str.slice(i, i+6)));
    }
    return elts;
}

если вы объедините результаты для всех 6,000 элементов, у вас будет 1500 упакованных чисел, которые при шести символах каждый превратится в 9K. Это около 1,5 байта на 7-битное значение. Это ни в коем случае не теоретико-информационное максимальное сжатие, но это не так уж плохо. Чтобы декодировать просто обратный процесс:

Unicode

первый мы упакуем два 7-битных значения в одно целое число:

function pack(a1,a2){
    return (a1 << 8 | a2) << 8;
}

мы сделаем это для всех 6,000 входов, а затем используем нашего друга String.fromCharCode чтобы превратить все 3000 значений в строку Юникода из 3000 символов:

function compress(elts){
    var packeds = [];
    for (var i = 0; i < elts.length; i+=2) {
        packeds.push(pack(elts[i], elts[i+1]);
    }
    return String.fromCharCode.apply(0, packeds);
}

возвращаясь в другую сторону, это совсем просто:

function uncompress(str) {
    var elts = [], code;
    for (var i = 0; i < str.length; i++) {
        code=str.charCodeAt(i);
        elts.push(code>>8, code & 0xff);
    }
    return elts;
}

это займет два байта на два 7-битных значения, поэтому примерно на 33% эффективнее, чем подход base 32.

если вышеуказанная строка будет записана в тег скрипта как назначение Javascript, такое как var data="HUGE UNICODE STRING";, тогда кавычки в строке должны быть экранированы:

javascript_assignment = 'var data = "' + compress(elts).replace(/"/g,'\"') + '";';

приведенный выше код не предназначен для производства и, в частности, не обрабатывает крайние случаи, когда количество входов не кратно четырем или двум.


как сказал дандавис, можно кодировать непечатаемые символы ASCII в JSON-строку. Но для случайных данных он дал мне 13KB (потому что многие символы должны быть экранированы). Вы можете кодировать строку в base64, а затем в JSON-string. Он дал мне 7.9 КБ случайных данных.

var randint = function (from, to) {
    return Math.floor(Math.random() * (to - from + 1)) + from;
}

var data = '';
for (var i = 0; i < 6000; ++i) {
    data += String.fromCharCode(randint(0, 127));
}
// encoding `data` as JSON-string at this point gave me 13KB

var b64data = btoa(data);
// encoding `b64data` as JSON-string gave me 7.9KB

раскодировать

var data = atob(b64data);
var adata = [];
for (var i = 0; i < data.length; ++i) {
    adata.push(data.charCodeAt(i));
}

определенно должен быть более эффективный метод для кодирования ваших данных, но я считаю, что это компромисс по сложности и эффективности. ПС. В некоторых браузерах вы возможно, придется написать atob и btoa самостоятельно.


на самом деле строки работают нормально, если вы используете JSON для кодирования любых потенциальных гадостей в код JS-escape:

var codes=",Ñkqëgdß\u001f", // (10 chars JSON encoded to store all chars ranges)
mySet=codes[4].charCodeAt().toString(2).split("").map(Number).map(Boolean).reverse();

alert(mySet); // shows: [true,false,false,false,true,true,true] 


/*  broken down into bite-sized steps: (pseudo code)
char == "g" (codes[4])
"g".charCodeAt() == 103
(103).toString(2) == "1100111"
.split().map(Number) ==  [1,1,0,0,1,1,1]
.map(Boolean).reverse() == [true,true,true,false,false,true,true]  */

и чтобы заполнить массив, обратный процесс:

var toStore= [true, false, true, false, true, false, true];
var char= String.fromCharCode(parseInt(toStore.map(Number).reverse().join(""),2));
codes+=char;

//verify (should===true):   
codes[10].charCodeAt().toString(2).split("")
   .map(Number).map(Boolean).reverse().toString() === toStore.toString();

для экспорта результатов в файл ascii, JSON.stringify (коды), или при сохранении в localStrorage, вы можете просто сохранить необработанную строковую переменную, так как браузеры используют два байта на char localStorage...