Last active
July 6, 2017 08:37
-
-
Save JobLeonard/e25c445d987295b0114407e457dde9ad to your computer and use it in GitHub Desktop.
LZString old vs new compression short repetitive string #jsbench #jsperf (http://jsbench.github.io/#e25c445d987295b0114407e457dde9ad) #jsbench #jsperf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"/> | |
<title>LZString old vs new compression short repetitive string #jsbench #jsperf</title> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/benchmark/1.0.0/benchmark.min.js"></script> | |
<script src="./suite.js"></script> | |
</head> | |
<body> | |
<h1>Open the console to view the results</h1> | |
<h2><code>cmd + alt + j</code> or <code>ctrl + alt + j</code></h2> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
(function (factory) { | |
if (typeof Benchmark !== "undefined") { | |
factory(Benchmark); | |
} else { | |
factory(require("benchmark")); | |
} | |
})(function (Benchmark) { | |
var suite = new Benchmark.Suite; | |
Benchmark.prototype.setup = function () { | |
var LZString = (function() { | |
// private property | |
var f = String.fromCharCode; | |
var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; | |
var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$"; | |
var baseReverseDic = {}; | |
function getBaseValue(alphabet, character) { | |
if (!baseReverseDic[alphabet]) { | |
baseReverseDic[alphabet] = {}; | |
for (var i=0 ; i<alphabet.length ; i++) { | |
baseReverseDic[alphabet][alphabet.charAt(i)] = i; | |
} | |
} | |
return baseReverseDic[alphabet][character]; | |
} | |
var LZString = { | |
compressToBase64 : function (input) { | |
if (input == null) return ""; | |
var res = LZString._compress(input, 6, function(a){return keyStrBase64.charAt(a);}); | |
switch (res.length % 4) { // To produce valid Base64 | |
default: // When could this happen ? | |
case 0 : return res; | |
case 1 : return res+"==="; | |
case 2 : return res+"=="; | |
case 3 : return res+"="; | |
} | |
}, | |
decompressFromBase64 : function (input) { | |
if (input == null) return ""; | |
if (input == "") return null; | |
return LZString._decompress(input.length, 32, function(index) { return getBaseValue(keyStrBase64, input.charAt(index)); }); | |
}, | |
compressToUTF16 : function (input) { | |
if (input == null) return ""; | |
return LZString._compress(input, 15, function(a){return f(a+32);}) + " "; | |
}, | |
decompressFromUTF16: function (compressed) { | |
if (compressed == null) return ""; | |
if (compressed == "") return null; | |
return LZString._decompress(compressed.length, 16384, function(index) { return compressed.charCodeAt(index) - 32; }); | |
}, | |
//compress into uint8array (UCS-2 big endian format) | |
compressToUint8Array: function (uncompressed) { | |
var compressed = LZString.compress(uncompressed); | |
var buf=new Uint8Array(compressed.length*2); // 2 bytes per character | |
for (var i=0, TotalLen=compressed.length; i<TotalLen; i++) { | |
var current_value = compressed.charCodeAt(i); | |
buf[i*2] = current_value >>> 8; | |
buf[i*2+1] = current_value % 256; | |
} | |
return buf; | |
}, | |
//decompress from uint8array (UCS-2 big endian format) | |
decompressFromUint8Array:function (compressed) { | |
if (compressed===null || compressed===undefined){ | |
return LZString.decompress(compressed); | |
} else { | |
var buf=new Array(compressed.length/2); // 2 bytes per character | |
for (var i=0, TotalLen=buf.length; i<TotalLen; i++) { | |
buf[i]=compressed[i*2]*256+compressed[i*2+1]; | |
} | |
var result = []; | |
buf.forEach(function (c) { | |
result.push(f(c)); | |
}); | |
return LZString.decompress(result.join('')); | |
} | |
}, | |
//compress into a string that is already URI encoded | |
compressToEncodedURIComponent: function (input) { | |
if (input == null) return ""; | |
return LZString._compress(input, 6, function(a){return keyStrUriSafe.charAt(a);}); | |
}, | |
//decompress from an output of compressToEncodedURIComponent | |
decompressFromEncodedURIComponent:function (input) { | |
if (input == null) return ""; | |
if (input == "") return null; | |
input = input.replace(/ /g, "+"); | |
return LZString._decompress(input.length, 32, function(index) { return getBaseValue(keyStrUriSafe, input.charAt(index)); }); | |
}, | |
compress: function (uncompressed) { | |
return LZString._compress(uncompressed, 16, function(a){return f(a);}); | |
}, | |
_compress: function (uncompressed, bitsPerChar, getCharFromInt) { | |
if (uncompressed == null) return ""; | |
var i, value, | |
context_dictionary= {}, | |
context_dictionaryToCreate= {}, | |
context_c="", | |
context_wc="", | |
context_w="", | |
context_enlargeIn= 2, // Compensate for the first entry which should not count | |
context_dictSize= 3, | |
context_numBits= 2, | |
context_data=[], | |
context_data_val=0, | |
context_data_position=0, | |
ii; | |
for (ii = 0; ii < uncompressed.length; ii += 1) { | |
context_c = uncompressed.charAt(ii); | |
if (!Object.prototype.hasOwnProperty.call(context_dictionary,context_c)) { | |
context_dictionary[context_c] = context_dictSize++; | |
context_dictionaryToCreate[context_c] = true; | |
} | |
context_wc = context_w + context_c; | |
if (Object.prototype.hasOwnProperty.call(context_dictionary,context_wc)) { | |
context_w = context_wc; | |
} else { | |
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { | |
if (context_w.charCodeAt(0)<256) { | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
} | |
value = context_w.charCodeAt(0); | |
for (i=0 ; i<8 ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
} else { | |
value = 1; | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1) | value; | |
if (context_data_position ==bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = 0; | |
} | |
value = context_w.charCodeAt(0); | |
for (i=0 ; i<16 ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
} | |
context_enlargeIn--; | |
if (context_enlargeIn == 0) { | |
context_enlargeIn = Math.pow(2, context_numBits); | |
context_numBits++; | |
} | |
delete context_dictionaryToCreate[context_w]; | |
} else { | |
value = context_dictionary[context_w]; | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
} | |
context_enlargeIn--; | |
if (context_enlargeIn == 0) { | |
context_enlargeIn = Math.pow(2, context_numBits); | |
context_numBits++; | |
} | |
// Add wc to the dictionary. | |
context_dictionary[context_wc] = context_dictSize++; | |
context_w = String(context_c); | |
} | |
} | |
// Output the code for w. | |
if (context_w !== "") { | |
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { | |
if (context_w.charCodeAt(0)<256) { | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
} | |
value = context_w.charCodeAt(0); | |
for (i=0 ; i<8 ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
} else { | |
value = 1; | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1) | value; | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = 0; | |
} | |
value = context_w.charCodeAt(0); | |
for (i=0 ; i<16 ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
} | |
context_enlargeIn--; | |
if (context_enlargeIn == 0) { | |
context_enlargeIn = Math.pow(2, context_numBits); | |
context_numBits++; | |
} | |
delete context_dictionaryToCreate[context_w]; | |
} else { | |
value = context_dictionary[context_w]; | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
} | |
context_enlargeIn--; | |
if (context_enlargeIn == 0) { | |
context_enlargeIn = Math.pow(2, context_numBits); | |
context_numBits++; | |
} | |
} | |
// Mark the end of the stream | |
value = 2; | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
// Flush the last char | |
while (true) { | |
context_data_val = (context_data_val << 1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data.push(getCharFromInt(context_data_val)); | |
break; | |
} | |
else context_data_position++; | |
} | |
return context_data.join(''); | |
}, | |
decompress: function (compressed) { | |
if (compressed == null) return ""; | |
if (compressed == "") return null; | |
return LZString._decompress(compressed.length, 32768, function(index) { return compressed.charCodeAt(index); }); | |
}, | |
_decompress: function (length, resetValue, getNextValue) { | |
var dictionary = [], | |
next, | |
enlargeIn = 4, | |
dictSize = 4, | |
numBits = 3, | |
entry = "", | |
result = [], | |
i, | |
w, | |
bits, resb, maxpower, power, | |
c, | |
data = {val:getNextValue(0), position:resetValue, index:1}; | |
for (i = 0; i < 3; i += 1) { | |
dictionary[i] = i; | |
} | |
bits = 0; | |
maxpower = Math.pow(2,2); | |
power=1; | |
while (power!=maxpower) { | |
resb = data.val & data.position; | |
data.position >>= 1; | |
if (data.position == 0) { | |
data.position = resetValue; | |
data.val = getNextValue(data.index++); | |
} | |
bits |= (resb>0 ? 1 : 0) * power; | |
power <<= 1; | |
} | |
switch (next = bits) { | |
case 0: | |
bits = 0; | |
maxpower = Math.pow(2,8); | |
power=1; | |
while (power!=maxpower) { | |
resb = data.val & data.position; | |
data.position >>= 1; | |
if (data.position == 0) { | |
data.position = resetValue; | |
data.val = getNextValue(data.index++); | |
} | |
bits |= (resb>0 ? 1 : 0) * power; | |
power <<= 1; | |
} | |
c = f(bits); | |
break; | |
case 1: | |
bits = 0; | |
maxpower = Math.pow(2,16); | |
power=1; | |
while (power!=maxpower) { | |
resb = data.val & data.position; | |
data.position >>= 1; | |
if (data.position == 0) { | |
data.position = resetValue; | |
data.val = getNextValue(data.index++); | |
} | |
bits |= (resb>0 ? 1 : 0) * power; | |
power <<= 1; | |
} | |
c = f(bits); | |
break; | |
case 2: | |
return ""; | |
} | |
dictionary[3] = c; | |
w = c; | |
result.push(c); | |
while (true) { | |
if (data.index > length) { | |
return ""; | |
} | |
bits = 0; | |
maxpower = Math.pow(2,numBits); | |
power=1; | |
while (power!=maxpower) { | |
resb = data.val & data.position; | |
data.position >>= 1; | |
if (data.position == 0) { | |
data.position = resetValue; | |
data.val = getNextValue(data.index++); | |
} | |
bits |= (resb>0 ? 1 : 0) * power; | |
power <<= 1; | |
} | |
switch (c = bits) { | |
case 0: | |
bits = 0; | |
maxpower = Math.pow(2,8); | |
power=1; | |
while (power!=maxpower) { | |
resb = data.val & data.position; | |
data.position >>= 1; | |
if (data.position == 0) { | |
data.position = resetValue; | |
data.val = getNextValue(data.index++); | |
} | |
bits |= (resb>0 ? 1 : 0) * power; | |
power <<= 1; | |
} | |
dictionary[dictSize++] = f(bits); | |
c = dictSize-1; | |
enlargeIn--; | |
break; | |
case 1: | |
bits = 0; | |
maxpower = Math.pow(2,16); | |
power=1; | |
while (power!=maxpower) { | |
resb = data.val & data.position; | |
data.position >>= 1; | |
if (data.position == 0) { | |
data.position = resetValue; | |
data.val = getNextValue(data.index++); | |
} | |
bits |= (resb>0 ? 1 : 0) * power; | |
power <<= 1; | |
} | |
dictionary[dictSize++] = f(bits); | |
c = dictSize-1; | |
enlargeIn--; | |
break; | |
case 2: | |
return result.join(''); | |
} | |
if (enlargeIn == 0) { | |
enlargeIn = Math.pow(2, numBits); | |
numBits++; | |
} | |
if (dictionary[c]) { | |
entry = dictionary[c]; | |
} else { | |
if (c === dictSize) { | |
entry = w + w.charAt(0); | |
} else { | |
return null; | |
} | |
} | |
result.push(entry); | |
// Add w+entry[0] to the dictionary. | |
dictionary[dictSize++] = w + entry.charAt(0); | |
enlargeIn--; | |
w = entry; | |
if (enlargeIn == 0) { | |
enlargeIn = Math.pow(2, numBits); | |
numBits++; | |
} | |
} | |
} | |
}; | |
return LZString; | |
})(); | |
var LZStringNew = ( | |
function () { | |
// private property | |
var f = String.fromCharCode, | |
Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), | |
UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), | |
Base64ReverseDic = {}, | |
UriSafeReverseDic = {}, | |
i = 65; | |
while (i--) { | |
Base64ReverseDic[Base64CharArray[i].charCodeAt(0)] = i; | |
UriSafeReverseDic[UriSafeCharArray[i].charCodeAt(0)] = i; | |
} | |
var getChar16Bits = function (a) { return f(a); }, | |
getCharFromBase64 = function (a) { return Base64CharArray[a]; }, | |
getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, | |
getCharFromUTF16 = function (a) { return f(a + 32); }; | |
var LZString = { | |
compressToBase64: function (input) { | |
if (input == null) return ""; | |
var res = LZString._compressToArray(input, 6, getCharFromBase64); | |
// To produce valid Base64 | |
var i = res.length % 4; | |
while (i--) { | |
res.push("="); | |
} | |
return res.join(''); | |
}, | |
decompressFromBase64: function (input) { | |
if (input == null) return ""; | |
if (input == "") return null; | |
return LZString._decompress(input.length, 32, function (index) { return Base64ReverseDic[input.charCodeAt(index)]; }); | |
}, | |
compressToUTF16: function (input) { | |
if (input == null) return ""; | |
var compressed = LZString._compressToArray(input, 15, getCharFromUTF16); | |
compressed.push(" "); | |
return compressed.join(''); | |
}, | |
decompressFromUTF16: function (compressed) { | |
if (compressed == null) return ""; | |
if (compressed == "") return null; | |
return LZString._decompress(compressed.length, 16384, function (index) { return compressed.charCodeAt(index) - 32; }); | |
}, | |
//compress into uint8array (UCS-2 big endian format) | |
compressToUint8Array: function (uncompressed) { | |
var compressed = LZString.compressToArray(uncompressed); | |
var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character | |
for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { | |
var current_value = compressed[i].charCodeAt(0); | |
buf[i * 2] = current_value >>> 8; | |
buf[i * 2 + 1] = current_value & 0xFF; | |
} | |
return buf; | |
}, | |
//decompress from uint8array (UCS-2 big endian format) | |
decompressFromUint8Array: function (compressed) { | |
if (compressed === null || compressed === undefined) { | |
return LZString.decompressFromArray(compressed); | |
} else if (compressed.length == 0) { | |
return null; | |
} | |
return LZString._decompress(compressed.length, 128, function (index) { return compressed[index]; }); | |
}, | |
//compress into a string that is already URI encoded | |
compressToEncodedURIComponent: function (input) { | |
if (input == null) return ""; | |
return LZString._compressToArray(input, 6, getCharFromURISafe).join(''); | |
}, | |
//decompress from an output of compressToEncodedURIComponent | |
decompressFromEncodedURIComponent: function (input) { | |
if (input == null) return ""; | |
if (input == "") return null; | |
input = input.replace(/ /g, "+"); | |
return LZString._decompress(input.length, 32, function (index) { return UriSafeReverseDic[input.charCodeAt(index)]; }); | |
}, | |
compress: function (uncompressed) { | |
return LZString.compressToArray(uncompressed).join(''); | |
}, | |
compressToArray: function (uncompressed) { | |
return LZString._compressToArray(uncompressed, 16, getChar16Bits); | |
}, | |
_compressToArray: function (uncompressed, bitsPerChar, getCharFromInt) { | |
if (uncompressed == null) return []; | |
var i = 0, j = 0, value = 0, | |
dictionary = {}, | |
freshNode = true, | |
c = 0, | |
c0 = 1, | |
new_node = { 0: 3 }, // first node will always be | |
node = new_node, // initialised like this. | |
enlargeIn = 1, | |
dictSize = 4, | |
numBits = 2, | |
data = [], | |
data_val = 0, | |
data_position = 0; | |
if (uncompressed.length) { | |
// If there is a string, the first charCode is guaranteed to | |
// be new, so we write it to output stream, and add it to the | |
// dictionary. For the same reason we can initialize freshNode | |
// as true, and new_node, node and dictSize as if | |
// it was already added to the dictionary (see above). | |
c = uncompressed.charCodeAt(0); | |
c0 = c + 1; | |
// == Write first charCode token to output == | |
// 8 or 16 bit? | |
value = c < 256 ? 0 : 1 | |
// insert "new 8/16 bit charCode" token | |
// into bitstream (value 1) | |
for (i = 0; i < numBits; i++) { | |
// Value is 0 (8 bit) or 1 (16 bit). | |
// We shift it into the bitstream in reverse | |
// (shifting has precedence over bitmasking) | |
data_val = value >> i | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
// insert charCode bits into bitstream | |
// Nasty but effective hack: | |
// loop 8 or 16 times based on token value | |
value = 8 + 8 * value; | |
for (i = 0; i < value; i++) { | |
// shifting has precedence over bitmasking | |
data_val = c >> i & 1 | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
// Add charCode to the dictionary. | |
dictionary[c0] = new_node; | |
for (j = 1; j < uncompressed.length; j++) { | |
c = uncompressed.charCodeAt(j); | |
c0 = c + 1; | |
// does the new charCode match an existing prefix? | |
new_node = node[c0]; | |
if (new_node) { | |
// continue with next prefix | |
node = new_node; | |
} else { | |
// Prefix+charCode does not exist in trie yet. | |
// We write the prefix to the bitstream, and add | |
// the new charCode to the dictionary if it's new | |
// Then we set `node` to the root node matching | |
// the charCode. | |
if (freshNode) { | |
// Prefix is a freshly added character token, | |
// which was already written to the bitstream | |
freshNode = false; | |
} else { | |
// write out the current prefix token | |
value = node[0]; | |
for (i = 0; i < numBits; i++) { | |
// shifting has precedence over bitmasking | |
data_val = value >> i & 1 | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
} | |
// Is the new charCode a new character | |
// that needs to be stored at the root? | |
new_node = dictionary[c0]; | |
if (new_node == undefined) { | |
// increase token bitlength if necessary | |
if (--enlargeIn == 0) { | |
enlargeIn = 1 << numBits++; | |
} | |
// insert "new 8/16 bit charCode" token, | |
// see comments above for explanation | |
value = c < 256 ? 0 : 1 | |
for (i = 0; i < numBits; i++) { | |
data_val = value >> i | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
value = 8 + 8 * value; | |
for (i = 0; i < value; i++) { | |
data_val = c >> i & 1 | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
new_node = { 0: dictSize++ }; | |
dictionary[c0] = new_node; | |
// Note of that we already wrote | |
// the charCode token to the bitstream | |
freshNode = true; | |
} | |
// add node representing prefix + new charCode to trie | |
new_node = { 0: dictSize++ }; | |
node[c0] = new_node; | |
// increase token bitlength if necessary | |
if (--enlargeIn == 0) { | |
enlargeIn = 1 << numBits++; | |
} | |
// set node to first charCode of new prefix | |
node = dictionary[c0]; | |
} | |
} | |
// === Write last prefix to output === | |
if (freshNode) { | |
// character token already written to output | |
freshNode = false; | |
} else { | |
// write out the prefix token | |
value = node[0]; | |
for (i = 0; i < numBits; i++) { | |
// shifting has precedence over bitmasking | |
data_val = value >> i & 1 | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
} | |
// Is c a new character? | |
new_node = dictionary[c0]; | |
if (new_node == undefined) { | |
// increase token bitlength if necessary | |
if (--enlargeIn == 0) { | |
enlargeIn = 1 << numBits++; | |
} | |
// insert "new 8/16 bit charCode" token, | |
// see comments above for explanation | |
value = c < 256 ? 0 : 1 | |
for (i = 0; i < numBits; i++) { | |
data_val = value >> i | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
value = 8 + 8 * value; | |
for (i = 0; i < value; i++) { | |
data_val = c >> i & 1 | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
} | |
// increase token bitlength if necessary | |
if (--enlargeIn == 0) { | |
enlargeIn = 1 << numBits++; | |
} | |
} | |
// Mark the end of the stream | |
for (i = 0; i < numBits; i++) { | |
// shifting has precedence over bitmasking | |
data_val = 2 >> i & 1 | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
// Flush the last char | |
data_val <<= bitsPerChar - data_position; | |
data.push(getCharFromInt(data_val)); | |
return data; | |
}, | |
decompress: function (compressed) { | |
if (compressed == null) return ""; | |
if (compressed == "") return null; | |
return LZString._decompress(compressed.length, 32768, function (index) { return compressed.charCodeAt(index); }); | |
}, | |
decompressFromArray: function (compressed) { | |
if (compressed == null) return ""; | |
if (compressed.length == 0) return null; | |
return LZString._decompress(compressed.length, 32768, function (index) { return compressed[index].charCodeAt(0); }); | |
}, | |
_decompress: function (length, resetValue, getNextValue) { | |
// "Math.log2(resetValue)" is ES6, so we use | |
// this while loop instead for backwards compatibility | |
var _resetValue = 0; | |
while (resetValue >> ++_resetValue) { } | |
var dictionary = [0, 1, 2], | |
enlargeIn = 4, | |
dictSize = 4, | |
numBits = 3, | |
entry = "", | |
result = [], | |
w = "", | |
bits = 0, | |
maxpower = 2, | |
power = 0, | |
c = "", | |
data_val = getNextValue(0), | |
data_position = _resetValue, | |
data_index = 1; | |
// Get first token, guaranteed to be either | |
// a new character token (8 or 16 bits) | |
// or end of stream token. | |
while (power != maxpower) { | |
// shifting has precedence over bitmasking | |
bits += (data_val >> --data_position & 1) << power++; | |
if (data_position == 0) { | |
data_position = _resetValue; | |
data_val = getNextValue(data_index++); | |
} | |
} | |
// if end of stream token, return empty string | |
if (bits == 2) { | |
return ""; | |
} | |
// else, get character | |
maxpower = bits * 8 + 8; | |
bits = power = 0; | |
while (power != maxpower) { | |
// shifting has precedence over bitmasking | |
bits += (data_val >> --data_position & 1) << power++; | |
if (data_position == 0) { | |
data_position = _resetValue; | |
data_val = getNextValue(data_index++); | |
} | |
} | |
c = f(bits); | |
dictionary[3] = c; | |
w = c; | |
result.push(c); | |
// read rest of string | |
while (data_index <= length) { | |
// read out next token | |
maxpower = numBits; | |
bits = power = 0; | |
while (power != maxpower) { | |
// shifting has precedence over bitmasking | |
bits += (data_val >> --data_position & 1) << power++; | |
if (data_position == 0) { | |
data_position = _resetValue; | |
data_val = getNextValue(data_index++); | |
} | |
} | |
// 0 or 1 implies new character token | |
if (bits < 2) { | |
maxpower = (8 + 8 * bits); | |
bits = power = 0; | |
while (power != maxpower) { | |
// shifting has precedence over bitmasking | |
bits += (data_val >> --data_position & 1) << power++; | |
if (data_position == 0) { | |
data_position = _resetValue; | |
data_val = getNextValue(data_index++); | |
} | |
} | |
dictionary[dictSize] = f(bits); | |
bits = dictSize++; | |
if (--enlargeIn == 0) { | |
enlargeIn = 1 << numBits++; | |
} | |
} else if (bits == 2) { | |
// end of stream token | |
return result.join(''); | |
} | |
if (bits > dictionary.length) { | |
return null; | |
} | |
entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); | |
result.push(entry); | |
// Add w+entry[0] to the dictionary. | |
dictionary[dictSize++] = w + entry.charAt(0); | |
w = entry; | |
if (--enlargeIn == 0) { | |
enlargeIn = 1 << numBits++; | |
} | |
} | |
return ""; | |
} | |
}; | |
return LZString; | |
} | |
)(); | |
var testStringRandomNrs = ''; | |
var i; | |
for (i=0 ; i<1000 ; i++) | |
testStringRandomNrs += Math.random() + " "; | |
var testStringRepeat1024 = 'aaaaabaaaaacaaaaadaaaaaeaaaaa'; | |
testStringRepeat1024 += testStringRepeat1024; | |
testStringRepeat1024 += testStringRepeat1024; | |
testStringRepeat1024 += testStringRepeat1024; | |
testStringRepeat1024 += testStringRepeat1024; | |
testStringRepeat1024 += testStringRepeat1024; | |
testStringRepeat1024 += testStringRepeat1024; | |
testStringRepeat1024 += testStringRepeat1024; | |
testStringRepeat1024 += testStringRepeat1024; | |
testStringRepeat1024 += testStringRepeat1024; | |
testStringRepeat1024 += testStringRepeat1024; | |
var testStringAllUTF16 = ''; | |
var i; | |
for (i = 32; i < 127; ++i) { | |
testStringAllUTF16 += String.fromCharCode(i); | |
} | |
for (i = 128 + 32; i < 55296; ++i) { | |
testStringAllUTF16 += String.fromCharCode(i); | |
} | |
for (i = 63744; i < 65536; ++i) { | |
testStringAllUTF16 += String.fromCharCode(i); | |
} | |
}; | |
suite.add("OLD 1000 Random Float Nrs", function () { | |
// OLD 1000 Random Float Nrs | |
var t = LZString.compress(testStringRandomNrs); | |
}); | |
suite.add("NEW 1000 Random Float Nrs", function () { | |
// NEW 1000 Random Float Nrs | |
var t = LZStringNew.compress(testStringRandomNrs); | |
}); | |
suite.add("OLD 'aaaaabaaaaacaaaaadaaaaaeaaaaa' * 1024", function () { | |
// OLD 'aaaaabaaaaacaaaaadaaaaaeaaaaa' * 1024 | |
var t = LZString.compress(testStringRepeat1024); | |
}); | |
suite.add("NEW 'aaaaabaaaaacaaaaadaaaaaeaaaaa' * 1024", function () { | |
// NEW 'aaaaabaaaaacaaaaadaaaaaeaaaaa' * 1024 | |
var t = LZStringNew.compress(testStringRepeat1024); | |
}); | |
suite.add("OLD all printable UTF16", function () { | |
// OLD all printable UTF16 | |
var t = LZString.compress(testStringAllUTF16); | |
}); | |
suite.add("NEW all printable UTF16", function () { | |
// NEW all printable UTF16 | |
var t = LZStringNew.compress(testStringAllUTF16); | |
}); | |
suite.on("cycle", function (evt) { | |
console.log(" - " + evt.target); | |
}); | |
suite.on("complete", function (evt) { | |
console.log(new Array(30).join("-")); | |
var results = evt.currentTarget.sort(function (a, b) { | |
return b.hz - a.hz; | |
}); | |
results.forEach(function (item) { | |
console.log((idx + 1) + ". " + item); | |
}); | |
}); | |
console.log("LZString old vs new compression short repetitive string #jsbench #jsperf"); | |
console.log(new Array(30).join("-")); | |
suite.run(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment