Mik*_*uel 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
}
Run Code Online (Sandbox Code Playgroud)
请注意,最后一个字节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:
除高代理和低代理代码点之外的任何Unicode代码点
Java绝对采用代码点视图,并不试图防止字符串中的无效标量值.
"字符串不变性和持久性"解释了这种表示的效率优势.
我之前谈到的不可变数据类型的一个好处是它们不仅是不可变的,它们也是"持久的"."持久性"是指一种不可变的数据类型,这种类型的常见操作(如向队列添加新项或从树中删除项)可以重用现有数据的大部分或全部内存结构体.因为它是完全不可变的,所以你可以重复使用它的部件,而不必担心它们会改变你.
编辑:以上在概念上和实践中都是正确的,但VM和CLR在某些情况下可以自由地做不同的事情.
Java语言规范要求字符串在文件中以某种方式布局.class,并且其JNI jstring类型抽象出内存中的表示细节,因此理论上VM可以将内存中的字符串表示为NUL终止的UTF-8字符串用于嵌入式NUL字符而不是两字节形式int32 length和uint16[] bytes允许高效的随机存取码单元表示.
但VM在实践中并没有这样做. "最昂贵的单字节错误"认为NUL终止的字符串在C中是一个巨大的错误,因此我怀疑虚拟机会出于效率原因在内部采用它们.
我能够提出的最佳候选者是使用NUL终止的文本字符串的C/Unix/Posix.选择非常简单:C语言应该将字符串表示为地址+长度元组,还是将带有魔术字符(NUL)的地址标记为结尾?
...
思考一下虚拟内存系统为我们解决了这个问题.优化已知长度字节串的移动可以利用存储器总线和高速缓存行的全宽度,而不会触及不属于源或目标字符串的存储器位置.
一个例子是FreeBSD的libc,其中bcopy(3)/ memcpy(3)实现将在"unsigned long"(通常为32或64位)的块中移动尽可能多的数据,然后"清除任何尾随字节"作为注释用字节操作描述它
但是,如果源字符串被NUL终止,则尝试以大于字节为单位访问它可能会尝试在NUL之后读取字符.如果NUL字符是[虚拟内存]页面的最后一个字节,并且未定义下一个[虚拟内存]页面,则会导致进程死于不合理的"页面不存在"错误.
Eri*_*ert 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做了什么.