背景:公司生產線上出現異常,報的錯是記錄日志時數據庫長度超出,導致異常,經查詢發現是由於在計算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會以字符存儲,而不是以字節,當然對數據庫也可以進行配置,參考以下: