fwrite() в C и readInt() в Java различаются последовательностью байтов

Собственный код:

запись числа 27 с помощью fwrite().

int main()
{
  int a = 27;
  FILE *fp;
  fp = fopen("/data/tmp.log", "w");
  if (!fp)
     return -errno;

  fwrite(&a, 4, 1, fp);
  fclose();
  return 0;
}

Чтение данных (27) с помощью DataInputStream.readInt():

public int readIntDataInputStream(void)
{
   String filePath = "/data/tmp.log";
   InputStream is = null;
   DataInputStream dis = null;
   int k;

   is = new FileInputStream(filePath);
   dis = new DataInputStream(is);
   k = dis.readInt();
   Log.i(TAG, "Size : " + k);
   return 0;
}

O/p

Size : 452984832

Ну, это в шестнадцатеричном формате 0x1b000000

0x1b это 27. Но readInt() считывает данные с прямым порядком байтов, в то время как мой родной код пишет с обратным порядком байтов. . Итак, вместо 0x0000001b я получаю 0x1b000000.

Правильно ли я понимаю? Кто-нибудь сталкивался с этой проблемой раньше?


person mk..    schedule 09.12.2016    source источник
comment
Да вы правы. C будет писать в соответствии с порядком байтов ЦП, который для процессоров x86 является прямым порядком байтов. DataInputStream.readInt() всегда будет читаться с обратным порядком байтов. Решение. Решите, какой порядок следования байтов должен иметь ваш файл, и убедитесь, что оба действуют соответствующим образом.   -  person Andreas    schedule 09.12.2016
comment
Более того, решите, что файл должен быть с обратным порядком байтов, что делает его переносимым и совместимым с Java, и соответствующим образом скорректируйте код C. Все, что вам нужно в этом коде C, это int a = htonl(27);   -  person user207421    schedule 09.12.2016
comment
Спасибо @Андреас. У меня есть большие объемы данных для записи. Как я могу эффективно справиться с этим в C?   -  person mk..    schedule 09.12.2016
comment
@EJP На самом деле у меня очень большой объем данных, которые я записываю в файл из собственного кода и читаю из приложения Java. Есть ли рекомендуемый способ для этого?   -  person mk..    schedule 09.12.2016
comment
Я только что дал вам один.   -  person user207421    schedule 09.12.2016
comment
Я несколько не согласен с @EJP. Файл не обязательно должен иметь прямой порядок байтов, хотя обратный порядок байтов (также известный как сетевой порядок байтов) является наиболее часто используемым порядком байтов для обмена данными. Вам просто нужно решить, что это должно быть, и убедиться, что C записывает это, а Java читает это. В Java самый простой способ контролировать порядок следования байтов — использовать ByteBuffer. В C вы бы построили массив байтов (char[]) и преобразовали int значений в char, используя битовый сдвиг.   -  person Andreas    schedule 09.12.2016
comment
см. также stackoverflow. ком/вопросы/5078100/   -  person Scary Wombat    schedule 09.12.2016
comment
@Andreas Использование стандарта всегда предпочтительнее, а стандартом является сетевой порядок байтов/   -  person user207421    schedule 09.12.2016
comment
@EJP У меня есть большая куча двоичных данных, которые я записываю в файл. Если я сделаю htonl() для 4 байтов по отдельности, думаю, это будет выглядеть не очень хорошо. Итак, может быть, мне придется изменить свой дизайн решения.   -  person mk..    schedule 09.12.2016
comment
@Andreas, как насчет других API в DataInputStream()? Например, readFully().. Если я напишу двоичные данные и буду использовать этот API, это будет нормально? Я собираюсь попробовать эти варианты. Но слово мудрости мне обязательно поможет.   -  person mk..    schedule 09.12.2016
comment
@mk.. Зачем тебе использовать htonl()? байт1 = i ›› 24; байт2 = i ›› 16; байт3 = я ›› 8; байт4 = я;   -  person Andreas    schedule 09.12.2016
comment
@mk Если вы просто собираетесь читать байты в byte[] и, возможно, использовать ByteBuffer для извлечения значений int из таких массивов байтов, не используйте DataInputStream. Используйте InputStream напрямую, а точнее BufferedInputStream для лучшей производительности. DataOutputStream/DataInputStream предназначены для пересылки данных между Java-программами. Не используйте их для обмена с другими языками.   -  person Andreas    schedule 09.12.2016
comment
хорошо .. На самом деле данные в файл записываются собственным слоем и читаются слоем java.. Данные имеют формат ‹Строка из 9 байтов›‹размер›‹полезная нагрузка[размер]›‹Строка из 9 байт›‹размер ›‹payload[size]›.... ex SPS_FRAME1b000000.... Приложение для Android должно считывать байты полного размера и ждать, пока не будет доступно столько данных. Итак, я хотел API readFully() для этой цели, так как это удобно. Я вижу, что этот API доступен только в DataInputStream. Доступен ли он в других интерфейсах? @Андреас   -  person mk..    schedule 09.12.2016
comment
Вызов InputStream.read(byte[] b, int off, int len) несколько раз, пока все байты не будут получены, не так сложно. Это довольно простая петля.   -  person Andreas    schedule 09.12.2016
comment
@Андреас Согласен. Я попробую это и вернусь, если меня что-то беспокоит. Спасибо   -  person mk..    schedule 09.12.2016
comment
@Андреас Ерунда. DataInput/OutputStream специально разработаны для обмена данными с другими языками и платформами. Вот почему они используют сетевой порядок байтов.   -  person user207421    schedule 20.11.2017
comment
@EJP Не знаю, почему вы возродили эту старую ветку, но в javadoc этих объектов ничего подобного не говорится. DataOutputStream предназначен для записи примитивных типов данных Java переносимым способом, т. е. таким образом, чтобы данные могли читаться с помощью DataInputStream. Это не значит, что данные могут быть прочитаны другими языками, и они переносимы, как на любой платформе, где работает Java. Конечно, использование сетевого порядка байтов более стандартно, но это не указанная цель.   -  person Andreas    schedule 20.11.2017


