字符串
- 52、推荐使用String直接量赋值
- 53、注意方法中传递的参数要求
- 54、正确使用String、StringBuffer、StringBuilder
- 55、注意字符串的位置
- 56、自由选择字符串拼接方法
- 57、推荐在复杂字符串操作中使用正则表达式
- 58、强烈建议使用UTF编码
- 59、对字符串排序持一种宽容的心态
52、推荐使用String直接量赋值
一般对象都是通过new关键字生成的,但是String还有第二种生成方式,也就是我们经常使用的直接声明方式,比如Str str = “a”,即是通过直接量“a”进行赋值的。对于String对象来说,这种方式是极力推荐的,但不建议使用new String(“a”)的方式赋值。为什么呢?我们来看一段程序:
publicclassClient{publicstaticvoidmain(String[]args){Stringstr1="中国";Stringstr2="中国";Stringstr3=newString("中国");Stringstr4=str3.intern();//两个直接量是否相等booleanb1=(str1==str2);//trueSystem.out.println(b1);//直接量和对象是否相等booleanb2=(str1==str3);//falseSystem.out.println(b2);//经过intern处理后的对象与直接量是否相等booleanb3=(str1==str4);//trueSystem.out.println(b3);}}注意看上面的程序,我们使用“==”判断的是两个对象的引用地址是否相同,也就是判断是否为同一个对象,打印的结果是true,false,true。即有两个直接量是同一个对象(经过intern处理后的String与直接量是同一个对象),但直接通过new生成的对象却与之不相等,原因何在?
原因是Java为了避免在一个系统中大量产生String对象(为什么会大量产生?因为String字符串是程序中最经常使用的类型),于是就设计了一个字符串池(也有叫做字符串常量池,String Pool或String Constant Pool 或String Literal Pool),在字符串池中所容纳的都是String字符串对象,它的创建机制是这样的:创建一个字符串时,首先检查池中是否有字面值相等的字符串,如果有,则不再创建,直接返回池中该对象的引用,若没有则创建之,然后放到池中,并返回新建对象的引用,这个池和我们平常所说的池概念非常相似。对于此例子来说,就是在创建第一个“中国”字符串时,先检查字符串池中有没有该对象,发现没有,于是就创建了“中国”这个字符串并放到池中,待再创建str2字符串时,由于池中已经有了该字符串,于是就直接返回了该对象的引用,此时,str1和str2指向的是同一个地址,所以使用“==”来判断那当然是相等的了。
那为什么使用new String(“中国”)就不相等了呢?因为直接声明一个String对象是不检查字符串池的,也不会把对象放到池中,那当然“==”为false了。
那为什么使用intern方法处理后就又相等了呢?因为intern会检查当前的对象在对象池中是否有字面值相同的引用对象,如果有则返回池中对象,如果没有则放置到对象池中,并返回当前对象。
可能有读者要问了,对象放到池中会不会产生线程安全问题呀?好问题,不过Java已经考虑到了,String类是一个不可变(Immutable)对象其实有两层意思:一是String类是final类,不可继承,不可能产生一个String的子类;二是在String类提供的所有方法中,如果有String返回值,就会新建一个String对象,不对原对象进行修改,这也就保证了原对象是不可改变的。
还有读者问了,放到池中,是不是要考虑垃圾回收问题呀?不用考虑了,虽然Java的每个对象都保存在堆内存中,但是字符串池非常特殊,它在编译期已经决定了其存在JVM的常量池(Constant Pool),垃圾回收器是不会对它进行回收的。
通过上面的介绍,我们发现Java在字符串的创建方面确实提供了非常好的机制,利用对象池不仅可以提高效率,同时也减少了内存空间的占用,建议大家在开发中使用直接量赋值方式,除非确有必要才新建立一个String对象。
53、注意方法中传递的参数要求
有这样一个简单需求:写一个方法,实现从原始字符串中删除与之匹配的所有子字符串,比如在“蓝蓝的天,白云飘”中,删除“白云飘”,输出“蓝蓝的天,”,代码如下:
publicclassStringUtils{//删除字符串publicstaticStringremove(Stringsource,Stringsub){returnsource.replaceAll(sub,"");}}StringUtils工具类很简单,它采用了String的replace方法,该方法是做字符串替换的,我们来编写一个测试用例,检查remove方法是否正确,如下所示:
assertTrue(StringUtils.remove("好是好","好").equals("是"));测试的结果是绿条(Green Bar),正确无误,但是再看看如下的测试用例:
assertTrue(StringUtils.remove("$是$","$").equals("是"));上面只是把“好是好”中的两个“好”字替换成了一个“$"符号,猜猜结果会是什么,应该也是绿条吧?但是非常遗憾,结果是红条,测试未通过。就这么简单一个的替换,为什么测试通不过呢?
问题就出在了replaceAll方法上,该方法确实需要传递两个String类型的参数,也确实进行了字符串替换,但是它要求第一个参数是一个正则表达式,符合正则表达式的字符串才会被替换。对上面的例子来说,第一个测试案例传递进来的是一个字符串“好”,这是一个全匹配查找替换,处理得非常正确,第二个测试案例传递进来的是“”符号,“ ”符号,“”符号,“”符号在正则表达式中表示的是字符串的结束位置,也就是说执行完repalceAll后,在字符串结尾的地方加上了空字符串,其结果还是“是 是是”,所以测试失败也就在所难免了。问题清楚了,解决方案也就出来了:使用replace方法替代即可,它是repalceAll方法的简化版,可传递两个String参数继续替换,与我们的编码意图是相吻合的。
读者如果注意看JDK文档,会发现replace(CharSequence target,CharSequencereplacement)方法是在1.5版本以后才开始提供的,在此之前如果要对一个字符串进行全替换,只能使用replaceAll方法,不过由于replaceAll方法的第二个参数使用了正则表达式,而且参数类型只要是CharSequence就可以(String的父类),所以很容易让使用者误解,稍有不慎就会导致严重的替换错误。
注意replaceAll传递的第一个参数是正则表达式。
54、正确使用String、StringBuffer、StringBuilder
CharSequence接口有三个实现类与字符串有关:String、StringBuffer、StringBuilder,虽然它们都与字符串有关,但是其处理机制是不同的。
String类是不可改变的量,也就是创建后就不能再修改了,比如创建了一个“abc”这样的字符串对象,那么它在内存中永远都会是“abc”这样具有固定表面值的一个对象,不能被修改,即使想通过String提供的方法来尝试修改,也是要么创建一个新的字符串对象,要么返回自己,比如:
Stringstr="abc";Stringstr1=str.substring(1);其中,str是一个字符串对象,其值是“abc”,通过substring方法又重新生成了一个字符串str1,它的值是“bc”,也就是说str引用的对象一旦产生就永远不会改变。为什么上面还说有可能不创建对象而返回自己呢?那是因为采用str.substring(0)就不会创建新对象,JVM会从字符串池中返回str的引用,也就是自身的引用。
StringBuffer是一个可变字符序列,它与String一样,在内存中保存的都是一个有序的字符序列(char类型的数组),不同点是StringBuffer对象的值是可改变的,例如:
StringBuffersb=newStringBuffer("a");sb.append("b");从上面的代码可以看出sb的值在改变,初始化的时候是“a”,经过append方法后,其值变成了“ab”。可能有读者会问了,这与String类通过“+”连接有什么区别?例如:
Strings="a";s=s+"b";有区别,字符串变量s初始化时是“a”对象的引用,经过加号计算后,s变量就修改为了“ab”的引用,但是初始化的“a”对象还是没有改变,只是变量s指向了新的引用地址。再看看StringBuffer的对象,它的引用地址虽不变,但值在改变。
StringBuilder与StringBuffer基本相同,都是可变字符序列,不同点是:StringBuffer是线程安全的,StringBuilder是线程不安全的,翻翻两者的源代码,就会发现在StringBuffer的方法前都有synchronized关键字,这也是StringBuffer在性能上远低于StringBuilder的原因。
在性能方面,由于String类的操作都是产生新的String对象,而StringBuilder和StringBuffer只是一个字符数组的再扩容而已,所以String类的操作要远慢于StringBuffer和StringBuilder。
弄清楚了三者的原理,我们就可以在不同的场景下使用不同的字符序列了:
- (1)使用String类的场景
在字符串不经常变化的场景中可以使用String类,例如常量的声明、少量的变量运算等。 - (2)使用StringBuffer类的场景
在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程的环境中,则可以考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装等。 - (3)使用StringBuilder类的场景
在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程的环境中,则可以考虑使用StringBuilder,如SQL语句的拼装、JSON封装等。
注意在适当的场景选用字符串类型。
55、注意字符串的位置
看这样一段程序:
publicstaticvoidmain(String[]args){Stringstr1=1+2+" apples";Stringstr2="apples:"+1+2;}想想看这两个字符串输出的苹果数量是否一致?如果一致,那是几个呢?
答案是不一致,str1的值是“3 apples”,str2的值是“apples:12”,这中间悬殊很大,只是把“apples”调换了一下位置,为何会发生如此大的变化呢?
这都源于Java对加号的处理机制:在使用加号进行计算的表达式中,只要遇到String字符串,则所有的数据都会转换为String类型进行拼接,如果是原始数据,则直接拼接,如果是对象,则调用toString方法的返回值然后拼接,如:
str=str+newArrayList();上面就是调用ArrayList对象的toString方法返回值进行拼接的。再回到前面的问题上,对于str1字符串,Java的执行顺序是从左到右,先执行1+2,也就是算数加法运算,结果等于3,然后再与字符串进行拼接,结果就是“3apples”,其形式类似于如下计算:
Stringstr1=(1+2)+" apples";而对于str2字符串,由于第一个参与运算的是String类型,加上1后的结果是“apples:1”,这仍然是一个字符串,然后再与2相加,其结果还是一个字符串,也就是“apples:12”。这说明如果第一个参数是String,则后续的所有计算都会转变成String类型,谁让字符串是老大呢!
注意在“+”表达式中,String字符串具有最高优先级。
56、自由选择字符串拼接方法
对一个字符串进行拼接有三种方法:加号、concat方法及StringBuilder(或StringBuffer,由于StringBuffer的方法与StringBuilder相同,文中不再赘述)的append方法,其中加号是最常用的,其他两种方式偶尔会出现在一些开源项目中,那这三者之间有什么区别吗?我们来看下面的例子:
//加号拼接str+="c";//concat方法连接str=str.concat("c");上面是两种不同的字符串拼接方式,循环5万次后再检查其执行的时间,加号方式的执行时间是1438毫秒,而concat方法的执行时间是703毫秒,时间相差1倍,如果使用StringBuilder方式,执行时间会更少,其代码如下:
publicstaticvoiddoWithStringBuffer(){StringBuildersb=newStringBuilder("a");for(inti=0;i<50000;i++){sb.append("c");}Stringstr=sb.toString();}StringBuffer的append方法的执行时间是0毫秒,说明时间非常非常短暂(毫秒不足以计时,读者可以使用纳秒进行计算)。这个实验也说明在字符串拼接方式中,append方法最快,concat方法次之,加号最慢,这是为何呢?
- (1)“+”方法拼接字符串
虽然编译器对字符串的加号做了优化,它会使用StringBuilder的append方法进行追加,按道理来说,其执行时间也应该是0毫秒,不过它最终是通过toString方法转换成String字符串的,例子中“+”拼接的代码与如下代码相同:
str=newStringBuilder(str).append("c").toString();注意看,它与纯粹使用StringBuilder的append方法是不同的:一是每次循环都会创建一个StringBuilder对象,二是每次执行完毕都要调用toString方法将其转换为字符串—它的执行时间就是耗费在这里了!
- (2)concat方法拼接字符串
我们从源码上看一下concat方法的实现,代码如下:
publicStringconcat(Stringstr){intotherLen=str.length();//如果追加的字符串长度为0,则返回字符串本身if(otherLen==0){returnthis;}//字符数组,容纳的是新字符串的字符charbuf[]=newchar[count+otherLen];//取出原始字符串放到buf数组中getChars(0,count,buf,0);//追加的字符串转化成字符数组,添加到buf中str.getChars(0,otherLen,buf,count);//复制字符数组,产生一个新的字符串returnnewString(0,count+otherLen,buf);}其整体看上去就是一个数组拷贝,虽然在内存中的处理都是原子性操作,速度非常快,不过,注意看最后的return语句,每次的concat操作都会新创建一个String对象,这就是concat速度慢下来的真正原因,它创建了5万个String对象呀!
- (3)append方法拼接字符串
StringBuilder的append方法直接由父类AbstractStringBuilder实现,其代码如下:
publicAbstractStringBuilderappend(Stringstr){//如果是null值,则把null作为字符串处理if(str==null)str="null";intlen=str.length();//字符串长度为0,则返回自身if(len==0)returnthis;intnewCount=count+len;//追加后的字符数组长度是否超过当前值if(newCount>value.length)expandCapacity(newCount);//加长,并做数组拷贝//字符串复制到目标数组str.getChars(0,len,value,count);count=newCount;returnthis;}看到没,整个append方法都在做字符数组处理,加长,然后数组拷贝,这些都是基本的数据处理,没有新建任何对象,所以速度也就最快了!注意:例子中是在最后通过StringBuffer的toString返回了一个字符串,也就是说在5万次循环结束后才生成了一个String对象。
三者的实现方法不同,性能也就不同,但并不表示我们一定要使用StringBuilder,这是因为“+”非常符合我们的编码习惯,适合人类阅读,两个字符串拼接,就用加号连一下,这很正常,也很友好,在大多数情况下我们都可以使用加号操作,只有在系统性能临界(如在性能“增之一分则太长”的情况下)的时候才可以考虑使用concat或append方法。而且,很多时候系统80%的性能是消耗在20%的代码上的,我们的精力应该更多的投入到算法和结构上。
注意适当的场景使用适当的字符串拼接方式。
57、推荐在复杂字符串操作中使用正则表达式
字符串的操作,诸如追加、合并、替换、倒序、分割等,都是在编码过程中经常用到的,而且Java也提供了append、replace、reverse、split等方法来完成这些操作,它们使用起来也确实方便,但是更多的时候,需要使用正则表达式来完成复杂的处理,我们来看一个例子:统计一篇文章中英文单词的数量,很简单吧?代码如下:
publicstaticvoidmain(String[]args){//接收键盘输入Scannerinput=newScanner(System.in);while(input.hasNext()){Stringstr=input.nextLine();//使用split方法分隔后统计intwordsCount=str.split(" ").length;System.out.println(str+" 单词数:"+wordsCount);}}使用split方法根据空格来分割单词,然后计算分隔后的数组长度,这种方法可靠吗?可行吗?我们来看输出:
TodayisMondayTodayisMonday单词数:3TodayisMondayTodayisMonday单词数:4TodayisMonday?No!TodayisMonday?No!单词数:3I'mOk.I'mOk.单词数:2注意看输出,除了第一个输入“Todady is Monay”正确外,其他都是错误的!第二条输入中单词“Monday”前有2个连续的空格,第三条输入中“NO”单词的前后都没有空格,最后一个输入则没有把连写符号“'”考虑进去,这样统计出来的单词数量肯定错误一堆,那怎么做才合理呢?
如果考虑使用一个循环来处理这样的“异常”情况,会使程序的稳定性变差,而且要考虑太多太多的因素,这让程序的复杂性也大大提高了。那如何处理呢?可以考虑使用正则表达式,代码如下:
publicstaticvoidmain(String[]args){//接收键盘输入Scannerinput=newScanner(System.in);while(input.hasNext()){Stringstr=input.nextLine();//正则表达式对象Patternpattern=Pattern.compile("\\b\\w+\\b");//生成匹配器Matchermatcher=pattern.matcher(str);//记录单词数量intwordsCount=0;//遍历查找匹配,统计单词数量while(matcher.find()){wordsCount++;}System.out.println(str+" 单词数:"+wordsCount);}}准不准确,我们来看相同的输入所产生的结果:
TodayisMondayTodayisMonday单词数:3TodayisMondayTodayisMonday单词数:3TodayisMonday?No!TodayisMonday?No!单词数:4I'mOk.I'mOk.单词数:3每项的输出都是准确的,而且程序也不复杂,先生成一个正则表达式对象,然后使用匹配器进行匹配,之后通过一个while循环统计匹配的数量。需要说明的是,在Java的正则表达式中“\b”表示的是一个单词的边界,它是一个位置界定符,一边为字符或数字,另外一边则非字符或数字,例如“A”这样一个输入就有两个边界,即单词“A”的左右位置,这也就说明了为什么要加上“\w”(它表示的是字符或数字)。
正则表达式在字符串的查找、替换、剪切、复制、删除等方面有着非凡的作用,特别是面对大量的文本字符需要处理(如需要读取大量的LOG日志)时,使用正则表达式可以大幅地提高开发效率和系统性能,但是正则表达式是一个恶魔(Regular Expressions is evil),它会使程序难以读懂,想想看,写一个包含^、$、\A、\s、\Q、+、?、()、[]、{}等符号的正则表达式,然后告诉你这是一个“这样,这样……”的字符串查找,你是不是要崩溃了?这代码只有上帝才能看懂了!
注意正则表达式是恶魔,威力巨大,但难以控制。
58、强烈建议使用UTF编码
Java的乱码问题由来已久,有点经验的开发人员肯定都遇到过乱码问题,有时是从Web上接收的乱码,有时是从数据库中读取的乱码,有时是在外部接口中接收到的乱码文件,这些都让我们困惑不已,甚至是痛苦不堪,看如下代码:
publicstaticvoidmain(String[]args)throwsException{Stringstr="汉字";//读取字节byte[]b=str.getBytes("UTF-8");//重新生成一个新的字符串System.out.println(newString(b));}Java文件是通过IDE工具默认创建的,编码格式是GBK,大家想想看上面的输出结果会是什么?可能是乱码吧?两个编码格式不相同。我们暂不公布结果,先解释一下Java中的编码规则。Java程序涉及的编码包括两部分:
- (1)Java文件编码
如果我们使用记事本创建一个.java后缀的文件,则文件的编码格式就是操作系统默认的格式。如果是使用IDE工具创建的,如Eclipse,则依赖于IDE的设置,Eclipse默认是操作系统编码(Windows一般为GBK)。 - (2)Class文件编码
通过javac命令生成的后缀名为.class的文件是UTF-8编码的UNICODE文件,这在任何操作系统上都是一样的,只要是class文件就会是UNICODE格式。需要说明的是,UTF是UNICODE的存储和传输格式,它是为了解决UNICODE的高位占用冗余空间而产生的,使用UTF编码就标志着字符集使用的是UNICODE。
再回到我们的例子上,getBytes方法会根据指定的字符集提取出字节数组(这里按照UNICODE格式来提取),然后程序又通过newString(byte[] bytes)重新生成一个字符串。来看看String这个构造函数:通过操作系统默认的字符集解码指定的byte数组,构造一个新的String。结果已经很清楚了,如果操作系统是UTF-8编码的话,输出就是正确的,如果不是,则会是乱码。由于这里使用的是默认编码GBK,那么输出的结果也就是乱码了。我们再详细分解一下运行步骤:- 步骤1 创建Client.java文件。
该文件的默认编码GBK(如果使用Eclipse,则可以在属性查看到)。 - 步骤2 编写代码(如上)。
- 步骤3 保存,并使用javac编译。
注意我们没有使用“javac-encoding GBK Client.java”显式声明Java的编码格式,javac会自动按照操作系统的编码(GBK)读取Client.java文件,然后将其编译成.class文件。 - 步骤4 生成.class文件。
编译结束,生成.class文件,并保存到硬盘上。此时.class文件使用的是UTF-8格式编码的UNICODE字符集,可以通过javap命令阅读class文件。其中“汉字”变量也已经由GBK编码转变成UNICODE格式了。 - 步骤5 运行main方法,提取“汉字”的字节数组。
“汉字”原本是按照UTF-8格式保存的,要再提取出来当然没有任何问题了。 - 步骤6 重组字符串。
读取操作系统的编码格式(GBK),然后重新编码变量b的所有字节。问题就在这里产生了:因为UNICODE的存储格式是两个字节表示一个字符(注意这里是指UCS-2标准),虽然GBK也是2个字节表示一个字符,但两者之间没有影射关系,要想做转换只能读取映射表,不能实现自动转换—于是JVM就按照默认的编码格式(GBK)读取了UNICODE的两个字节。 - 步骤7 输出乱码,程序运行结束。
问题清楚了,解决方案也随之产生,方案有两个。 - 步骤8 修改代码。
明确指定编码即可,代码如下:
System.out.println(newString(b,"UTF-8"));- 步骤9 修改操作系统的编码方式。
各个操作系统的修改方式不同,不再赘述。
- 步骤1 创建Client.java文件。
我们可以把从字符串读取字节的过程看作是数据传输的需要(比如网络、存储),而重组字符串则是业务逻辑的需求,这样就可使乱码现场重现:通过JDBC读取的字节数组是GBK的,而业务逻辑编码时采用的是UTF-8,于是乱码产生了。对于此类问题,最好的解决办法就是使用统一的编码格式,要么都用GBK;要么都用UTF-8,各个组件、接口、逻辑层都用UTF-8,拒绝独树一帜的情况。
问题解释清楚了,我们再来看以下代码:
publicclassClient{publicstaticvoidmain(String[]args)throwsException{Stringstr="汉字";//读取字节byte[]b=str.getBytes("GB2312");//重新生成一个新的字符串System.out.println(newString(b));}}仅仅修改了读取字节的编码格式(修改成了GB2312格式的),结果会是怎样的呢?又或者将其修改成GB18030,结果又是怎样的呢?结果都是“汉字”,不是乱码。哈哈,这是因为GB2312是中文字符集的V1.0版,GBK是V2.0版本,GB18030是V3.0版,版本是向下兼容的,只是它们包含的汉字数量不同而已,注意,UNICODE可不在这个序列之内的。
注意一个系统使用统一的编码。
59、对字符串排序持一种宽容的心态
在Java中一涉及中文处理就会冒出很多问题来,其中排序也是一个让人头疼的课题,我们来看下面的代码:
publicstaticvoidmain(String[]args){String[]strs={"张三(Z)","李四(L)","王五(W)"};//排序,默认是升序Arrays.sort(strs);inti=0;for(Stringstr:strs){System.out.println((++i)+"、"+str);}}上面的代码定义一个数组,然后进行升序排序,我们期望的结果是按照拼音升序排列,即为李四、王五、张三,但是结果却不是这样的:
1、张三(Z)2、李四(L)3、王五(W)这是按照什么排序的呀,非常混乱!我们知道Arrays工具类的默认排序是通过数组元素的compareTo方法来进行比较的,那我们来看String类的compareTo的主要实现:
while(k<lim){//原字符串的字符数组charc1=v1[k];//比较字符串的字符数组charc2=v2[k];if(c1!=c2){//比较两者的char值大小returnc1-c2;}k++;}上面的代码先取得字符串的字符数组,然后一个一个地比较大小,注意这里是字符比较(减号操作符),也就是UNICODE码值的比较,查一下UNICODE代码表,“张”的码值是5F20,而“李”是674E,这样一看,“张”排在“李”的前面也就很正确了—但这明显与我们的意图冲突了。这一点在JDK文档中也有说明:对于非英文的String排序可能会出现不准确的情况。那该如何解决这个问题呢?Java推荐使用Collator类进行排序,那好,我们把代码修改一下:
publicstaticvoidmain(String[]args)throwsException{String[]strs={"张三(Z)","李四(L)","王五(W)"};//定义一个中文排序器Comparatorc=Collator.getInstance(Locale.CHINA);//升序排列Arrays.sort(strs,c);inti=0;for(Stringstr:strs){System.out.println((++i)+"、"+str);}}输出结果如下:
1、李四(L)2、王五(W)3、张三(Z)这确实是我们期望的结果,应该举杯庆贺了吧!但是且慢,中国的汉字博大精深,Java是否都能精确的排序呢?最主要的一点是汉字中有象形文字,音形分离,是不是每个汉字都能按照拼音的顺序排列好呢?我们写一个复杂的汉字来看看:
publicstaticvoidmain(String[]args)throwsException{String[]strs={"犇(B)","鑫(X)"};Arrays.sort(strs,Collator.getInstance(Locale.CHINA));inti=0;for(Stringstr:strs){System.out.println((++i)+"、"+str);}}三个牛“犇”读bēn,三个金“鑫”读xīn,这两个字经常出现在饭店和商店的名称上,我们来看排序的输出结果:
1、鑫(X)2、犇(B)输出结果又乱了!不要责怪Java,它已经尽量为我们考虑了,只是因为我们的汉字文化太博大精深了,要做好这个排序确实有点难为它。更深层次的原因是Java使用的是UNICODE编码,而中文UNICODE字符集是来源于GB18030的,GB18030又是从GB2312发展起来,GB2312是一个包含了7000多个字符的字符集,它是按照拼音排序,并且是连续的,之后的GBK、GB18030都是在其基础上扩充出来的,所以要让它们完整排序也就难上加难了。
如果是排序对象是经常使用的汉字,使用Collator类排序完全可以满足我们的要求,毕竟GB2312已经包含了大部分的汉字,如果需要严格排序,则要使用一些开源项目来自己实现了,比如pinyin4j可以把汉字转换为拼音,然后我们自己来实现排序算法,不过此时你也会发现要考虑诸如算法、同音字、多音字等众多问题。
注意如果排序不是一个关键算法,使用Collator类即可。