如何将.NET Guid读入Java UUID

27

我需要将在.NET中生成的Guid传递给一个Java应用程序。我使用Guid.ToByteArray()将其存储为byte[],然后将其读入Java并转换为UUID。为此,我复制了UUID的(私有)构造函数的实现,该构造函数接受一个byte[]参数:

private UUID(byte[] data) {
    long msb = 0;
    long lsb = 0;
    assert data.length == 16;
    for (int i=0; i<8; i++)
        msb = (msb << 8) | (data[i] & 0xff);
    for (int i=8; i<16; i++)
        lsb = (lsb << 8) | (data[i] & 0xff);
    this.mostSigBits = msb;
    this.leastSigBits = lsb;
}

但是,当我使用toString()检查UUID时,Java UUID与.NET Guid不同。

例如,.NET Guid为

888794c2-65ce-4de1-aa15-75a11342bc63

转换为Java UUID

c2948788-ce65-e14d-aa15-75a11342bc63

看起来前三组字节的排序是反着的,而后两组的排序是相同的。

由于我期望Guid和UUID的toString()产生相同的结果,所以有人知道我应该如何将.NET Guid正确地读入Java UUID吗?

编辑:澄清一下,实现不是我自己写的。它是java.util.UUID类的私有构造函数,它需要一个byte[],我复制了它并用于从磁盘读取一个byte[]到UUID中。

我不想使用字符串来存储Guid,因为我要存储很多Guid,这似乎是浪费空间。

Russell Troywest的链接至少说明了为什么Guid的前几组会反转,而后半部分保持相同的顺序。问题是,我可以依赖于.NET始终以相同的顺序生成这些字节吗?


看起来你把位移搞错了。为什么要试图用巧妙的方式解决呢?首先读取字节并进行适当的赋值(使用索引),然后再使用位移运算符进行优化(如果必要)。关键是要编写易于理解的代码。 - casperOne
Java严格采用大端字节序存储数据,而C#没有指定“字节序”,但通常将数据存储为小端字节序。正如@casperOne所说,您正在错误地进行移位操作。 - Doug Stephen
1
我一直在试图反向工程一个使用此结构的框架。盯着那个奇怪的位移超过两个小时,直到我找到了这个线程。 - Jouke Waleson
8个回答

13

你能不能把.Net Guid存储为字符串,然后在Java中读取呢?这样就不用担心字节顺序或其他任何问题了。

如果行不通的话,可以参考下面这篇文章来了解C#中字节的布局:

http://msdn.microsoft.com/zh-cn/library/fx22893a.aspx


12

2017-08-30编辑:根据评论,交换了数组元素6和7。

我需要在C#应用程序中从/向MySQL(存储为binary(16))读取和写入Guid,但数据库也由Java应用程序使用。以下是我用于在.NET小端和Java大端字节顺序之间转换的扩展方法:

public static class GuidExtensions
{
    /// <summary>
    /// A CLSCompliant method to convert a Java big-endian Guid to a .NET 
    /// little-endian Guid.
    /// The Guid Constructor (UInt32, UInt16, UInt16, Byte, Byte, Byte, Byte,
    ///  Byte, Byte, Byte, Byte) is not CLSCompliant.
    /// </summary>
    [CLSCompliant(true)]
    public static Guid ToLittleEndian(this Guid javaGuid) {
        byte[] net = new byte[16];
        byte[] java = javaGuid.ToByteArray();
        for (int i = 8; i < 16; i++) {
            net[i] = java[i];
        }
        net[3] = java[0];
        net[2] = java[1];
        net[1] = java[2];
        net[0] = java[3];
        net[5] = java[4];
        net[4] = java[5];
        net[6] = java[7];
        net[7] = java[6];
        return new Guid(net);
    }

    /// <summary>
    /// Converts little-endian .NET guids to big-endian Java guids:
    /// </summary>
    [CLSCompliant(true)]
    public static Guid ToBigEndian(this Guid netGuid) {
        byte[] java = new byte[16];
        byte[] net = netGuid.ToByteArray();
        for (int i = 8; i < 16; i++) {
            java[i] = net[i];
        }
        java[0] = net[3];
        java[1] = net[2];
        java[2] = net[1];
        java[3] = net[0];
        java[4] = net[5];
        java[5] = net[4];
        java[6] = net[7];
        java[7] = net[6];
        return new Guid(java);
    }
}