Ответы (2)


Из Javadoc для readInt():

Этот метод подходит для чтения байтов, записанных методом writeInt интерфейса DataOutput.

Если вы хотите прочитать что-то, написанное программой на языке C, вам придется выполнить замену байтов самостоятельно, используя средства java.nio. Я никогда этого не делал, но я считаю, что вы должны прочитать данные в ByteBuffer, установить порядок буфера в ByteOrder.LITTLE_ENDIAN, а затем создать представление IntBuffer поверх ByteBuffer, если у вас есть массив значений, или просто использовать ByteBuffer#getInt() для одного значения.

Помимо всего этого, я согласен с @EJP в том, что внешний формат данных должен быть с обратным порядком байтов для максимальной совместимости.

person Jim Garrison    schedule 09.12.2016
comment
ByteBuffer имеет getInt() чтобы прочитать следующие 4 байта как int с заданным порядком байтов. Представление IntBuffer полезно только в том случае, если все данные int, например. если это int[]. - person Andreas; 09.12.2016

В вашем коде есть несколько проблем:

  • Вы предполагаете, что размер int равен 4, это не обязательно верно, и поскольку вы хотите иметь дело с 32-битными целыми числами, вы должны использовать int32_t или uint32_t.

  • Вы должны открыть файл в двоичном формате, чтобы надежно записывать двоичные данные. Вышеупомянутый код потерпит неудачу в Windows для менее тривиального вывода. Используйте fopen("/data/tmp.log", "wb").

  • Вы должны иметь дело с порядком байтов. Вы используете этот файл для обмена данными между разными платформами, которые могут иметь разный исходный порядок байтов и/или специфичные для байтов API. Похоже, что в Java используется обратный порядок байтов, также известный как сетевой порядок байтов, поэтому вам следует преобразовать значения на платформе C с помощью служебной функции hton32(). Маловероятно, что это окажет существенное влияние на производительность на стороне ПК, так как эта функция обычно расширяется встроенно, возможно, в виде отдельной инструкции, и в любом случае большая часть времени будет потрачена на ожидание ввода-вывода.

Вот модифицированная версия кода:

#include <endian.h>
#include <stdint.h>
#include <stdio.h>

int main(void) {
    uint32_t a = hton32(27);
    FILE *fp = fopen("/data/tmp.log", "wb");
    if (!fp) {
        return errno;
    }
    fwrite(&a, sizeof a, 1, fp);
    fclose();
    return 0;
}
person chqrlie    schedule 11.12.2016
comment
привет chqrlie, Спасибо за ответ. Что касается пункта 1 и пункта 2, я вроде как в курсе этих вещей. Кроме того, dis z только тестовый код. Пункт 1 -> я принял его за 4 байта, потому что он упоминается, readInt() в java в любом случае будет читать ровно 4 байта. 2-› Я работаю над Unix-системами. В системах Unix 'b' в fopen не имеет никакого значения. Со страницы руководства Это строго для совместимости с C89 и не имеет никакого эффекта; 'b' игнорируется во всех системах, соответствующих POSIX, включая Linux. Но это хорошие моменты, чтобы программа выглядела элегантно. Спасибо. - person mk..; 11.12.2016
comment
@mk..: Я понимаю, что опубликованный код - это просто быстрый и грязный тест. Я всегда стараюсь дать подробный ответ не только ОП, но и другим читателям, чтобы увидеть все потенциальные проблемы. "wb" строго эквивалентен "w" на большинстве платформ Unix, но использование b не повредит и сделает более очевидным, что "/data/tmp.log" является двоичным файлом, чего не подразумевает название. int имеет 32-битную длину в подавляющем большинстве систем Unix, но размер long (64-битный в java) варьируется в зависимости от ABI, даже на одном и том же хосте (32-битный или 64-битный режим). Элегантность должна стать второй натурой. - person chqrlie; 11.12.2016