在 Java 中,是否有内置或常见的 Stream 允许输出,比如说,5 位数据?

In Java, is there a built-in or common kind of Stream that allows outputting, say, 5-bit data?

现在我有一个小程序试图有效地将消息分成几部分,计算需要将字符单独附加到 OutputStream,通常是 BAOS,然后执行类似 byte[] packed = packData(baos) 并根据打包大小计算大小。这个打包步骤是必要的,因为我在 baos.write(my5bitbyte).

时浪费了一些位

所以在打包步骤中,我通常会这样做:

  1. baos.toByteArray()
  2. 的字节中取出一个位集
  3. 为构造字节设置新位
  4. 从每个字节中取出位 0-4 并以明显的方式将它们附加到新的位集
  5. 从新的位集创建一个 byte[],填充最后一个字节的最后 7 位

我的问题是:

有什么方法或类似 BitOutputStream 的东西吗?或者类似的东西?我目前这样做的方式似乎很愚蠢,我肯定可以更聪明,但我想知道我是否忽略了一些已经存在的东西。

编辑 在检查了 ByteArrayOutputStream 的来源后,很明显它可以用完全相同的方式对某些 BitArrayOutputStream 实施,因为它是只是一个 byte[] 封装了一些花哨的东西,所以你可以做 boolean[]。但我认为它不存在,现在我进一步研究它,所以我的问题就变成了......

那么这是实现 BitArrayOutputStream 的合理方式吗?

class FixedLengthBitArrayOutputStream extends OutputStream {

    private boolean[][] buffer;
    private final int originalLength;
    private final int bitLength;
    private int position = 0;
    private int expansions = 0;

    FixedLengthBitArrayOutputStream(short bitLength, short length) {
        this.buffer = new boolean[length][bitLength];
        this.originalLength = length;
        this.bitLength = bitLength;
    }

    private int limitBeforeExpansion(double factor) {
        return Math.max(
                (int) Math.floor(factor * buffer.length),
                (int) Math.floor( (1 - Math.pow(factor, expansions + 1)) * buffer.length)
        );
    }

    private boolean needsExpansion() {
        return position > limitBeforeExpansion(0.8);
    }

    private void expandIfNecessary() {
        if (needsExpansion()) {
            expansions++;
            this.buffer = Arrays.copyOf(this.buffer, (int) Math.pow((double)this.originalLength, expansions + 1));
        }
    }

    public boolean[] bitValue(int number) throws IllegalStateException {

        int remainder = number;
        boolean[] bits = new boolean[this.bitLength];

        for (int i = this.bitLength - 1; i >= 0; i--) {
            int power = (int) Math.pow(2, i + 1);
            boolean value = remainder > power;
            bits[i] = value;
            if (value) {
                remainder -= power;
            }
        }

        if (remainder != 0)
            throw new IllegalStateException("whoa");

        return bits;
    }

    @Override
    public void write(int b) throws IOException, IllegalStateException {
        expandIfNecessary();

        this.buffer[position] = bitValue(b);
        position++;
    }

    public byte[] toByteArray() {

        BitSet bitSet = new BitSet(this.position * this.bitLength);

        for (int i = 0; i < position; i++) {
            boolean[] bits = this.buffer[i];

            for (int j = 0; j < bits.length; j++) {
               bitSet.set( i * bits.length + j , bits[j] ); 
            }
        }

        return bitSet.toByteArray();
    }
}

一种自然的方法是编写一个单独的输出流 class 链接(或包装)另一个输出流(与链接写入器、缓冲流和非缓冲流的方式相同)。

代码可能与此类似。我缓冲了几位直到它达到一个完整的字节并将其写入输出流。我还没有测试过。因此它可能包含一两个错误。

class PackedBitsOutputStream {

    private OutputStream outputStream;
    private int numBufferedBits;
    private byte bufferedBits;

    PackedBitsOutputStream(OutputStream os) {
        outputStream = os;
    }

    void writeBitSet(int data, int relevantBits) {
        bufferedBits = (byte) (bufferedBits | (data << bufferedBits));
        numBufferedBits += relevantBits;
        if (numBufferedBits >= 8) {
            outputStream.write(bufferedBits);
            numBufferedBits -= 8;
            bufferedBits = (byte) (data >> (relevantBits - numBufferedBits));
        }
    }

    void flush() {
        outputStream.write(bufferedBits);
        bufferedBits = 0;
        numBufferedBits = 0;
        outputStream.flush();
    }

    void close() {
        flush();
        outputStream.close();
    }
}

注意:writeBitSet目前一次最多可以写入8位。