deflate-raw圧縮形式を高速展開するprogramをJavaScript風に書き散らかしておきます。高速化の肝は入力元をbit単位で切り出さず、byte単位でじゃんじゃか読み込む事です。しかし毎度2~3bytes参照するので最速必至でもないぞ。組み込み関数のDecompressionStream様には当然負けます。
//huffman符号表召喚
const getHC=(L,z)=>{
let b, c=0, i=z, m=16, C=new Uint16Array(m);
for(;i;)C[L[--i]]++;
for(;m--&&!C[m];);
if(m<0)return 0;
let N=new Uint16Array(m+1), l=1<<m, e;
for(;i<m;)N[i+1]=c=c+C[i++]<<1;
C=new Uint32Array(l);C.m=l-1;C.b=m;
//各要素の下位4bitsに符号長、上位bitsにhuffman符号に対応する記号格納
for(i=-1;++i<z;)if(l=L[i]){
for(c=0,e=N[b=l]++;b--;e>>>=1)c=c<<1|e&1;
for(b=1<<m-l,e=1<<l,l|=i<<4;b--;c+=e)C[c]=l
}return C
}
//展開処理
//@In: deflate-rawで圧縮された配列. Array, ArrayBuffer, Uint8Arrayに対応
//@oz: 展開後の大きさ. 不明なら省略
//@return: Uint8Array. @ozを省略するとlength < byteLength となり得る
function inflate(In,oz=0){
if(In instanceof ArrayBuffer||!(In instanceof Uint8Array))In=new Uint8Array(In);
let a=2,o=0;
const z=In.length,U=Uint8Array,
L=new U(320),//符号長配列
O=new U(oz>0?oz:oz=Math.max(32768,z*2+1024));//出力配列
//固定huffman符号構築
for(;o<144;)L[o++]=8;
for(;o<256;)L[o++]=9;
for(;o<280;)L[o++]=7;
for(;o<288;)L[o++]=8;
const FL=getHC(L,288),FD=getHC(new U(32).fill(5),32),
Lb=new Uint16Array(29), Le=new U(29),//一致長extra bits
Db=new Uint16Array(30), De=new U(30),//距離extra bits
ST=new U([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);
//extra bits構築
for(let b=0,c,d=0;b<30;De[b++]=c=b>4&&b-3>>1)
Lb[b]=b<4?++a:b<28?a+=1<<o:258,
Le[b]=o=b>7&&b<28&&b-4>>2, Db[b]=d+=1<<c;
//出力配列拡張
function grow(e){
if(e>oz){
for(;oz<e;)oz*=2;
e=new U(oz);e.set(O);O=e
}
}
function copy(d,l){
if(d<2)return O.fill(O[o-1],o,o+=l);
if(d>=l)return O.copyWithin(o,o-d,o-d+l,o+=l);
for(d=o-d;l--;)O[o++]=O[d++]
}
for(let b,c,d,e=a=o=0,l,t,u,T,U;!e;){
c=(In[b=a>>3]|In[++b]<<8)>>(a&7);
l=c>>1&3;e=c&1;a+=3;
if(!l){//stored block
if(c=a&7)a+=8-c;
l=In[b=a>>3]|In[++b]<<8;
if(l^~(In[++b]|In[++b]<<8)&65535)throw Error("inflate:stored block LEN/NLEN mismatch");
grow(o+l);O.set(In.subarray(++b,b+=l),o);o+=l;a=b*8;
continue
}
if(l<2)T=FL,U=FD;//固定huffman
else if(l<3){//可変huffman
c=(In[b=a>>3]|In[++b]<<8|In[++b]<<16)>>(a&7);
let i=0, h=(c&31)+257, k=(c>>5&31)+1;
l=(c>>10&15)+4;L.fill(0,0,19);
//huffman符号長解読
for(a+=14;i<l;a+=3)L[ST[i++]]=(In[b=a>>3]|In[++b]<<8)>>(a&7)&7;
T=getHC(L,19);l=h+k;t=T.m;
//literal + length + distanceの符号長構築
for(i=0;i<l;){
c=T[(In[b=a>>3]|In[++b]<<8)>>(a&7)&t];
a+=c&15;c>>>=4;
if(c<16)L[i++]=c;
else{
d=(In[b=a>>3]|In[++b]<<8)>>(a&7);b=0;
if(c<17)c=(d&3)+3,a+=2,b=L[i-1];
else if(c<18)c=(d&7)+3,a+=3;
else c=(d&127)+11,a+=7;
for(;c--;)L[i++]=b
}
}
T=getHC(L,h);U=getHC(L.subarray(h),k)
}
//LZ系列展開
for(t=T.m,u=U.m;;){
//literal or length
c=T[(In[b=a>>3]|In[++b]<<8|In[++b]<<16)>>(a&7)&t];
a+=c&15;c>>>=4;
if(c<256){grow(o+1);O[o++]=c;continue}
if(c<257)break;
//一致長
l=Lb[c-=257];
if(c>7)c=Le[c], l+=(In[b=a>>3]|In[++b]<<8)>>(a&7)&(1<<c)-1, a+=c;
//距離
c=U[(In[b=a>>3]|In[++b]<<8|In[++b]<<16)>>(a&7)&u];
a+=c&15;d=Db[c>>>=4];
if(c>3)c=De[c], d+=(In[b=a>>3]|In[++b]<<8|In[++b]<<16)>>(a&7)&(1<<c)-1, a+=c;
grow(o+l);copy(d,l)
}
}return O.subarray(0,o)
}
test
(async()=>{
var c=Uint8Array.from("He told me that that that that that boy said at that time is that that",a=>a.charCodeAt());
//deflate-rawで圧縮
c=await new Response(new Blob([c]).stream().pipeThrough(new CompressionStream("deflate-raw"))).arrayBuffer();
//伸長
c=inflate(c);
console.log(String.fromCharCode(...c))
})()
bit単位に切り出す版。仕様は前述同様なので、解説文省略
const getHC=(L,z)=>{
let b,c=0,i=z,m=16,C=new Uint16Array(m);
for(;i;)C[L[--i]]++;for(;m--&&!C[m];);
if(m<0)return 0;
let N=new Uint16Array(m+1),l=1<<m,e;
for(;i<m;)N[i+1]=c=c+C[i++]<<1;
C=new Uint32Array(l);C.mask=l-1;C.maxBits=m;
for(i=-1;++i<z;)if(l=L[i]){
for(c=0,e=N[b=l]++;b--;e>>>=1)c=c<<1|e&1;
for(b=1<<m-l,e=1<<l,l|=i<<4;b--;c+=e)C[c]=l
}return C
}
function inflate(In,oz=0){
if(In instanceof ArrayBuffer||!(In instanceof Uint8Array))In=new Uint8Array(In);
let a=2,B=0,b=0,o=0,L=new Uint8Array(320),z=In.length,O=new Uint8Array(oz>0?oz:oz=Math.max(32768,z*2+1024));
for(;o<144;)L[o++]=8;
for(;o<256;)L[o++]=9;
for(;o<280;)L[o++]=7;
for(;o<288;)L[o++]=8;
let FL=getHC(L,288),FD=getHC(new Uint8Array(32).fill(5),32),Lb=new Uint16Array(29),Le=new Uint8Array(29),Db=new Uint16Array(30),De=new Uint8Array(30),ST=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);
for(let c;b<30;De[b++]=c=b>4&&b-3>>1)
Lb[b]=b<4?++a:b<28?a+=1<<o:258,Le[b]=o=b>7&&b<28&&b-4>>2,Db[b]=B+=1<<c;
function grow(e){
if(e>oz){
for(;oz<e;)oz*=2;
e=new Uint8Array(oz);e.set(O);O=e
}
}
function readByte(){
if(b>7){
let v=B&255;
B>>>=8;b-=8;
return v
}return In[a++]
}
function copy(d,l){
if(d<2)return O.fill(O[o-1],o,o+=l);
if(d>=l)return O.copyWithin(o,o-d,o-d+l,o+=l);
for(d=o-d;l--;)O[o++]=O[d++]
}
for(a=b=o=B=0;25>b;b+=8)B|=In[a++]<<b;
for(let e,t,u,v,w,T,U;!e;){
if(b<3)B|=In[a++]<<b,b+=8;
let l=B>>1&3;e=B&1;B>>>=3,b-=3;
if(!l){
B>>>=l=b&7;b-=l;
l=readByte()|readByte()<<8;
if(l^~(readByte()|readByte()<<8)&65535)throw Error("inflate:stored block LEN/NLEN mismatch");
for(grow(o+l);l--;)O[o++]=In[a++];
continue
}
if(l<2)T=FL,U=FD;
else if(l<3){
for(L.fill(0,0,19);14>b;b+=8)B|=In[a++]<<b;
let i=0,HLIT=(B&31)+257,HDIST=(B>>5&31)+1;
l=(B>>10&15)+4;B>>>=14;
for(b-=14;i<l;L[ST[i++]]=B&7,B>>>=3,b-=3)if(b<3)B|=In[a++]<<b,b+=8;
T=getHC(L,19);l=HLIT+HDIST;
t=T.mask;
for(i=0;i<l;){
if(b<7)B|=In[a++]<<b,b+=8;
let c=T[B&t],n=c&15;
B>>>=n;b-=n;c>>>=4;
if(c<16)L[i++]=c;
else{
if(c<17){
if(b<2)B|=In[a++]<<b,b+=8;
c=3+(B&3),B>>>=2,b-=2;n=L[i-1]
}else if(n=0,c<18){
if(b<3)B|=In[a++]<<b,b+=8;
c=3+(B&7),B>>>=3,b-=3
}else{
if(b<7)B|=In[a++]<<b,b+=8;
c=11+(B&127),B>>>=7,b-=7
}
for(;c--;)L[i++]=n
}
}
T=getHC(L,HLIT);U=getHC(L.subarray(HLIT),HDIST)
}
t=T.mask,v=T.maxBits;u=U.mask,w=U.maxBits;
for(;;){
for(;v>b;b+=8)B|=In[a++]<<b;
let c=T[B&t],n=c&15;
B>>>=n;b-=n;c>>>=4;
if(c<256){grow(o+1);O[o++]=c;continue}
if(c<257)break;
l=Lb[c-=257];
if(c>7){c=Le[c];if(c>b)B|=In[a++]<<b,b+=8;l+=B&(1<<c)-1;B>>>=c;b-=c}
for(;w>b;b+=8)B|=In[a++]<<b;
c=U[B&u],n=c&15;
B>>>=n;b-=n;c>>>=4;n=Db[c];
if(c>3){for(c=De[c];c>b;b+=8)B|=In[a++]<<b;n+=B&(1<<c)-1;B>>>=c;b-=c}
grow(o+l);copy(n,l)
}
}return O.subarray(0,o)
}
参考文献様
高速で機能も豊富