Я новичок в Java и начал реализовывать отправитель UDP с помощью BitSet и ByteBuffer, по какой-то причине я получаю поведение, которого не ожидал.
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.BitSet;
public class Main
{
public static void main(String[] args) {
ByteBuffer out = ByteBuffer.allocate(2);
BitSet byt = new BitSet(8);
byt.set(0, true);
byt.set(1, false);
out.put(byt.toByteArray());
byt.set(0, true);
byt.set(1, false);
byt.set(2, true);
out.put(byt.toByteArray());
System.out.println("First byte is " + out.array()[0]+ " second is " + out.array()[1]);
}
}
где я получаю результат
First byte is 1 second is 5
что я считаю неправильным, поскольку порядок байтов неправильный
Когда я пытаюсь запустить это:
public class Main
{
public static void main(String[] args) {
ByteBuffer out = ByteBuffer.allocate(2);
BitSet byt = new BitSet(8);
byt.set(0, false);
byt.set(1, false);
out.put(byt.toByteArray());
byt.set(0, true);
byt.set(1, false);
byt.set(2, true);
out.put(byt.toByteArray());
System.out.println("First byte is " + out.array()[0]+ " second is " + out.array()[1]);
}
}
Вывод изменится на
First byte is 5 second is 0
Я считаю, что это правильный ответ.
Обратите внимание, что изменение произошло только в строке 7, при этом порядок байтов также изменился.
Быстрый тест здесь и здесь
Я новичок в Java. Так что все это может быть большим недоразумением. Спасибо, в любом случае!
Почему вы думаете, что «порядок байтов неправильный»? Ожидаете ли вы, что при добавлении байта в буфер этот байт появится в более высокой позиции (большем индексе)? javadoc для allocate() : «Позиция нового буфера будет равна нулю» и для put(): «Записывает заданный байт в этот буфер в текущей позиции, а затем увеличивает позицию».
И, кстати, в Java вы можете легко записывать двоичные литеральные числа, например 0b0101 (или (byte)0b0101, если необходимо, в виде байта), нет необходимости использовать BitSet




