从字节数组构造的Java字符串长度不正确

时间:2012-10-04 14:51:07

标签: java arrays string bytearray byte

我很难理解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 []构造函数的文档说明,“新String的长度是字符集的函数,因此可能不等于字节数组的长度。”确实如此,在US-ASCII字符集中,字符串“abc”的长度为3,而不是32。

奇怪的是,即使abc2不包含空白字符,abc2.trim()返回相同的字符串,但长度调整为正确值3,abc1.equals(abc2)返回true ...我错过了什么明显?

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

4 个答案:

答案 0 :(得分:14)

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

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

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

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

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

答案 1 :(得分: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

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

答案 2 :(得分:0)

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

<强>例如

"abc" .equals("abc")

- 您可以使用\0方法从结果字符串中删除trim(),然后您将获得所需的结果....

答案 3 :(得分: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'的字符串中最后一个字符的索引< / p>