在网页中嵌入二进制数据?

11

我有一个含有6000个元素的数据结构,每个元素需要存储7位信息。如果我单纯地将其作为填满数字的6000个元素的数组存储,则需占用大约22KB的空间。我想要缩小页面的大小 - 如何以最佳方式存储6000 * 7位信息(应该大约为5KB)。我想要一种"比特流"般的数据结构。我考虑过将其编码为字符串甚至图像,但不确定。我之所以没有将其编码为字符串,是因为我无法数学保证没有字符是不可打印的ASCII字符(例如ASCII 1-25)。


1
将字符串位移至不包括0的某个范围可能是最简单的解决方案(例如使用charFromCode[value +32]之类的方法来获取所有安全范围内的值)- 轻松访问每个元素... - Alexei Levenkov
1
可能会看到这个 - Ishank
1
你是否启用了服务器压缩?我本以为gzip会相当好地压缩一个简单的数组?22k是观察到的网络效应还是仅仅是服务器文件大小的增加? - rlb
太好了!但是响应头部分是否表明响应被“gzip”压缩了?不好意思再强调一下,尝试将数据放在页面上的方法很可能会增加未压缩的原始大小,虽然图像的想法可能不错。(将在适当的时候提供完整答案) - rlb
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/32128/discussion-between-rlb-and-wrick - rlb
显示剩余5条评论
3个回答

9

让我们考虑两个解决方案。

Base 32

为了好玩,让我们考虑使用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个紧凑数字,每个数字有六个字符,大约会变成9K。每个7位值大约占用1.5字节。这绝不是信息论最大压缩率,但也不算太糟糕。 要解码,只需反向进行此过程:

Unicode

首先,我们将两个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位值,因此比base 32方法更有效率,提高了约33%。

如果上述字符串将被写入到一个JavaScript赋值脚本标签中,例如var data="HUGE UNICODE STRING";,则字符串中的引号需要被转义:

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

上述代码不适用于生产环境,特别是不能处理输入数量不是四的倍数或两的倍数的边缘情况。


如果您能将问题修改为人们可以理解的用例,那么这将是一个很好的示例,可以与他人分享。 - vol7ron

1

实际上,如果使用JSON将任何潜在的恶意内容编码为JS转义代码,则字符串可以正常工作:

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(codes)。如果保存到localStorage中,您可以只保存原始字符串变量,因为浏览器在localStorage中每个字符使用两个字节...


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. 在某些浏览器中,您可能需要自己编写 atobbtoa


为了详细说明“在某些浏览器中,您可能需要自己编写atob和btoa”的部分:atob和btoa需要IE 11、Edge 16、Firefox 52、Chrome 49、Safari 10.1(或iOS上的9.3)或Opera 45(或Mini)。它不会在早期的浏览器中默认存在,也不会在Node.JS上运行,也不会在Android 4应用程序的WebView中(这些WebView组件不会被Chrome更新修复)。 - Silas S. Brown

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接