Есть ли объяснение поведению этого Java ByteBuffer? - PullRequest
4 голосов
/ 14 ноября 2011

Мне нужно преобразовать числовые значения в байтовые массивы. Например, чтобы преобразовать long в байтовый массив, у меня есть этот метод:

public static byte[] longToBytes(long l) {
  ByteBuffer buff = ByteBuffer.allocate(8);

  buff.order(ByteOrder.BIG_ENDIAN);

  buff.putLong(l);

  return buff.array();
}

Это довольно просто - возьмите длинный, выделите массив, который может его содержать, и добавьте его туда. Независимо от значения l я верну 8-байтовый массив, который затем смогу обработать и использовать по назначению. В моем случае я создаю собственный двоичный формат, а затем передаю его по сети.

Когда я вызываю этот метод со значением 773450364, я получаю обратно массив [0 0 0 0 46 25 -22 124]. У меня есть код, который также преобразует байтовые массивы обратно в их числовые значения:

public static Long bytesToLong(byte[] aBytes, int start) {
  byte[] b = new byte[8];

  b[0] = aBytes[start + 0];
  b[1] = aBytes[start + 1];
  b[2] = aBytes[start + 2];
  b[3] = aBytes[start + 3];
  b[4] = aBytes[start + 4];
  b[5] = aBytes[start + 5];
  b[6] = aBytes[start + 6];
  b[7] = aBytes[start + 7];

  ByteBuffer buf = ByteBuffer.wrap(b);
 return buf.getLong();
}

Когда я передаю массив из другого метода обратно в этот метод, я получаю 773450364, что правильно.

Теперь я передаю этот массив по TCP другому клиенту Java. В документации по методу java.io.InputStream.read() говорится, что он возвращает значение int в диапазоне от 0 до 255, если только не достигнут конец потока и не возвращается -1. Однако, когда я использую его для заполнения байтового массива, я продолжаю получать отрицательные значения на принимающей стороне. Я подозреваю, что это связано с переполнением (значение 255 не может вписаться в байт Java, поэтому, когда я помещаю его в массив байтов, оно переполняется и становится отрицательным).

Это подводит меня к моей проблеме. Существование отрицательных чисел касается меня. Сейчас я занимаюсь разработкой Java-приложения, где байт находится в диапазоне от -128 до 127 включительно. Другая конечная точка может быть на C, C ++, Python, Java, C # ... кто знает. Я не уверен, как наличие отрицательного значения в некоторых байтовых массивах повлияет на обработку. Кроме документирования этого поведения, что я могу / должен сделать, чтобы облегчить себе и будущим разработчикам, работающим над этой системой, особенно в конечных точках, которые не написаны на Java?

Ответы [ 3 ]

6 голосов
/ 14 ноября 2011

A byte в Java представлен в 8-битном формате с двумя дополнениями .Если у вас есть int, который находится в диапазоне 128 - 255, и вы приведете его к byte, тогда он станет byte с отрицательным значением (между -1 и -128).

После прочтения байта вы должны проверить, равен ли он -1 , прежде чем приводить его к byte.Причина, по которой метод возвращает int, а не byte, заключается в том, что вы можете проверить конец потока, прежде чем преобразовать его в byte.

Еще одна вещь: почему выскопировать массив aBytes в ваш метод bytesToLong?Вы можете значительно упростить этот метод и сохранить ненужную копию:

public static Long bytesToLong(byte[] aBytes, int start) {
    return ByteBuffer.wrap(aBytes, start, 8).order(ByteOrder.BIG_ENDIAN).getLong();
}
1 голос
/ 14 ноября 2011

Ваши отправляющие и принимающие конечные точки в настоящее время реализованы на Java. Возможно, вы используете OutputStream на отправляющей стороне и InputStream на принимающей стороне. Предполагая, что мы можем доверять деталям реализации базового сокета на некоторое время, мы будем считать, что любой байт, отправленный через сокет, поступит к месту назначения точно так же.

Так что же на самом деле происходит на уровне Java при выгрузке чего-либо в OutputStream? При проверке JavaDoc для метода, пишущего байтовый массив , мы видим, что все это говорит нам о том, что байты передаются по потоку. Ничего особенного там нет. Но когда вы проверите документ для метода , взяв int в качестве аргумента , вы увидите, как подробно записывается этот int: 8 бит младших разрядов передаются по потоку в виде байта, в то время как старшие 24 бита (int имеет 32-битное представление в Java) просто игнорируются.

На приемную сторону. У вас есть InputStream. Если вы не используете один из методов, считывающих непосредственно в байтовый массив , вы получите int. Как говорит документ , int будет иметь значение от 0 до 255 включительно или -1, если достигнут конец потока. Это важный бит. С одной стороны, мы хотим, чтобы все возможные битовые комбинации одного байта были читаемыми из InputStream. Но у нас также должен быть какой-то способ обнаружения, когда чтение больше не может возвращать значимые значения. Вот почему этот метод возвращает int вместо байта ... Значение -1 - это флаг, указывающий, что конец потока достигнут. Если вы получаете что-то кроме -1, единственное, что вас интересует, это младшие 8 бит. Поскольку это может быть любая битовая комбинация, их десятичное значение будет в диапазоне от -128 до 127 включительно. Когда вы читаете непосредственно в байтовый массив вместо int per int, это «обрезание» будет сделано для вас. Так что есть смысл, что вы увидите эти негативные ценности. Тем не менее, они только отрицательны из-за того, как Java представляет байт в виде десятичного числа со знаком. Единственная вещь, которая представляет интерес, - это фактическая битовая комбинация. Для всех вас это может представлять значения от 0 до 255 или от 1000 до 1255.

Типичный цикл чтения InputStream, использующий по одному байту за раз, будет выглядеть следующим образом:

InputStream ips = ...;
int read = 0;
while((read = ips.read()) != -1) {
    byte b = (byte)read;
    //b will now have a bit pattern ranging from 0x00 to 0xff in hex, or -128 to 127 in two-complement signed representation
}

При запуске высвечивается следующее (использует литералы Java 7 int):

public class Main {

    public static void main(String[] args) {

        final int i1 = Ox00_00_00_fe;
        final int i1 = Ox80_00_00_fe;

        final byte b1 = (byte)i1;
        final byte b2 = (byte)i2;

        System.out.println(i1);
        System.out.println(i2);

        System.out.println(b1);
        System.out.println(b2);

        final int what = Ox12_34_56_fe;
        final byte the_f = (byte)what;

        System.out.println(what);
        System.out.println(the_f);

    }

}

Как будет ясно из этого, приведение от int к байту просто отбросит все, кроме младших 8 бит. Таким образом, int может быть положительным или отрицательным числом, оно не будет иметь никакого отношения к значению байта. Только последние 8 бит.

Короче говоря: вы получаете правильные значения байтов из вашего InputStream. Реальное беспокойство здесь заключается в том, что если клиентская сторона может быть написана на любом языке программирования и работать на любой платформе, вам необходимо четко указать в своей документации, что означают полученные байты и если они long, как это закодировано. Дайте понять, что кодирование выполняется в Java с использованием метода ByteBuffer putLong с определенным порядком байтов. Только тогда они получат информацию (в сочетании со спецификациями Java), чтобы быть абсолютно уверенными в том, как интерпретировать эти байты.

0 голосов
/ 15 ноября 2011

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

...