Подтвердить что ты не робот

Наиболее эффективный способ хранения больших массивов целых чисел в localStorage с помощью Javascript

* "Эффективный" здесь в основном означает меньший размер (чтобы уменьшить время ожидания ввода-вывода) и быстрое время поиска/десериализации. Время хранения не так важно.

Мне нужно хранить несколько десятков массивов целых чисел, каждый из которых имеет 1800 значений в диапазоне 0-50, в браузере localStorage, то есть в виде строки.

Очевидно, что самый простой метод - это просто JSON.stringify он, однако, добавляет много ненужной информации, учитывая, что диапазоны данных хорошо известны. Средний размер для одного из этих массивов составляет ~ 5500 байт.

Вот некоторые другие методы, которые я пытался (результирующий размер и время для десериализации его 1000 раз в конце)

  • нулевое заполнение чисел, поэтому каждый из них имеет длину 2 символа, например:

    [5, 27, 7, 38] ==> "05270738"
    
  • base 50, кодирующий его:

    [5, 11, 7, 38] ==> "5b7C"
    
  • просто используя значение в качестве символьного кода (добавив 32, чтобы избежать появления в начале странных управляющих символов):

    [5, 11, 7, 38] ==> "%+'F" (String.fromCharCode(37), String.fromCharCode(43) ...)
    

Вот мои результаты:

                  size     Chrome 18   Firefox 11
-------------------------------------------------
JSON.stringify    5286          60ms         99ms
zero-padded       3600         354ms        703ms
base 50           1800         315ms        400ms
charCodes         1800          21ms        178ms

Мой вопрос в том, есть ли еще лучший метод, который я еще не рассматривал?

Обновление
MDΓΓBDLL предложил использовать сжатие данных. Сочетание этой реализации LZW с данными базы 50 и charCode. Я также тестировал код арофа (упаковывая 4 целых числа в 3 байта). Я получил следующие результаты:

                  size     Chrome 18   Firefox 11
-------------------------------------------------
LZW base 50       1103         494ms        999ms
LZW charCodes     1103         194ms        882ms
bitpacking        1350        2395ms        331ms
4b9b3361

Ответ 1

Если ваш диапазон равен 0-50, вы можете упаковать 4 числа в 3 байта (6 бит на номер). Это позволит вам хранить 1800 номеров, используя ~ 1350 байт. Этот код должен сделать это:

window._firstChar = 48;

window.decodeArray = function(encodedText) {
    var result = [];
    var temp = [];

    for (var index = 0; index < encodedText.length; index += 3) {
        //skipping bounds checking because the encoded text is assumed to be valid
        var firstChar = encodedText.charAt(index).charCodeAt() - _firstChar;
        var secondChar = encodedText.charAt(index + 1).charCodeAt() - _firstChar;
        var thirdChar = encodedText.charAt(index + 2).charCodeAt() - _firstChar;

        temp.push((firstChar >> 2) & 0x3F);    //6 bits, 'a'
        temp.push(((firstChar & 0x03) << 4) | ((secondChar >> 4) & 0xF));  //2 bits + 4 bits, 'b'
        temp.push(((secondChar & 0x0F) << 2) | ((thirdChar >> 6) & 0x3));  //4 bits + 2 bits, 'c'
        temp.push(thirdChar & 0x3F);  //6 bits, 'd'

    }

    //filter out 'padding' numbers, if present; this is an extremely inefficient way to do it
    for (var index = 0; index < temp.length; index++) {
        if(temp[index] != 63) {
            result.push(temp[index]);
        }            
    }

    return result;
};

window.encodeArray = function(array) {
    var encodedData = [];

    for (var index = 0; index < dataSet.length; index += 4) {
        var num1 = dataSet[index];
        var num2 = index + 1 < dataSet.length ? dataSet[index + 1] : 63;
        var num3 = index + 2 < dataSet.length ? dataSet[index + 2] : 63;
        var num4 = index + 3 < dataSet.length ? dataSet[index + 3] : 63;

        encodeSet(num1, num2, num3, num4, encodedData);
    }

    return encodedData;
};

window.encodeSet = function(a, b, c, d, outArray) {
    //we can encode 4 numbers in 3 bytes
    var firstChar = ((a & 0x3F) << 2) | ((b >> 4) & 0x03);   //6 bits for 'a', 2 from 'b'
    var secondChar = ((b & 0x0F) << 4) | ((c >> 2) & 0x0F);  //remaining 4 bits from 'b', 4 from 'c'
    var thirdChar = ((c & 0x03) << 6) | (d & 0x3F);          //remaining 2 bits from 'c', 6 bits for 'd'

    //add _firstChar so that all values map to a printable character
    outArray.push(String.fromCharCode(firstChar + _firstChar));
    outArray.push(String.fromCharCode(secondChar + _firstChar));
    outArray.push(String.fromCharCode(thirdChar + _firstChar));
};

Вот быстрый пример: http://jsfiddle.net/NWyBx/1

Обратите внимание, что размер хранилища может быть дополнительно уменьшен путем применения сжатия gzip к результирующей строке.

В качестве альтернативы, если упорядочение ваших номеров не является значительным, вы можете просто выполнить сортировку в виде ведра с использованием 51 ведра (при условии, что 0-50 включает в себя как 0, так и 50 в качестве допустимых номеров) и хранить подсчеты для каждого ведра вместо сами цифры. Это, вероятно, даст вам лучшее сжатие и эффективность, чем любой другой подход.

Ответ 2

Предполагая (как и в вашем тесте), что сжатие занимает больше времени, чем уменьшает уменьшение размера, ваша кодировка char самая маленькая, которую вы получите без битрейта. Вы используете один байт для каждого номера, но если они гарантированно будут достаточно маленькими, вы можете поместить два числа в каждый байт. Вероятно, это будет чрезмерная оптимизация, если это не очень горячая часть вашего кода.

Ответ 3

Возможно, вы захотите использовать Uint8Array или ArrayBuffer. Этот blogpost показывает, как это делается. Копирование его логики, вот пример, предполагая, что у вас есть существующий Uint8Array с именем arr.

function arrayBufferToBinaryString(buffer, cb) {
    var blobBuilder = new BlobBuilder();
    blobBuilder.append(buffer);
    var blob = blobBuilder.getBlob();
    var reader = new FileReader();
    reader.onload = function (e) {
        cb(reader.result);
    };
    reader.readAsBinaryString(blob);
}
arrayBufferToBinaryString(arr.buffer, function(s) { 
  // do something with s
});