Использование Java BitSet и байт []

У меня есть это приложение, где я должен использовать класс BitSet и записывать в файл по частям. Я знаю, что не могу записать биты в файл, поэтому сначала я преобразовываю объект BitSet в массив байтов и пишу в качестве байтового массива. Но проблема в том, что класс BitSet, индексированный из right to left, когда я преобразовываю объект BitSet в массив байтов и записываю в файл, он записывает обратную ссылку.

Например, это мой объект BitSet:

10100100

и BitSet.get(0) дает false, а BitSet.get(7) дает значение true. Я хочу записать это в файл, например:

00100101

поэтому первый бит будет равен 0, а последний бит будет равен 1.

Мой метод конвертации:

public static byte[] toByteArray(BitSet bits) 
{
    byte[] bytes = new byte[(bits.length() + 7) / 8];       
    for (int i = 0; i < bits.length(); i++) {
        if (bits.get(i)) {
            bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8);
        }
    }
    return bytes;
}

Мой метод записи:

    FileOutputStream fos = new FileOutputStream(filePath);
    fos.write(BitOperations.toByteArray(cBitSet));
    fos.close();

Предполагается ли это так, или я делаю что-то неправильно? Спасибо.

Ответ 1

BitSet имеет несколько проблем:

  • длина массива байтов, который он предоставляет на выходе, с использованием .toByteArray(), зависит от самого верхнего бита, установленного в 1 (0, если бит не установлен, 1, если последний бит установлен равным < 8, 2, если < 16 и т.д. - по существу, indexOf(highestBitSet) + 7) / 8);
  • как таковой, вы не можете полагаться на него для вычисления битовой маски фиксированной длины.

Рассмотрим вместо этого вместо обложки ByteBuffer. Пример кода ниже.

Примечание: для построения используется "статические методы factory", поэтому для создания нового экземпляра вам потребуется использовать либо BitFlags.withByteLength(), либо BitFlags.withBitLength(). Разумеется, вы можете разработать свои собственные методы или просто сделать конструктор общедоступным. Чтобы получить базовый массив, вызовите .toByteArray().

public final class BitFlags
{
    private final int nrBytes;
    private final ByteBuffer buf;

    private BitFlags(final int nrBytes)
    {
        if (nrBytes < 1)
            throw new IllegalArgumentException("need at least one byte");
        this.nrBytes = nrBytes;
        buf = ByteBuffer.allocate(nrBytes);
    }

    public static BitFlags withByteLength(final int nrBytes)
    {
        return new BitFlags(nrBytes);
    }

    public static BitFlags withBitLength(final int nrBits)
    {
        return new BitFlags((nrBits - 1) / 8 + 1);
    }

    public void setBit(final int bitOffset)
    {
        if (bitOffset < 0)
            throw new IllegalArgumentException();

        final int byteToSet = bitOffset / 8;
        if (byteToSet > nrBytes)
            throw new IllegalArgumentException();

        final int offset = bitOffset % 8;
        byte b = buf.get(byteToSet);
        b |= 1 << offset;
        buf.put(byteToSet, b);
    }

    public void unsetBit(final int bitOffset)
    {
        if (bitOffset < 0)
            throw new IllegalArgumentException();

        final int byteToSet = bitOffset / 8;
        if (byteToSet > nrBytes)
            throw new IllegalArgumentException();

        final int offset = bitOffset % 8;
        byte b = buf.get(byteToSet);
        b &= ~(1 << offset);
        buf.put(byteToSet, b);
    }

    public byte[] toByteArray()
    {
        return buf.array();
    }
}

Ответ 2

BitSet реализует Serializable. Если вам нужно только восстановить BitSet в Java и не нужно иначе проверять его состояние в файле, вы должны просто сказать ему, чтобы сохранить его в файле.

Если вы хотите записать его в файл, содержащий другие, несериализованные данные, вы можете записать его в ByteArrayOutputStream и извлечь из него байт []. Тем не менее, вы, вероятно, получите лучшую производительность, записывая непосредственно в файл.

Ответ 3

Это выглядит разумным для меня. Это будет не очень быстро, но это должно сработать. Если вы хотите, чтобы он записывал биты в обратном порядке, просто измените индексирование и сдвиг:

byte[] bytes = new byte[(bits.length() + 7) / 8];       
for (int i = 0; i < bits.length(); i++) {
    if (bits.get(i)) {
        bytes[i / 8] |= 1 << (7 - i % 8);
    }
}

или даже:

        bytes[i / 8] |= 128 >> (i % 8);

Если ваш битсет довольно разрежен (или, возможно, даже если это не так), то только повторение по 1 битам может быть более эффективным:

byte[] bytes = new byte[(bits.length() + 7) / 8];
for ( int i = bits.nextSetBit(0); i >= 0; i = bits.nextSetBit(i+1) ) {
    bytes[i / 8] |= 128 >> (i % 8);
}

Если вам нужна более высокая скорость для плотных битов, вы можете попробовать использовать стандартный метод BitSet.toByteArray(), а затем использовать бит-трюки для обратного преобразования бит в отдельных байтах:

byte[] bytes = bits.toByteArray();
for ( int i = 0; i < bytes.length; i++ ) {
    byte b = bytes[i];
    b = ((b & 0x0F) << 4) | ((b & 0xF0) >> 4);
    b = ((b & 0x33) << 2) | ((b & 0xCC) >> 2);
    b = ((b & 0x55) << 1) | ((b & 0xAA) >> 1);
    bytes[i] = b;
}