Uint8Array を操作してビットを読み書きする方法について同様の質問をしたところ、最終結果としてこれを示す素晴らしい回答が得られました。
function createBitOrder(fromLeft, bits) {
const bitMasks = [];
var i = bits;
while (i-- > 0) { bitMasks.push(fromLeft ? (1 << i) : (1 << (bits - 1 - i))) }
return bitMasks;
}
function BitStream(mask) {
var pos = 0, buf = [];
const maskOut = mask.map(v => 0xFF ^ v);
return {
set pos(p) { pos = p },
set buf(data) { buf = data },
get bit() { return (buf[pos >> 3] & mask[pos++ % 8]) ? 1 : 0 },
set bit(val) {
const byte = pos >> 3, bit = pos ++ % 8;
buf[byte] = (buf[byte] & maskOut[bit]) + (val ? mask[bit] : 0);
},
writeTo(stream, bits) { while (bits-- > 0) { stream.bit = this.bit } },
clear(bits) { while (bits-- > 0) { this.bit = 0 } },
};
}
// replacement functions
function BitMovers() {
const mask = createBitOrder(true, 8); // high to low
const read = BitStream(mask);
const write = BitStream(mask);
return {
moveBits(fromBuf, fromBit, numBits, toBuf, toBit) {
read.buf = fromBuf;
write.buf = toBuf;
read.pos = fromBit;
write.pos = toBit;
read.writeTo(write, numBits);
},
clearBits(buf, fromBit, numBits) {
write.buf = buf;
write.pos = fromBit;
write.clear(numBits);
},
}
}
const movers = BitMovers();
const bufA = [255,0,255,0];
const bufB = [0,0,0,0];
movers.moveBits(bufA, 4, 8, bufB, 20);
movers.clearBits(bufB, 1, 6);
const readStream = BitStream(createBitOrder(true, 8));
const writeStream = BitStream(createBitOrder(true, 8));
function bufGet(bufA, readPos, bits, bufB, writePos) {
readStream.buf = bufA; // set buffers
writeStream.buf = bufB;
readStream.pos = readPos; // set read and write positions
writeStream.pos = writePos;
while (bits-- > 0) { writeStream.bit = readStream.bit }
}
const f11 = [0b10111001, 0b10111001, 0b10111001];
const f12 = [0, 0, 0];
bufGet(f11, 1, 16, f12, 0);
これは、「ビットストリーム」を適切に処理する方法について必要なことを正確に教えてくれます。私はそれを概念とは考えていませんでした。その質問に対する私の解決策は、理想とはほど遠いものでした。
ただし、一度に1 ビットで動作します。これは、私の状況では最適ではないようです。
たとえば、次のようなエンコーディングがあるとします。
<2-bit-marker>
<8-bit-value>
<32-bit-value>
<arbitrary-multiple-of-2-bit-value>
したがって、次の可能性があります。
10
10101010
10101010101010101010101010101010
1010101010101010101010101010101010101010101010101010101010101010
それらのそれぞれで常に 2 の倍数です。最初の 3 つの値は 4 番目の値のメタデータであるため、最初の 2 ビット チャンクを読み取り、次に次の 8 ビットを読み取り、次に次の 32 ビットを読み取る必要があります。32 ビット値が、次の 4 番目の値に含まれるビット数を示しているとします。4 番目の値には、その数のビットが含まれ、2 の倍数になるようにパディングされます。
基本的に、その 4 番目のセクションを Uint32Array (または別の Uint32Array) の別のセクションにコピーし、おそらくコピーされたバージョンを2 のべき乗でデータをチャンク化するように制限します。したがって、1 つのチャンクに数メガバイトのデータがある場合、それは 4096 ビットのチャンクにコピーされて保存されます。2 の任意の累乗になる可能性がありますが、この質問をあまり複雑にしたくありません。本質は、私たちがしなければならないことです:
- すべての意図と目的のために、データの 4 番目のセクションをメモリ内の新しい場所、または別の Uint32Array にコピーします。と、
- 同時に、4 番目のセクションを処理し、チャンクアップする可能性があります (最大サイズ 4096 ビットのチャンクにするとしましょう)。これを回答に含める必要はありませんが、受け取ったデータに対して何らかの処理が行われることを知っておいてほしいだけです。それが要点です。
そのため、単純なストリーミング コピー操作は理想的ではない場合があります。しかし、ストリームするものは、4 番目のチャンクから一度に 32 ビットとしましょう。3 番目のチャンクからのサイズ情報を使用して、4 番目のチャンクのトラバースをいつ停止するかを判断し、32 ビット チャンクをマイクロ処理して確実に処理しないようにします。テールエンドで遠すぎたり短すぎたりしないでください...それはうまくいくかもしれません.
これを JavaScript で最適に行うにはどうすればよいでしょうか?
上記のBitStreamクラスは一度に 1 ビットを読み取りますが、これは理想的とは言えません。あちこちに奇数ビットを使用した非常に複雑なエンコードがあれば、それが有利になることがわかります。しかし、この例についてどう思いますか? どのように最適化しますか?
データの 4 番目のセグメントがどのくらいの長さかわからないため、任意の場所で読み取る必要があります。たとえば、次のようなものがあるとします。
<2-bit><8-bit><16-bit><48-bit>
<2-bit><8-bit><16-bit><12-bit>
<2-bit><8-bit><16-bit><100000-bit>
<2-bit><8-bit><16-bit><20-bit>
<2-bit><8-bit><16-bit><1000-bit>
<2-bit><8-bit><16-bit><10000-bit>
<2-bit><8-bit><16-bit><64-bit>
すべてがシーケンスに圧縮されます。Uint32Arrayからこれらの大きなチャンクを読み取り、それらをメモリの別の部分に配置するための良いアプローチは何でしょうか? 私はビット操作のベストプラクティスに慣れていないので、これが現実の世界でどのように見えるかを理解しようとしています.
上記の例を JavaScript で簡単に実装すると、どのようになりますか? あるいは、この一度に 1 ビットのアプローチが理想的であると思われる場合は、それも機能します。アプリケーションのこの部分が高度に最適化されていることを確認したいだけです。これは、特に「ビット ストリーミング」の (リンクされた) 醜い実装ではボトルネックであるためです。