Получить диапазон битов из массива байтов

У меня есть 16-байтовый массив байтов с различными значениями, заполненными внутри него. Эти значения могут и пересекают границу байта.

Вот двоичное представление моих данных

0011 | 0011 | 1110 | 1000 | 1100 | 1000 | 1100 | 1000 | 0100 | 1101 | 0010 | 1111 | 1010 | 0001 | 1111 | 1111 [ 0 - 63]

0000 | 0101 | 0000 | 0011 | 0000 | 0011 | 1110 | 1000 | 1100 | 1000 | 1100 | 1000 | 0000 | 0001 | 0010 | 1100 [64 -127]

Различные значения, которые мне нужно получить, хранятся в следующих битовых позициях.

0-3 | 4-5 | 6-15 | 16-23 | 24-31 | 32 - 63 | 64-71 | 72-79 | 80-83 | 84-85 | 86-94 | 96-103 | 104-111 | 112-127

КОД:

Вот код для заполнения массива байтов:

protected byte[] createMessageHeader(byte[] content) {
    int[] set = new int[128];
    set = intToBinary(set, 3, 3);
    set = intToBinary(set, 0, 5);
    set = intToBinary(set, 1000, 15);
    set = intToBinary(set, 200, 23);
    set = intToBinary(set, 200, 31);
    set = intToBinary(set, 1294967295, 63);
    set = intToBinary(set, 5, 71);
    set = intToBinary(set, 3, 79);
    set = intToBinary(set, 0, 83);
    set = intToBinary(set, 0, 85);
    set = intToBinary(set, 1000, 95);
    set = intToBinary(set, 200, 103);
    set = intToBinary(set, 200, 111);
    set = intToBinary(set, 300, 127);

    BitSet bitSet = binArrayToBitset(set);
    byte[] b1 = bitSet.toByteArray();

    for(int i = 0; i < b1.length; i++) {
        b1[i] = (byte) (Integer.reverse(b1[i]) >>> 24);
    }

    return b1;
}

protected int[] intToBinary(int[] binary, int val, int start) {
        // Number should be positive
        while (val > 0) {
            binary[start--] = val % 2;
            val = val / 2;
        }
        return binary;
    }
protected BitSet binArrayToBitset(int[] binArray) {
        BitSet set = new BitSet(128);

        for(int i = 0; i < binArray.length; i++) {
            if (binArray[i] != 0)
                set.set(i);
        }
        return set;
    }
//Convenience method to print binary representation of values
protected void toBinString(int[] set) {
        StringBuilder stringBuilder = new StringBuilder();
        for(int i = 0; i < set.length; i++) {
            if (i % 4 == 0)
                stringBuilder.append("|");
            if (i % 64 == 0)
                stringBuilder.append("\n");
            stringBuilder.append(set[i]);
        }
    }


    

Приведенный выше код должен создавать массив байтов с указанными значениями в определенных диапазонах битовых индексов.


Я пробовал множество методов для получения этих значений, совсем недавно:

private int extractBits2(byte[] header, int start, int end) {
    BitSet bitSet = BitSet.valueOf(header);
    return (int) bitSet.get(start, end + 1).toLongArray()[0];
}

Используя вышеуказанный метод, если я вызову его с помощью:

int извлечено = ExtractBits2 (заголовок, 6, 15)

Возвращаемое значение — «0b00000011_10100000» (окно IntelliJ Debugger), но я думал, что оно вернет «0b0011 1110 1000» (int 1000).

Что мне здесь не хватает? Мне нужно иметь возможность получить диапазон битов, чтобы я мог проверить их значения.

ПРИМЕЧАНИЕ: все сохраненные значения являются целыми числами, за исключением одного, которое представляет собой значение метки времени (длинное).

Можете ли вы опубликовать полный код, чтобы воспроизвести это, включая определение вашего массива входных байтов?

Thomas Timbul 08.12.2022 15:24

Вероятно, проще всего рассматривать все это как строку. Ваши диапазоны x-y могут быть простой операцией подстроки, за которой следует двоичный файл синтаксического анализа.

g00se 08.12.2022 16:35

Обратите внимание, я добавил пример для заполнения массива байтов @ThomasTimbul

millerbill3 09.12.2022 10:26

Извините, но это не компилируется. set = intToBinary(set, 0); отсутствует аргумент «старт». Параметр 'content' не используется. set = intToBinary(set, 1000, 95); не совпадает с "началом" в конце одной из заявленных вами позиций (должно быть 94?) Может быть, немного подправить, и если есть ошибка, она может проявиться при этом? Учитывая довольно сложные средства настройки этого массива, можно ли это упростить?

Thomas Timbul 09.12.2022 11:06

Извините... У меня была ошибка копирования/вставки/редактирования фрагмента intToBinary(set, 0). Я обновил свой пример кода. Если есть более простой способ заполнить byteArray, я весь в ушах.

