【问题标题】:Java String constructed from byte array has bad length从字节数组构造的 Java 字符串长度错误
【发布时间】:2012-10-04 14:51:06
【问题描述】:

我很难理解 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...我错过了什么吗明显吗?

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

【问题讨论】:

  • 您的意思是使用bytes[2] 而不是bytes[3]

标签: java arrays string bytearray byte


【解决方案1】:

在 Java 中,字符串不是以空值分隔的。从字节数组构造的字符串使用数组的整个长度。由于 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 用于澄清 Java 字符串不是 C 字符串 :) 也许还值得一提的是 String(byte[], int, int, String) 构造函数,它允许您指定用于创建字符串的确切字节子集。跨度>
【解决方案2】:

生成的 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

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

【讨论】:

  • 有趣的是,Character.isWhitespace('\0') 返回false
  • @TedHopp:当然。这是一个奇怪的空白定义,但它按照文档记录的。
【解决方案3】:

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

例如:

"abc" .equals("abc")

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

【讨论】:

    【解决方案4】:

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

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

    如果你检查String 类的equals 方法,你就会知道原因。它正在迭代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'的最后一个字符的索引

    【讨论】:

    • 是的,字节数组索引问题是我帖子中的一个错字,我深表歉意。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-05
    • 2020-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多