Java和C#的String实现

时间:2011-09-08 18:14:29

标签: c# java string arrays

String的Java和C#实现中,底层信息是否为C / C ++中的以null结尾的char数组?

(除了尺寸等其他信息外)

3 个答案:

答案 0 :(得分:16)

没有。它是UTF-16代码单元和长度的序列。 Java和C#字符串可以包含嵌入的NUL。

每个UTF-16代码单元占用两个字节,因此您可以将字符串"\n\0\n"视为:

{
  length: 3,  // 3 pairs of bytes == 3 UTF-16 code units
  bytes:  [0, 10, // \n
           0, 0,  // \0
           0, 10] // \n
}

请注意bytes中的最后一个字节不是0. length字段表示使用了多少字节。这允许substring非常高效 - 重用相同的字节数组,但具有不同的长度(如果您的VM实现无法指向数组,则返回偏移量。)

  

UTF-16(16位Unicode转换格式)是Unicode的字符编码,能够在Unicode代码空间中编码1,112,064个数字(称为代码点),范围为0到0x10FFFF。它产生每个代码点一个或两个16位代码单元的可变长度结果。

来自javadoc

  

String表示UTF-16格式的字符串,其中补充字符由代理项对表示(有关详细信息,请参阅Character类中的Unicode字符表示形式一节)。索引值是指char代码单元,因此补充字符在String中使用两个位置。

C# System.String的定义类似

  

字符串中的每个Unicode字符都由Unicode标量值定义,也称为Unicode代码点或Unicode字符的序数(数字)值。每个代码点使用UTF-16编码进行编码,并且编码的每个元素的数值由Char表示。生成的Char对象集合构成String。

我不确定C#是否可以防范孤儿代理,但上面的文字似乎混淆了“标量值”和“代码点”这两个令人困惑的词汇。因此unicode.org定义scalar value

  

除高代理和低代理代码点之外的任何Unicode代码点

Java绝对采用代码点视图,并不会尝试防止字符串中的无效标量值。

"Strings Immutability and Persistence"解释了这种表现形式的效率优势。

  

我之前谈到的不可变数据类型的一个好处是它们不仅是不可变的,它们也是“持久的”。 “持久性”是指一种不可变的数据类型,这种类型的常见操作(如向队列添加新项或从树中删除项)可以重用现有数据的大部分或全部内存结构体。因为它是完全不可变的,所以你可以重复使用它的部件而不必担心它们会改变你。

编辑: 以上在概念上和实践中都是正确的,但VM和CLR在某些情况下可以自由地做事。

Java语言规范要求.class文件中的字符串为laid out a certain way,其JNI jstring类型抽象出内存中的表示详细信息,因此VM可以理论上,将内存中的字符串表示为NUL终止的UTF-8字符串,其中包含用于嵌入式NUL字符的双字节格式,而不是int32 lengthuint16[] bytes表示,允许对代码进行有效的随机访问-units。

但VM在实践中并没有这样做。 "The Most Expensive One-byte Mistake"认为NUL终止的字符串在C中是一个巨大的错误,所以我怀疑虚拟机会出于效率原因在内部采用它们。

  

我能够提出的最佳候选者是C / Unix / Posix使用NUL终止的文本字符串。选择非常简单:C语言应该将字符串表示为地址+长度元组,还是将带有魔术字符(NUL)的地址表示为结尾?

     

...

     

关于虚拟内存系统的思考为我们解决了这个问题。优化已知长度字节串的移动可以利用内存总线和高速缓存行的全宽,而不会触及不属于源或目标字符串的内存位置。

     

一个例子是FreeBSD的libc,其中bcopy(3)/ memcpy(3)实现将在“unsigned long”(通常为32或64位)的块中移动尽可能多的数据,然后“擦除任何尾随字节“正如评论所描述的那样,使用字节范围的操作.2

     

但是,如果源字符串被NUL终止,则尝试以大于字节为单位访问它可能会尝试在NUL之后读取字符。如果NUL字符是[虚拟内存]页面的最后一个字节,并且未定义下一个[虚拟内存]页面,则会导致进程死于无理由的“页面不存在”错误。

答案 1 :(得分:10)

作为一个实现细节,CLR的Microsoft实现中的字符串在内存中布局与在BS中的BSTR几乎相同。 (有关BSTR的详细信息,请参阅http://blogs.msdn.com/b/ericlippert/archive/2003/09/12/52976.aspx。)

也就是说,一个字符串被布置为包含长度的四个字节,后面是许多两个字节的UTF-16字符,后跟两个零字节。

当然结束带有零字符的长度前缀字符串并非必要,但这样做当然很方便,尤其是在考虑必须在C#之间进行互操作的场景时程序和非托管C ++或VB6程序。 marshaller有时可以节省一些复制,因为它知道字符串已经是以空终止格式。

正如我所说,这是一个实施细节;你不应该依赖它。

我不知道Java的用途。

答案 2 :(得分:1)

我不能代表C#,但Java的字符串来源说不。数组的大小信息存储在数组中,不需要空终止。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** The value is used for character storage. */
    private final char value[];

    /** The offset is the first index of the storage that is used. */
    private final int offset;

    /** The count is the number of characters in the String. */
    private final int count;

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    // ... rest of class
}