millerbill3 09.12.2022 11:39
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
5
131
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

IIrc Bitset.get берет только диапазон битов из байта, в котором находится начальный индекс. Что вы могли бы сделать, так это перебрать массив заголовков и извлечь нужные биты из каждого байта. Например, вы можете сделать что-то вроде этого:

private int extractBits(byte[] header, int start, int end) {
// First, figure out which byte in the header array the start and end indices are in
int startByte = start / 8;
int endByte = end / 8;

// Next, calculate the indices of the start and end bits within the start and end bytes
int startBit = start % 8;
int endBit = end % 8;

// Initialize a result variable to 0
int result = 0;

// If the start and end indices are in the same byte, we can just use the BitSet.get()
// method to extract the bits that we want
if (startByte == endByte) {
    BitSet bitSet = BitSet.valueOf(new byte[]{header[startByte]});
    result = (int) bitSet.get(startBit, endBit + 1).toLongArray()[0];
} else {
    // If the start and end indices are in different bytes, we need to do some additional
    // processing to extract the bits that we want.

    // First, create a new array that contains only the bytes that we need to extract bits from
    byte[] bytes = Arrays.copyOfRange(header, startByte, endByte + 1);

    // Next, create a BitSet from the bytes array
    BitSet bitSet = BitSet.valueOf(bytes);

    // Then, get the bits from the start index to the end of the start byte and add them to the result
    BitSet startBits = bitSet.get(startBit, 8);
    result = (int) startBits.toLongArray()[0];

    // Next, shift the bits in the result variable to the left by the number of bits that we've already extracted
    result <<= 8 - startBit;

    // Then, get the bits from the beginning of the end byte to the end index and add them to the result
    BitSet endBits = bitSet.get(0, endBit + 1);
    result |= (int) endBits.toLongArray()[0];
}

// Finally, return the result
return result;  }
Ответ принят как подходящий

Другие могут быть волшебниками чисел и математики, но мне лично было трудно рассуждать об этом, так как когда ваш BitSet печатался по крупицам, он вообще не соответствовал ожидаемому представлению. Оно оказалось перевернутым. Это согласуется с моим замечанием о том, насколько сложным оказалось создание входных данных, и, поскольку это в значительной степени симметричная операция, повторное извлечение информации будет таким же сложным.

В следующем подходе я попытался использовать встроенные методы для преобразования двоичных представлений в десятичные, а также для работы с массивом, что позволило избежать математических ошибок и иметь возможность легко рассуждать о выводе отладчика:

Это служебный метод для распечатки любого BitSet в двоичном побайтовом представлении, которое вы использовали:

/**
 * Print a bitset to stdout in binary notation, separating bytes with a pipe '|'.
 * @param bitSet
 */
private static void printBitSetByteWise(BitSet bitSet) {
    for(int i = 0; i< bitSet.size(); i++) {
        if (i>0 && i%4==0) System.out.print('|');
        System.out.print(bitSet.get(i) ? 1 : 0);
    }
    System.out.println();
}

Модифицированный метод извлечения, который преобразует массив байтов в BitSet, превращает его обратно в беззнаковую двоичную строку, а затем анализирует ее, используя Integer.parseInt с основанием 2 для двоичного кода:

/**
 * Extracts an integer formed from the given indices in an array of bytes.
 */
private int extractBits2(byte[] header, int start, int endInclusive) {
    BitSet bitSet = BitSet.valueOf(header);
    final BitSet subset = bitSet.get(start, endInclusive + 1);
    //create a String representation
    final int length = endInclusive - start + 1;
    StringBuilder b = new StringBuilder(length);
    for(int i = 0; i < length; i++) {
        b.append(subset.get(i)?'1':'0');
    }
    //parse using radix 2
    return Integer.parseInt(b.toString(), 2);
}

При создании входного массива нет необходимости передавать аргумент, а поскольку вы работаете непосредственно с самим массивом, нет смысла переназначать его самому себе на каждом шаге. Наконец, я удалил инверсионную часть внизу:

protected byte[] createMessageHeader() {
    int[] set = new int[128];
    integrate(set, 3, 3);
    integrate(set, 0, 5);
    integrate(set, 1000, 15);
    integrate(set, 200, 23);
    integrate(set, 200, 31);
    integrate(set, 1294967295, 63);
    integrate(set, 5, 71);
    integrate(set, 3, 79);
    integrate(set, 0, 83);
    integrate(set, 0, 85);
    integrate(set, 1000, 94);
    integrate(set, 200, 103);
    integrate(set, 200, 111);
    integrate(set, 300, 127);
    
    BitSet bitSet = binArrayToBitset(set);
    
    return bitSet.toByteArray();
}

Здесь, вместо деления, вставки в обратном порядке, а затем инвертирования, я просто использую двоичное строковое представление (любезно предоставленное встроенным методом JDK Integer.toBinaryString), преобразовываю его в его цифры 1 и 0 и arraycopy их в правильное положение:

