Java的中文问题由来已久前不久笔者需要做内存中的中文比较排序对字符串进行GBK或者GB编码以后使用pareTo方法仍然不能得到正确结果因此怀着怀疑的态度对JDK中String类的源代码做了一翻探究(作者使用JDK为版本)
以下是Stringjava中compareTo的源代码请注意其中的注释
public class String
{
…
public int compareTo(String anotherString) {
int len = count;
int len = unt;
//n为两个字符串长度的最小者
int n = Mathmin(len len);
//获取字符数组
char v[] = value;
char v[] = anotherStringvalue;
//取偏依位置
/** The offset is the first index of the storage that is used */
//offset 是第一个存储索引
int i = offset;
int j = anotherStringoffset;
//如果i == j
//这里可能是判断取同一内存中两个字符串的情景
// A < <
// Bs|
// C <|
// Ds
// E|
// F|
// G <
// 可能这种情况 i = j
if (i == j) {
int k = i;
int lim = n + i;
while (k < lim)
{
char c = v[k];
char c = v[k];
if (c != c) file://直到找到一个不相等的字符返回c c
return c c;
k++;
}
} else {
while (n != )
file://直到两个字符串长度记数为
{
char c = v[i++]; file://分别取字符
char c = v[j++];
if (c != c) {
//发现不相等立即返回c c;
return c c;
}
}
}
return len len;
//最后这里可能出现的情况是: 两个字符串比较完之后还没有得到结果相等的情况
}
…
}//end of class Stringc) file://直到找到一个不相等的字符返回c creturn c c; k++; } } else { while (n != ) file://直到两个字符串长度记数为{ char c = v[i++]; file://分别取字符 char c = v[j++];if (c != c) {//发现不相等立即返回c c;return c c; } } } return len len; //最后这里可能出现的情况是: 两个字符串比较完之后还没有得到结果相等的情况}…}//end of class String
为什么Java在做汉字的CompareTo时比较会有问题呢?通过对compareTo源代码的分析发现关键在于JDK的compareTo实现是直接使用Char来进行比较的
char c = v[k];
char c = v[k];
可是当Java使用GB编码时一个对汉字所获取到的Char值却是不规则的即一个汉字在Java中作为一个char来处理(双字节字符)时将这样的双字节字符进行强制转换成int类型时所得到的不是包含了汉字编码顺序的中文内码可以看一下一组测试数据可以看到其中奥妙
字符
Char值
Byte[]值
按Byte[]合成的值
我
[:]
[]
爱
[:]
[]
北
[:]
[]
京
[:]
[]
天
[:]
[]
安
[:]
[]
门
[:]
[]
A
[]
[]
B
[]
[]
C
[]
[]
D
[]
[]
按照中文顺序我字应该在爱字后面因此理论上来讲我字的Char值应该比爱字的char值要大可是不知道为什么Java的汉字char(两个byte)>int类型的转换会发生很大偏差而失去了汉字原本在GBK规范当中按内码排列好的顺序但从一个汉字拆分成个字节的byte[]时所得到的值并没有打乱GBK编码规定的顺序因此得到解决问题的思路将String进行GB编码后取得某个汉字获取其Char值时将汉字拆分成个字节byte[]再进行计算从而得到正确的内码
因此我自己写了下面这样几个函数基本上解决了汉字比较的问题
函数包括三个你可以随意放置到任何类当中作为辅助函数使用(Private Helper)
n public int compare(String s String s) 主要工作是为比较做一些前期的编码工作可以说是系统的一个外壳
n public int chineseCompareTo(String s String s)该函数则是中文字符串比较主体其内部实现了比较的最基本逻辑和JDK的compareTo所使用的逻辑是一样的调用接口也一样
n public static int getCharCode(String s)该函数则负责将一个以字符串形式存在的字符转换成为int编码儿不损失其位置信息注意输入通常是我或者A如果输入更长的字符串则改函数获得的是第一个字符的值
private static String __ENCODE__ = GBK; file://一定要是GBKprivate static String __SERVER_ENCODE__ = GB; file://服务器上的缺省编码/*比较两字符串*/ public int compare(String s String s) {String m_s = null m_s = null;try{ //先将两字符串编码成GBK m_s = new String ( sgetBytes(__SERVER_ENCODE__) __ENCODE__); m_s = new String ( sgetBytes(__SERVER_ENCODE__) __ENCODE__);}catch( Exception ex){ return pareTo(s);}int res = chineseCompareTo(m_s m_s); Systemoutprintln(比较 + s + | + s + ==== Result: + res);return res; } //获取一个汉字/字母的Char值 public static int getCharCode(String s) {if (s==null && sequals()) return ; file://保护代码byte [] b = sgetBytes();int value = ;//保证取第一个字符(汉字或者英文)for (int i = ; i < blength && i <= 2; i ++){ value = value * 100 + b[i];}return value; } //比较两个字符串 public int chineseCompareTo(String s1, String s2) {int len1 = s1.length();int len2 = s2.length(); int n = Math.min(len1, len2); for (int i = 0; i < n; i ++){ int s1_code = getCharCode(s1.charAt(i) + ""); int s2_code = getCharCode(s2.charAt(i) + ""); if (s1_code != s2_code) return s1_code - s2_code;}return len1 - len2; }
可见,对系统源代码的解剖,能让我们在迷惑之余同样有机会窥探系统内部运作的奥妙。Tw.WinGWit.CoM不过让人非常费解的是,Java内部的某些类书写风格非常不好,同时存在一些Bug。不过这也许是笔者个人感受。偶有所获,愿与大家共同分享,其中疏漏之处望不吝赐教。