我有一个包含6000个元素的数据结构,每个元素我需要存储7位信息。如果我天真地将它存储为6000个元素填充数字的数组,它需要大约22 KB。我试图减少页面的大小 - 什么是存储6000 * 7位信息的最佳方式(应该是大约5 KB)。我想要像数据结构一样的“比特流”。我想过把它编码成一个字符串甚至一个图像但不完全确定。我没有编码为字符串的原因是因为我无法保证所有字符都不会是不可打印的ASCII字符之一(例如ASCII 1-25)
答案 0 :(得分:8)
让我们考虑两种解决方案。
为了好玩,让我们考虑使用base-32数字。是的,你可以用JavaScript做到这一点。
首先将四个7位值打包成一个整数:
function pack(a1,a2,a3,a4){
return ((a1 << 8 | a2) << 8 | a3) << 8 | a4;
}
现在,转换为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个元素的结果,您将拥有1,500个打包数字,每个包含6个字符,将变为大约9K。每个7位值约为1.5个字节。它绝不是信息理论上的最大压缩,但并不是那么糟糕。 解码只是简单地逆转过程:
首先,我们将两个7位值打包成一个整数:
function pack(a1,a2){
return (a1 << 8 | a2) << 8;
}
我们将对所有6,000个输入执行此操作,然后使用我们的朋友String.fromCharCode
将所有3,000个值转换为3,000个字符的Unicode字符串:
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位值两个字节,因此比基本32方法的效率高出约33%。
如果上述字符串将作为Javascript赋值(例如var data="HUGE UNICODE STRING";
)写入脚本标记,则字符串中的引号将需要转义:
javascript_assignment = 'var data = "' + compress(elts).replace(/"/g,'\\"') + '";';
上述代码并不意味着生产,特别是不处理输入数量不是4或2的倍数的边缘情况。
答案 1 :(得分:1)
实际上,如果使用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,您可以保存原始字符串变量,因为浏览器每个localStorage字符使用两个字节...
答案 2 :(得分:1)
正如dandavis所说,可以将不可打印的ASCII字符编码为JSON字符串。但对于随机数据,它给了我13KB(因为必须转义许多字符)。您可以将字符串编码为base64,然后编码为JSON字符串。它给了我7.9KB的随机数据。
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));
}
肯定应该有更有效的方法对数据进行编码,但我相信这是对复杂性和效率的妥协。
PS。在某些浏览器中,您可能需要自己编写atob
和btoa
。