背景:公司生产线上出现异常,报的错是记录日志时数据库长度超出,导致异常,经查询发现是由于在计算byte长度时出了问题。
问题代码:
operatorLog.setOperAfterData(updateString.substring(0,updateString.getBytes("gbk").length > 1024?1024-(updateString.getBytes("gbk").length - updateString.length()):updateString.length()));
只有一行,这么长一行代码,压根没法看,分解成如下代码:
int defaultLen = updateString.length(); int gbkLen = updateString.getBytes("gbk").length; operatorLog.setOperAfterData(updateString.substring(0, defaultLen > 1024 ? 1024 - (gbkLen - defaultLen) : defaultLen));
笔者目地是想让保存的日志长度限制为1024个字节-byte(不管中文还是英文);当文本中出现中文,一个字符占用两个bytes,但是英文只占用一个byte,首先得计算出文字中包含的中文字符个数,知道差别后不难得出中文长度为:gbkLen - defaultLen,假设字符长度为1025,里面中文有5个,此时gbkLength = 1030, defaultLen = 1025,此时得出的子字符串为1024 - 5 = 1019个字符,其实此时能保证这1019能够存储在1024字节的数据库字段中,因为这1019字符长度肯定不会超过1024;这是作者想要的目地
发生问题场景:
当字符里全是中文的时候会出现什么问题,比如1025个汉字,那得到的字符将是1024 - 1025 = -1,那在取子串的时候updateString.substring(0, -1);,这个时候就发生了我们在生产上跑的异常,数组越界,不可能取索引为-1的元素的值
当时想的解决办法:
当时就有点被绕进去了,最后写出来的代码竟然和原作者差不多一样,只是在最前面加了先取原串的1024个字符,这样确实是当汉字长度小于1024时,问题都好解决,但是实际情况往往不是这样的
问题依然出现:
目标是为了取得1024个字节,但是取得的值完全不对,此时假设字符串长度为513,全为汉字,做为字符串存至数据库时会超出长度1024,此时字节长度为1026
简单解决:
最后由于我们只是简单的记录日志,不做过多处理,决定只取512长度,超过512就不取了
真正解决:
在网上搜索后找到真正的解决办法:
使用循环对字条串的每个字符进行是否为中文判断或都判断将字符一个个读出来,取到规定长度:
String.valueOf(c).getBytes("GBK").length > 1
参考:
http://jingyan.baidu.com/article/1709ad80d383d44634c4f0dc.html
http://www.cnblogs.com/myphoebe/archive/2011/12/20/2294171.html
引伸:
Q: oracle在对字符进行存储时到底使用的是哪种方式,bytes?char?
A: 在定义时,oracle默认是以byte定义的,就是说如果定义成varchar(20), 理论上来说只能存储10个汉字,但对不同编码方式来说,又不一样,一个汉字在oracle中可能会占3个byte,这个是由oracle本身决定,有方法可以解决此问题,在定义表格时这样字义
create table ABC_TABLE (A_FIELD varchar2(20 char))
就表示字段A_FIELD会以字符存储,而不是以字节,当然对数据库也可以进行配置,参考以下: