今天修复一个编码格式的bug,搜集了一些这方面的知识,总结一下吧。
需求:用户登录我们系统时候会去第三方系统获取用户基本信息,用户名(英文),job title等。这个流程是先读取信息,存储到我们的DB,然后从我们的DB读取数据进行展示。
bug: 当用户的job title含有®时,系统显示乱码。
原因分析(假设)
- 浏览器文字编码没有选择UTF-8
- 我们系统接收的就是乱码;
- 系统在转换存储的时候发出错误。
验证
假设一:选择UTF-8编码格式,仍然乱码
假设二:通过浏览器直接访问第三方系统,获取结果正常。
假设三:先排查转换,打印出结果确实是乱码。
把代码copy上来:
InputStream response = null;
ByteArrayOutputStream baos = null;
URL url = new URL(url_email);
HttpURLConnection urlConn = (HttpURLConnection) url
.openConnection();
urlConn.setRequestProperty("Accept-Charset", "utf-8");
response = urlConn.getInputStream();
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = response.read(buffer)) != -1) {
baos2.write(buffer, 0, len);
}
System.out.println(baos2.toString("utf-8"));
按照UTF-8的设置,不应该有问题才对。
没有办法,只能把®转换之后的int值打印出来看看,结果返回174,二进制为10101110。
int i = -1;
while ((i = response.read()) != -1) {
System.out.println(i);
baos.write(i);
}
对比下UTF-8的编码格式,174的编码应该是11000010 10101110。,两者结果不一致,那肯定是baos2.toString(“utf-8”)出了问题。少年,还是先看看文档吧。
果然,文档说的很明白了,*toSting(String charsetName)不是将内容转换成charsetName的格式,而是以charsetName的编码格式进行解码,返回系统默认的编码格式。所以说,我们用A编码方式去解码B的编码格式,肯定解析不了出现乱码了。经查,第三方系统返回的编码格式是ISO-8859-1, 所以我们改成baos2.toString(“ISO-8859-1”),一切OK了。
最后一个问题,我已经设置了accept charsetName为UTF-8去接受数据,为啥还是ISO-8859-1呢。推测第三方系统没有处理我们的参数,直接默认返回ISO-8859-1了。
总结
- 关于函数、参数的定义,要经常查Javadoc文档;
- 不要理所当然的认为别人的系统会鸟你;自己的系统应该统一用UTF-8的格式,以防别人系统调用;
- 在使用别人系统时,深入调研系统的支持编码格式,参数,甚至预测各种返回数据结果;
- 遇到问题不要怕,一步步推进。
注释:
UTF-8的编码规则很简单,只有二条1:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了编码规则,字母x表示可用编码的位。
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
——————–+———————————————
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
——————–+———————————————
跟据上表,解读UTF-8编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。
下面,还是以汉字”严”为例,演示如何实现UTF-8编码。
已知”严”的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此”严”的UTF-8编码需要三个字节,即格式是”1110xxxx 10xxxxxx 10xxxxxx”。然后,从”严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,”严”的UTF-8编码是”11100100 10111000 10100101”,转换成十六进制就是E4B8A5。