Java和C#之间的Base64和二进制流

时间:2015-09-24 21:57:29

标签: java c# scala base64

我觉得答案很明显,但假设我在C#中有以下内容

using (MemoryStream ms = new MemoryStream())
{
    using (BinaryWriter bw = new BinaryWriter(ms))
    {
        // Write some floats, bytes, and uints
        // Convert.ToBase64String this stuff from ms.ToArray
    }
}

以及Java中的以下内容(确定它是Scala但使用Java库):

val byteStream = new ByteArrayOutputStream()
val outStream = new DataOutputStream(byteStream)
// Write some floats, bytes, and longs where the uints were using 
// writeFloat, writeByte, and writeLong. .NET has an overloaded 
// function that takes whatever. 
// Base64.getEncoder.encodeToString byteStream.toByteArray

我得到完全不同的基本64字符串。他们在这做什么不同?我需要Java输出来匹配.NET输出。我假设它有某种字节顺序问题,但我没有运气使用ByteBuffer来解决这个问题。

Java:

  

PczMzT3MzM0 / gAAAPczMzQAAAAAAAAAAAAAAAD3MzM0 / gAAAAQAAAABRn8XzAAAAAAAAAAEAAAAAAAAAAQ ==

C#(因为我们因为原因而砍掉了未知=标志):

  

zczMPc3MzD0AAIA / zczMPQAAAAAAAAAAAAAAAM3MzD0AAIA / AfPFn1EBAAAAAQAAAA

我真的觉得它是字节排序,这就是为什么我尝试在Java代码中使用ByteBuffer,订购方法,改变排序但我没有成功。

为了进一步明确,Java代码在x86_64 CentOS Java 7上运行,而.NET在x86_64 Windows Server 2008 .NET 4上运行。这些值来自Protobuf对象,所以它们应该是我认为的跨平台。数字上数据是相同和一致的,无论我至少在写这些三种数据类型时放入了什么。唯一显着的区别是Java中缺少无符号类型,并且可能存在二进制表示差异,这是我最初尝试解决的问题,但我似乎无法弄明白。

正如我所说。使用其他格式不是一种选择。我需要从java编写的二进制数据然后基于64编码以产生与.NET相同的结果。序列化选择不是一种选择。必须如此。我需要一个有助于将它们组合在一起的资源,无论这意味着是否对字节数据进行二进制操作。我需要在数据类型中进行一些解释,因为我已经搜索了很多,并没有找到解释如何执行此操作的资源或真正的差异,因此我可以实现一个我决定在此处提出的解决方案。

3 个答案:

答案 0 :(得分:2)

如何实现跨平台二进制通​​信:

  • 定义完全字节格式
  • 在每个平台中实施

通常,您可以使用符合您需求的现成协议(例如https://en.wikipedia.org/wiki/BSON)来简化这两个步骤,并在您感兴趣的一个或所有平台上得到支持。

请注意,给定语言/框架中的基本二进制序列化类型通常严格地针对该语言/框架(通常是特定版本),因为它经常提供速度/大小优势,并且“二进制对象表示”没有被广泛接受的标准

替代方法是使用明确定义的文本格式,如JSON / XML,如其他答案所示。

二进制格式之间可能存在技术差异:

  • 整数类型的序列化可以按字节顺序/可能的替代表示(如.Net中的压缩int)而不同
  • 布尔和枚举类型的大小可能不同
  • 数组/字符串可以使用不同的类型来表示长度
  • 填充可以通过一些二进制表示来添加
  • 字符串可以是Utf8,Utf-16或任何其他指定/未指定的编码,有或没有尾随0。

答案 1 :(得分:2)

主要问题是C#的BinaryWriter首先写入数据类型的低字节,而Java的DataOutputStream首先写入高字节。

此外,当您编写.NET无符号整数时,它会写入4个字节。但是当你编写Java long时,它会写入8个字节。所以这就是另一个区别。

但是,一旦你理解了这些差异,修复它们就不那么难了。下面是2个代码片段,一个在C#中,另一个在Java中,它们编码相同的信息并输出相同的Base64编码字符串。就我而言,我选择覆盖Java如何写floatlong s。

.NET代码示例

static void Main(string[] args)
{
    using (MemoryStream ms = new MemoryStream())
    {
        using (BinaryWriter bw = new BinaryWriter(ms))
        {
            // floats
            bw.Write(-456.678f);
            bw.Write(0f);
            bw.Write(float.MaxValue);

            // bytes
            bw.Write((byte)0);
            bw.Write((byte)120);
            bw.Write((byte)255);

            // uints
            bw.Write(0U);
            bw.Write(65000U);
            bw.Write(4294967295U);
        }

        var base64String = Convert.ToBase64String(ms.ToArray());
        Console.WriteLine(base64String);
    }
}

Java代码示例

public static void main(String[] args) throws Exception {
    try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream()) {

        try (DataOutputStream outStream = new DataOutputStream(byteStream)) {
            // floats
            writeFloat(-456.678f, outStream);
            writeFloat(0f, outStream);
            writeFloat(Float.MAX_VALUE, outStream);

            // bytes
            outStream.writeByte(0);
            outStream.writeByte(120);
            outStream.writeByte(255);

            // longs (uints)
            writeUint(0L, outStream);
            writeUint(65000L, outStream);
            writeUint(4294967295L, outStream);
        }

        String base64String = Base64.getEncoder().encodeToString(byteStream.toByteArray());
        System.out.println(base64String);
    }
}

private static void writeFloat(float f, DataOutputStream stream) throws Exception {
    int val = Float.floatToIntBits(f);
    stream.writeByte(val & 0xFF);
    stream.writeByte((val >>> 8) & 0xFF);
    stream.writeByte((val >>> 16) & 0xFF);
    stream.writeByte((val >>> 24) & 0xFF);
}

private static void writeUint(long val, DataOutputStream stream) throws Exception {
    stream.writeByte((int) (val & 0xFF));
    stream.writeByte((int) ((val >>> 8) & 0xFF));
    stream.writeByte((int) ((val >>> 16) & 0xFF));
    stream.writeByte((int) ((val >>> 24) & 0xFF));
}

两个样本的输出

  

yVbkwwAAAAD // 39 / AHJ / AAAAAOj9AAD /////

确保使用float类型测试边缘情况,并在必要时进行调整。如果对你很重要,我希望像NaN这样有趣的价值观会引起分歧,但也许你并不关心这一点。否则,我希望它能正常工作。

答案 2 :(得分:1)

不同的平台具有不同的二进制表示。如果要匹配base64字符串,则应使用json或xml序列化。 Json或xml提供跨平台。

编辑:不要错过了解我:Base64是标准编码算法。它为相同的数据提供相同的输出。我的意思是字节数组可能不同。