将字节数组转换为字符串然后再转换回字节数组时,为什么长度不同?

我有以下Java代码:

byte[] signatureBytes = getSignature(); String signatureString = new String(signatureBytes, "UTF8"); byte[] signatureStringBytes = signatureString.getBytes("UTF8"); System.out.println(signatureBytes.length == signatureStringBytes.length); // prints false 

问:我可能误解了这一点,但我认为new String(byte[] bytes, String charset)String.getBytes(charset)是反向操作?

问:作为后续工作,将byte []数组作为String传输的安全方法是什么?

并非每个byte[]都是有效的UTF-8。 默认情况下,无效序列被固定字符替换,我认为这是长度变化的原因。

尝试使用Latin-1,它不应该发生,因为它是一个简单的编码,每个byte[]都是有意义的。

对于Windows-1252都不应该发生。 那里有未定义的序列(事实上是未定义的字节),但所有的字符都在一个字节中编码。 新byte[]可能与原始byte[]不同,但它们的长度必须相同。

我可能误解了这一点,但我认为新的String(byte [] bytes,String charset)和String.getBytes(charset)是反向操作?

不必要。

如果输入字节数组包含无效UTF-8的序列,则初始转换可能会将它们转换为(例如)问号。 第二个操作然后将这些转换为UTF-8编码'?' 字符….与原始表示不同。


确实,Unicode中的某些字符有多个表示forms; 例如,重音字符可以是单个代码点,也可以是基本字符代码点和重音代码点。 但是,在字节数组(包含有效的UTF-8)和String之间来回转换应该保留代码点序列。 它不执行任何“规范化”。


那么将byte []数组作为String传输的安全方法是什么呢?

最安全的替代方法是base64编码字节数组。 这具有额外的优点,即字符串中的字符将转换为可以表示拉丁字母和数字的任何字符集/编码。

另一种方法是使用Latin-1而不是UTF-8。 然而:

  • 如果数据(例如)被错误地解释为UTF-8,则存在损坏的风险。
  • 如果然后将“字符串”嵌入XML中,则此方法不合法。 许多控制字符在XML字符集之外,不能在XML文档中使用,甚至可以编码为字符实体。

我想到了两种可能性。

首先,您的签名不是完全有效的UTF8。 你不能只取任意二进制数据并“串”它。 并非每一个比特都定义了一个合法的字符。 String构造函数将为二进制数据插入一些默认替换内容,这些内容在UTF8中实际上并不代表任何内容。 这不是一个可逆的过程。 如果你想“串”一些任意的二进制数据,你需要使用一个已建立的方法这样做,我建议org.apache.commons.codec.binary.Base64

还有一些字符具有多个表示。 例如,带有重音符号的东西可以编码为重音字符,或者作为字符加上后面的重音来组合。 在编码之间来回移动时,无法保证这是一个可逆的过程。