0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptで高速inflate

0
Last updated at Posted at 2026-06-21

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)
}

参考文献様

高速で機能も豊富

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?