public class JavaCharset {
public static void main(String[] args) {
// 源内容
// 1
String sourceContent = "1";
// 源字节数组,假设源内容的编码为UTF-16
// [-2, -1, 0, 49]
byte[] sourceBytes = sourceContent.getBytes(StandardCharsets.UTF_16);
// 新内容,将源字节数组按UTF-8转成新内容
// �� 1
String newContent = new String(sourceBytes, StandardCharsets.UTF_8);
// 新字节数组,将新内容再按UTF-8转回字节数组
// [-17, -65, -67, -17, -65, -67, 0, 49]
byte[] newBytes = newContent.getBytes(StandardCharsets.UTF_8);
}
}
如上所示,源内容的值为 1,如果按 UTF-16 转字节数组,因为 1 的Unicode编号范围(十六进制)为 0001 0000 ~ 0010 FFFF, 所以转成 UTF-16 是四字节,结果为 [-2, -1, 0, 49] 即源字节数组。
源字节数组再按 UTF-8 转成新内容时,因为 UTF-8 和 UTF-16 的编码规则不一样,所以将源字节数组中的四个字节识别成四个字符 �� 1即新内容。
将新内容再按 UTF-8 转回字节数组时,�作为特殊字符转成了三个字节 -17, -65, -67,所以新字节数组为 [-17, -65, -67, -17, -65, -67, 0, 49]。
综上,源字节数组转字符串再转新字节数组时,如果中间转换不是都用源字节数组的编码,则就会因为编码规则不同导致最终数据失真。
上面这种尴尬的情况会发生在如下请求:
- 文件下载的接口需要对文件内容进行加密,但公共组件只提供了对字符串的加密方法(主要用以json字符串),所以此时必须将文件流转为字符串,由于文件的编码未知,所以转换结果发生失真。
解决方案如下,byte大小为1字节,char大小为2字节,byte和char可以相互强转,所以可以将源字节数组强转成字符数组,字符数组生成字符串,后续可以循环字符串中的所有字符,并强转回字节数组,这样前后的字节数组没有失真,内容是一样的。
// 字符数组,将源字节数组强转成字符数组
// [, , , 1]
char[] chars = new char[sourceBytes.length];
for (int i = 0; i < sourceBytes.length; i++) {
chars[i] = (char) sourceBytes[i];
}
// 字符内容,由字符数组生成
// 1
String charContent = new String(chars);
// 将字符内容中的字符重新强转成字节
// [-2, -1, 0, 49]
byte[] charByte = new byte[charContent.length()];
for (int i = 0; i < charContent.length(); i++) {
charByte[i] = (byte) charContent.charAt(i);
}