3

我很难理解 Java String(byte[]) 构造函数(Java 6)语义背后的基本原理。生成的 String 对象的长度通常是错误的。也许这里有人可以解释为什么这是有道理的。

考虑以下小型 Java 程序:

import java.nio.charset.Charset;

public class Test {
    public static void main(String[] args) {
        String abc1 = new String("abc");
        byte[] bytes = new byte[32];

        bytes[0] = 0x61; // 'a'
        bytes[1] = 0x62; // 'b'
        bytes[2] = 0x63; // 'c'
        bytes[3] = 0x00; // NUL

        String abc2 = new String(bytes, Charset.forName("US-ASCII"));

        System.out.println("abc1: \"" + abc1 + "\" length: " + abc1.length());
        System.out.println("abc2: \"" + abc2 + "\" length: " + abc2.length());

        System.out.println("\"" + abc1 + "\" " +
                (abc1.equals(abc2) ? "==" : "!=") + " \"" + abc2 + "\"");
    }
}

这个程序的输出是:

abc1: "abc" length: 3
abc2: "abc" length: 32
"abc" != "abc"

String byte[] 构造函数的文档指出,“新字符串的长度是字符集的函数,因此可能不等于字节数组的长度。” 确实如此,在 US-ASCII 字符集中,字符串“abc”的长度是 3,而不是 32。

奇怪的是,即使 abc2 不包含空格字符,abc2.trim() 返回相同的字符串,但长度调整为正确的值 3 并且 abc1.equals(abc2) 返回 true...我错过了一些明显的东西吗?

是的,我意识到我可以将显式长度传递给构造函数,我只是想了解默认语义。

4

4 回答 4

15

在 Java 中,字符串不是以空值分隔的。从字节数组构造的字符串使用数组的整个长度。由于 0x00 将一对一转换为字符'\0',因此生成的字符串与整个数组的长度相同——32。当它被打印到 System.out 时,空字符的宽度为零,所以它看起来像“abc”,但实际上是“abc\0\0\0...”(对于 32 个字符)。

解决这个问题的原因trim()是它认为'\0'是空白。

请注意,如果要将字符串的以 null 分隔的字节表示形式转换为 a String,则需要找到要停止的索引。然后(正如@Brian 在他的评论中指出的那样),您可以使用不同的 String 构造函数:

String abc2 = new String(bytes, 0, indexOfFirstNull, Charset.forName("US-ASCII"));

但是,必须谨慎行事。您正在为平台使用 US-ASCII 字符集,其中第一个零字节的索引可能是自然停止的地方。但是,在许多字符集(例如 UTF-16)中,零字节可以作为实际文本的正常部分出现。

于 2012-10-04T14:53:34.007 回答
5

生成的 String 对象的长度通常是错误的。

不,这是对的——你只是误解了它的意义。它基本上是基于每个字节一个字符创建一个字符串 - 至少当您使用 US-ASCII 编码时。

奇怪的是,即使 abc2 不包含空格字符,abc2.trim() 返回相同的字符串,但长度调整为正确的值 3 并且 abc1.equals(abc2) 返回 true...我错过了一些明显的东西吗?

状态文档trim()(在两个不适用的条件之后):

  • 否则,设 k 为字符串中编码大于 '\u0020' 的第一个字符的索引,设 m 为字符串中编码大于 '\u0020' 的最后一个字符的索引。创建了一个新的 String 对象,表示这个字符串的子字符串,它以索引 k 处的字符开始,以索引 m 处的字符结束——即 this.substring(k, m+1) 的结果。

所以trim()基本上将“空白”视为等同于“U+0000 到 U+0020 包括”。这是“空白”的一种奇怪的不准确(阅读:基本上早于 Unicode)表示,但它确实解释了这种行为。

基本上你看到的是:

String trailingNulls = "abc\0\0\0\0\0\0";
String trimmed = trailingNulls.trim();
System.out.println(trimmed.length()); // 3

这与从字节数组构造字符串无关。

于 2012-10-04T14:55:22.693 回答
0

-首先String是java中的Object类型,Object类的equals()方法来比较它们..

例如:

"abc" .equals("abc")

-您可以\0通过 using 方法从结果字符串中删除trim(),然后您将得到您想要的结果......

于 2012-10-04T14:56:38.617 回答
0

首先分配的索引是错误的。他们应该是

        bytes[0] = 0x61; // 'a'
        bytes[1] = 0x62; // 'b'
        bytes[2] = 0x63; // 'c'
        bytes[3] = 0x00; // NUL

如果你检查上课的equals方法,String你就会知道原因。它正在迭代char[]并检查每个值是否为索引。因此,如果长度不同,char[]它将返回您false.

  while (n-- != 0) {
                if (v1[i++] != v2[j++])
                    return false;
            }

修复是使用trim

 abc2.equals(abc1.trim())

String#trim()的 Java 文档

否则,设 k 为字符串中编码大于 '\u0020' 的第一个字符的索引,设 m 为字符串中编码大于 '\u0020' 的最后一个字符的索引

于 2012-10-04T14:58:47.283 回答