/**
 * Inserts the given value into the given int array, right-aligning its
 * binary representation to the given index within the array.
 */
protected void integrate(int[] binary, int value, int alignEndToIndex) {
    String binaryRepresentation = Integer.toBinaryString(value);
    int[] digits = numberStringToArrayOfDigits(binaryRepresentation);
    System.arraycopy(digits,0,binary, alignEndToIndex+1-digits.length, digits.length);
}

/**
 * Convert a String, which is assumed to represent a number, to
 * an integer array containing its individual digits.
 */
protected int[] numberStringToArrayOfDigits(String binaryRepresentation) {
    int[] digits = new int[binaryRepresentation.length()];
    for (int i = 0; i < binaryRepresentation.length(); i++) {
        digits[i] = binaryRepresentation.charAt(i) - '0';
    }
    return digits;
}

А это то же самое, что и у вас:

protected BitSet binArrayToBitset(int[] binArray) {
    BitSet set = new BitSet(128);
    
    for(int i = 0; i < binArray.length; i++) {
        if (binArray[i] != 0)
            set.set(i);
    }
    return set;
} 

Собираем все вместе:

    final byte[] header = createMessageHeader();
    BitSet bitSet = BitSet.valueOf(header);
    printBitSetByteWise(bitSet); //just to verify contents are as expected
    //0011|0011|1110|1000|1100|1000|1100|1000|0100|1101|0010|1111|1010|0001|1111|1111|0000|0101|0000|0011|0000|0111|1101|0000|1100|1000|1100|1000|0000|0001|0010|1100

    System.out.println(extractBits2(header, 6, 15)); //1000
    System.out.println(extractBits2(header, 32, 63)); //1294967295
    System.out.println(extractBits2(header, 104, 111)); //200

Спасибо @ThomasTimbul!!! Это было очень полезно. Это упростило мой код и сделало его намного более понятным.

millerbill3 09.12.2022 14:14

Я не знаю, поможет ли это. Это позволяет, по крайней мере, легко изменять и параметризовать ваши требования. Я не совсем уверен, как ваши данные попадают в первую очередь, но должно быть довольно легко получить их в «двоичную строку». Я использовал здесь класс кортежа (которого, к сожалению, не хватает в JRE), но нет большой необходимости обращаться к третьей стороне, если вы не хотите:

package com.technojeeves.tuples;

import org.javatuples.Pair;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class App {
    public static void main(String[] args) {

        String data = """
                0011 | 0011 | 1110 | 1000 | 1100 | 1000 | 1100 | 1000 | 0100 | 1101 | 0010 | 1111 | 1010 | 0001 | 1111 | 1111
                0000 | 0101 | 0000 | 0011 | 0000 | 0011 | 1110 | 1000 | 1100 | 1000 | 1100 | 1000 | 0000 | 0001 | 0010 | 1100
                """;

        String bitsWanted = "0-3, 4-5, 6-15, 16-23, 24-31, 32-63, 64-71, 72-79, 80-83, 84-85, 86-94, 96-103, 104-111, 112-127";

        List<Pair<Integer, Integer>> tuples = App.getTuples(bitsWanted);
        List<Integer> values = App.bitsStringToIntegersList(data, tuples);

        int ix = 0;
        for (Integer b : values) {
            System.out.printf("Bits %s = 0x%02X%n", tuples.get(ix).getValue0() + "-" + tuples.get(ix++).getValue1(), b);
        }
    }


    private static List<Pair<Integer, Integer>> getTuples(String tupleString) {
        List<Pair<Integer, Integer>> result = new ArrayList<>();
        String[] tuples = tupleString.split("\\s*,\\s*");
        for(String tuple : tuples) {
            String[] startEnd = tuple.split("-");
            result.add(new Pair(Integer.valueOf(startEnd[0]), Integer.valueOf(startEnd[1])));
        }
        return result;
    }




    private static List<Integer> bitsStringToIntegersList(String bits, List<Pair<Integer, Integer>> tuples) {
        List<Integer> result = new ArrayList<>();
        bits = bits.replaceAll("[^01]", "");
        for (Pair<Integer, Integer> startEnd : tuples) {
            int start = startEnd.getValue0();
            int end = startEnd.getValue1();
            result.add(Integer.valueOf(bits.substring(start, end), 2));
        }
        return result;
    }
}

Выход:

Bits 0-3 = 0x01
Bits 4-5 = 0x00
Bits 6-15 = 0x1F4
Bits 16-23 = 0x64
Bits 24-31 = 0x64
Bits 32-63 = 0x2697D0FF
Bits 64-71 = 0x02
Bits 72-79 = 0x01
Bits 80-83 = 0x00
Bits 84-85 = 0x00
Bits 86-94 = 0xFA
Bits 96-103 = 0x64
Bits 104-111 = 0x64
Bits 112-127 = 0x96

Другие вопросы по теме