Я считаю, что это правильный ответ.
Нисколько; вы неверно истолковываете то, что видите, ваш тест не особенно хорошо написан, чтобы это продемонстрировать.
Вот что на самом деле происходит:
BitSet byt = new BitSet(8);
byt.set(0, false);
byt.set(1, false);
out.put(byt.toByteArray());
Это ничего не пишет. Добавьте System.out.println(out.position());, чтобы увидеть это в действии.
Это потому, что спецификация BitSet полностью игнорирует размер вашего битового набора (8 в new BitSet(8) не влияет на то, что делает .toByteArray()), вместо этого он проверяет самый старший 1 бит в вашем битовом наборе и округляет его до ближайшего целого числа, делящегося на 8, и именно столько бит излучается. Учитывая, что ваш второй фрагмент содержит нулевые байты, для его представления достаточно «0 бит», и, следовательно, .toByteArray() послушно создает массив нулевой длины. И buffer.put(someZeroLenArray); послушно делает именно то, о чем вы его просите, а именно: ничего не делать и успешно. Не вносите изменений в байтовый буфер и не перемещайте позицию.'.
Так что вы:
Вы явно думаете, что цифра «5», которую вы видите, принадлежит второму BitSet, а 0, который вы видите, — из первого.
Порядок байтов правильный (например, порядок байтов, который вы получаете, равен [A] именно тому, что вы получаете в спецификации Java, а [B] соответствует всем вариантам использования порядка байтов, в частности, включая сетевые стандарты, за исключением чипов Intel); ваши ожидания относительно того, какой порядок байтов вы ожидаете, неверны, и все, что вам нужно сделать, это скорректировать это.
В более общем плане порядок байтов здесь даже не применяется: порядок байтов — это концепция, которая применяется, когда вы пишете вещи длиной более 8 бит, но никогда этого не делаете.
Это пример порядка байтов:
ByteBuffer bb = ByteBuffer.allocate(4);
bb.putInt(1);
System.out.println("At pos 0: " + bb.get(0)); // 0
System.out.println("At pos 3: " + bb.get(3)); // 1
bb = ByteBuffer.allocate(4);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.putInt(1);
System.out.println("At pos 0: " + bb.get(0)); // 1
System.out.println("At pos 3: " + bb.get(3)); // 0
Другими словами, учитывая, что «int» требует записи 4 байтов, какой из 4 байтов, составляющих int, мы пишем первым — тот, у которого самые младшие цифры (1 здесь — «1» в терминах int — это 00000000001 из конечно), или тот, у которого больше всего (0)? При использовании LITTLE_ENDIAN первой печатается цифра 1.
Вставленный вами фрагмент кода не имеет порядка байтов. Это ни младший, ни большой порядковый номер. Биты имеют формат с прямым порядком байтов, но биты с прямым порядком байтов — это то, как это делается для всех систем и всех процессоров, о которых сегодня уместно говорить.
BitSet.toByteArray() создает массив байтов минимальной длины, необходимой для представления BitSet. Вы можете прочитать в документах:
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/BitSet.html#toByteArray()
byte[] bytes = s.toByteArray();
then bytes.length == (s.length()+7)/8 and
s.get(n) == ((bytes[n/8] & (1<<(n%8))) != 0)
for all n < 8 * bytes.length.
и s.length():
Возвращает «логический размер» этого BitSet: индекс старшего установленного бита в BitSet плюс один. Возвращает ноль, если BitSet не содержит установленных битов.
Итак, по сути, если вы храните в своем битовом наборе только false, toByteArray() вернет пустой массив.
ByteBuffer будет заполнять байты по мере их ввода. Таким образом, порядок хранения байтов совпадает с порядком, в котором вы вызываете метод .put.
Теперь давайте последуем примеру вашего кода:
Изначально ваш ByteByffer размером 2:
| x | x |
0 1
х означает, что он пустой
Затем вы создаете BitSet: [true, false, false, false, false, false, false, false] Это массив: [1]. Затем вы помещаете его в свой ByteBuffer:
| 1 | x |
0 1
в первой записи у вас сейчас 1.
Затем вы создаете второй битовый набор: [true, false, true, false, false, false, false, false], который равен [5] (2^0 + 2^2 = 1 + 4 = 5). Затем вы помещаете его в BytBuffer:
| 1 | 5 |
0 1
Итак, ваш вывод: «Первый байт равен 1, второй — 5». И это правильно.
что я считаю неправильным, поскольку порядок байтов неправильный
Я не думаю, что ваша проблема как-то связана с порядком байтов: если вы ожидали увидеть «Первый байт равен 5, второй равен 1», то измените порядок добавления байтов в ByteBuffer.
Теперь давайте последуем второму примеру:
Изначально ваш ByteByffer размером 2:
| x | x |
0 1
затем BitSet с [false, false, false, false, false, false, false, false], который равен 0 в двоичном формате. Но BitSet.toByteArray создаст пустой массив, поскольку BitSet не имеет установленных битов.
Итак, в ByteBuffe нет никаких изменений, он все еще пуст:
| x | x |
0 1
Второй набор бит — [true, false, true, false, false, false, false, false], то есть [5], после помещения в ByteBuffer:
| 5 | x |
0 1
Здесь, поскольку первый BitSet ничего не добавил, мы видим только результат добавления второго байта.
Итак, ваша проблема связана с тем, как BitSet создает массив при использовании toByteArray(). Вы можете отказаться от использования BitSet и использовать какой-либо другой класс, некоторые подсказки см. в этом SO: Использование Java BitSet и byte[]
[редактировать] исправить второй пример можно вручную, принимая во внимание, что нулевой массив может быть возвращен BitSet.toByteBuffer и добавлением массива [0] вручную. Конечно, то же самое можно сделать и для второго байта.
ByteBuffer out = ByteBuffer.allocate(2);
BitSet byt = new BitSet(8);
byt.set(0, false);
byt.set(1, false);
byte[] byteArr = byt.toByteArray();
if (byteArr.length == 0) {
out.put((byte)0);
} else {
out.put(byteArr);
}
byt.set(0, true);
byt.set(1, false);
byt.set(2, true);
byteArr = byt.toByteArray();
out.put(byteArr);
System.out.println("First byte is " + out.array()[0]+ " second is " + out.array()[1]);
BitSetимеет прямой порядок байтов, поэтому последний битовый шаблон —00000101(5).