1
我不确定这是否是Java特定的问题,但我需要为活动目录GUID / nativeGuid翻转字节序,并使用此代码会产生错误。修复方法在最后两个字节交换中,而该代码中并没有:java [6] = net [7]; java [7] = net [6]; 还要注意的是这两种方法是相同的,您可以将其简化为一个flip函数。 - Maverik
所以你的意思是,应该交换这两个字节,而不是 net[6] = java[6]net[7] = java[7]?我知道这段代码在处理 MySQL 中的 Guids 时对我有效(现在手头没有那个数据库),但是下面 @Russell Troywest 的答案可能是更安全的选择。 - Paul Smith
1
这就是Active Directory的工作方式。很奇怪!我基本上使用您的代码在DirectoryEntry.Guid和DirectoryEntry.NativeGuid之间进行切换,并进行了修改。根据Guids的文档,最后一个字节应该根据我对该文本的基本理解进行交换。 - Maverik
你的代码中似乎有一个错别字(或者是一个 bug),net[6] = java[6]; 这一行是否应该改为 net[6] = java[7]; - Felix
我不得不在ToBigEndian中将net [7]移动到6,将net [6]移动到7。 - Alexander
@Alexander,如果这样可以的话,那么ToLittleEndian不应该也同样交换java[6]java[7]吗?假设是这样的话,我会同时交换两个字节。暂时不会将这些方法合并成一个SwapEndianness方法,但也许以后会这样做。 - Paul Smith

12

如前所述,在.NET中GUID的二进制编码中,前三个组的字节以小端序(反转)的方式放置 - 请参阅Guid.ToByteArray方法。要从中创建java.util.UUID,您可以使用以下代码:

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.UUID;

public UUID toUUID(byte[] binaryEncoding) {
    ByteBuffer source = ByteBuffer.wrap(binaryEncoding);
    ByteBuffer target = ByteBuffer.allocate(16).
        order(ByteOrder.LITTLE_ENDIAN).
        putInt(source.getInt()).
        putShort(source.getShort()).
        putShort(source.getShort()).
        order(ByteOrder.BIG_ENDIAN).
        putLong(source.getLong());
    target.rewind();
    return new UUID(target.getLong(), target.getLong());
}

7
作为对您编辑的回应,否,您不能始终依赖生成的字节以相同的顺序。运行时决定了字节序。不过,C#确实提供了BitConverter.isLittleEndian来解决这个问题。
我知道您无法更改Java实现和位移操作的字节序。但是,在存储后发送到Java之前,您可以在C#端移动位。
更新: MSDN关于IsLittleEndian的文章 编辑: 实际上,您可能总是可以依赖其以小端方式布局第一块字节,但从技术上讲,您不能这样做。

5
GUID.toByteArray在C#中十分奇怪,它的前半部分是小端字节序,后半部分是大端字节序。
这个页面上的一个评论提到了这一点:http://msdn.microsoft.com/en-us/library/system.guid.tobytearray.aspx 引用: 返回的字节数组中字节的顺序与Guid值的字符串表示形式不同。 前四个字节组和接下来的两个两字节组的顺序被颠倒,而最后的两字节组和结束的六字节组的顺序相同。

3
我认为你在这里的问题是,.NET 是小端序而 JAVA 是大端序,所以当你从一个 C# 应用程序中读取一个128位整数(GUID),并从 JAVA 应用程序进行读取时,你需要将其从小端序转换为大端序。

4
我认为C#的字节序(即大小端)是由运行时技术确定的,大多数CLR实现都是小端字节序。如果想要更加严谨,可以使用IsLittleEndian等方法。但这也可能是问题的根源,至少我猜是这样。 - Doug Stephen

1

编解码器 DotNetGuid1CodecDotNetGuid4Codec 可以将 UUID 编码为 .Net Guids。

// Convert time-based (version 1) to .Net Guid
UuidCodec<UUID> codec = new DotNetGuid1Codec();
UUID guid = codec.encode(timeUuid);

// Convert random-based (version 4) to .Net Guid
UuidCodec<UUID> codec = new DotNetGuid4Codec();
UUID guid = codec.encode(randomUuid);

请参见:uuid-creator


0

这段代码对我来说可行。

var msb: Long = 0
var lsb: Long = 0
for(i <- Seq(3, 2, 1, 0, 5, 4, 7, 6)) {
  msb = (msb << 8) | (data(i) & 0xFF)
}
for(i <- 8 until 16) {
  lsb = (lsb << 8) | (data(i) & 0xFF)
}
new UUID(msb, lsb)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接