【问题标题】:fwrite() in C & readInt() in Java differ in endianessC 中的 fwrite() 和 Java 中的 readInt() 字节序不同
【发布时间】:2017-04-24 13:35:45
【问题描述】:

本机代码:

使用 fwrite() 写入数字 27。

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;
}

使用 DataInputStream.readInt() 读回数据(27):

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

0x1b27。但是 readInt() 正在以大端方式读取数据,而我的本机编码正在以小端方式写入。 .所以,我得到的是0x1b000000,而不是0x0000001b

我的理解正确吗?以前有人遇到过这个问题吗?

【问题讨论】:

  • 是的,你是对的。 C 将写入 CPU 的字节序,对于 x86 处理器,它是小字节序。 DataInputStream.readInt() 将始终读取大端。解决方案:确定您的文件应该具有哪种字节顺序,并确保两者都相应地采取行动。
  • 更重要的是,决定文件应该是big-endian,这使其可移植并且与Java兼容,并相应地调整C代码。在这个 C 代码中你只需要int a = htonl(27);
  • 谢谢@Andreas。我有大量的数据要写。我如何在 C 中有效地处理这个问题?
  • @EJP 实际上,我有大量数据要从本机代码写入文件并从 Java 应用程序读取。有推荐的方法吗?

标签: java android c endianness datainputstream


【解决方案1】:

您的代码中有多个问题:

  • 你假设int的大小是4,不一定对,既然要处理32位的int,你应该使用int32_t或者uint32_t

  • 您必须更多地以二进制打开文件才能可靠地写入二进制数据。上面的代码将在 Windows 上失败,以获得不那么琐碎的输出。使用fopen("/data/tmp.log", "wb")

  • 您必须处理字节序。您正在使用该文件在可能具有不同本机字节序和/或字节序特定 API 的不同平台之间交换数据。 Java 似乎使用 big-endian,即 网络字节顺序,因此您应该使用 hton32() 实用函数在 C 平台上转换值。它不太可能对 PC 端的性能产生重大影响,因为此函数通常是内联扩展的,可能作为一条指令进行扩展,而且大部分时间都将花费在等待 I/O 上。

这里是修改后的代码:

#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;
}

【讨论】:

  • 嗨,chqrlie,感谢您的回答。关于第1点和第2点,我有点意识到这些事情。另外, dis z 只是一个测试代码。第 1 点 - > 我把它当作 4 个字节,因为它被提到,java 的 readInt() 无论如何都会读取 4 个字节。 2-> 我正在为 unix 系统工作。在 Unix 系统中,fopen 中的 'b' 没有任何意义。来自手册页“这完全是为了与 C8​​9 兼容并且没有任何效果;在所有符合 POSIX 的系统上,包括 Linux 在内,都会忽略 'b'。”但是,这些都是使程序看起来优雅的好点。谢谢。
  • @mk..:我知道发布的代码只是一个快速而肮脏的测试。我总是尝试为不仅为 OP 提供详细的答案,而且还为其他读者提供查看所有潜在问题的详细答案。在大多数 Unix 平台上,"wb" 严格等同于 "w",但使用 b 并没有什么坏处,而且更明显的是,"/data/tmp.log" 是一个二进制文件,名称并不暗示。 int 在绝大多数 Unix 系统上是 32 位长,但 long(Java 中为 64 位)的大小因 ABI 而异,即使在同一主机上也是如此(32 位与 64 位模式)。优雅应该成为第二天性。
【解决方案2】:

来自readInt() 的 Javadoc:

该方法适用于读取接口DataOutputwriteInt方法写入的字节

如果您想读取由 C 程序编写的内容,您必须自己进行字节交换,使用 java.nio 中的工具。我从来没有这样做过,但我相信你会将数据读入ByteBuffer,将缓冲区的顺序设置为ByteOrder.LITTLE_ENDIAN,然后在ByteBuffer 上创建一个IntBuffer 视图,如果你有一个值数组,或者只需将 ByteBuffer#getInt() 用于单个值。

除此之外,我同意 @EJP 的观点,即数据的外部格式应该是大端的,以获得最大的兼容性。

【讨论】:

  • ByteBuffer 有一个 getInt() 在给定的字节序中读取接下来的 4 个字节作为 intIntBuffer 视图仅在所有数据为 int 时才有用,例如如果是int[]
猜你喜欢
  • 2017-06-15
  • 2011-04-25
  • 2015-03-29
  • 1970-01-01
  • 2016-12-29
  • 1970-01-01
  • 1970-01-01
  • 2011-09-27
  • 2020-08-23
相关资源
最近更新 更多