java面试题:
编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。但是要保证汉字不被截半个,如"我ABC"4,应该截为"我AB",输入"我ABC汉DEF",6,应该输出为"我ABC"而不是"我ABC+汉的半个"。
目前很多流行的语言,如C#、Java内部采用的都是 Unicode 16(UCS2)编码,在这种编码中所有的字符都是两个字符,因此,如果要截取的字符串是中、英文、数字混合的,就会产生问题,如下面的字符串:
String s = "a加b等于c,如果a等1、b等于2,那么c等3";
上面的字符串既有汉字,又有英文字符和数字。如果要截取前6个字节的字符,应该是”a加b等",但如果用substring方法截取前6个字符就成了"a 加b等于c"。产生这个问题的原因是将substring方法将双字节的汉字当成一个字节的字符(UCS2字符)处理了。
英文字母和中文汉字在不同的编码格式下,所占用的字节数也是不同的,我们可以通过下面的例子来看看在一些常见的编码格式下,一个英文字母和一个中文汉字分别占用多少字节。
- import java.io.UnsupportedEncodingException;
- public class EncodeTest {
- /**
- * 打印字符串在指定编码下的字节数和编码名称到控制台
- *
- * @param s
- * 字符串
- * @param encodingName
- * 编码格式
- */
- public static void printByteLength(String s, String encodingName) {
- System.out.print("字节数:");
- try {
- System.out.print(s.getBytes(encodingName).length);
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- System.out.println(";编码:" + encodingName);
- }
- public static void main(String[] args) {
- String en = "A";
- String ch = "人";
- // 计算一个英文字母在各种编码下的字节数
- System.out.println("英文字母:" + en);
- EncodeTest.printByteLength(en, "GB2312");
- EncodeTest.printByteLength(en, "GBK");
- EncodeTest.printByteLength(en, "GB18030");
- EncodeTest.printByteLength(en, "ISO-8859-1");
- EncodeTest.printByteLength(en, "UTF-8");
- EncodeTest.printByteLength(en, "UTF-16");
- EncodeTest.printByteLength(en, "UTF-16BE");
- EncodeTest.printByteLength(en, "UTF-16LE");
- System.out.println();
- // 计算一个中文汉字在各种编码下的字节数
- System.out.println("中文汉字:" + ch);
- EncodeTest.printByteLength(ch, "GB2312");
- EncodeTest.printByteLength(ch, "GBK");
- EncodeTest.printByteLength(ch, "GB18030");
- EncodeTest.printByteLength(ch, "ISO-8859-1");
- EncodeTest.printByteLength(ch, "UTF-8");
- EncodeTest.printByteLength(ch, "UTF-16");
- EncodeTest.printByteLength(ch, "UTF-16BE");
- EncodeTest.printByteLength(ch, "UTF-16LE");
- }
- }
运行结果如下:
- 英文字母:A
- 字节数:1;编码:GB2312
- 字节数:1;编码:GBK
- 字节数:1;编码:GB18030
- 字节数:1;编码:ISO-8859-1
- 字节数:1;编码:UTF-8
- 字节数:4;编码:UTF-16
- 字节数:2;编码:UTF-16BE
- 字节数:2;编码:UTF-16LE
- 中文汉字:人
- 字节数:2;编码:GB2312
- 字节数:2;编码:GBK
- 字节数:2;编码:GB18030
- 字节数:1;编码:ISO-8859-1
- 字节数:3;编码:UTF-8
- 字节数:4;编码:UTF-16
- 字节数:2;编码:UTF-16BE
- 字节数:2;编码:UTF-16LE
UTF-16BE和UTF-16LE是UNICODE编码家族的两个成员。UNICODE标准定义了UTF-8、UTF-16、UTF-32三种编码格式,共有UTF-8、UTF-16、UTF-16BE、UTF-16LE、UTF-32、UTF-32BE、UTF-32LE七种编码方案。JAVA所采用的编码方案是UTF-16BE。从上例的运行结果中我们可以看出,GB2312、GBK、GB18030三种编码格式都可以满足题目的要求。下面我们就以GBK编码为例来进行解答。
我们不能直接使用String类的substring(int beginIndex, int endIndex)方法,因为它是按字符截取的。'我'和'Z'都被作为一个字符来看待,length都是1。实际上我们只要能区分开中文汉字和英文字母,这个问题就迎刃而解了,而它们的区别就是,中文汉字是两个字节,英文字母是一个字节。
以下是转载自http://www.javaeye.com/topic/216577的代码
下面这篇文字也有利于大家理解:
Sunshine 的 String.getBytes()和new String()在Java中,String.getBytes(String decode)方法会根据指定的decode编码返回某字符串在该编码下的byte数组表示,如
byte[] b_gbk = "中".getBytes("GBK");
byte[] b_utf8 = "中".getBytes("UTF-8");
byte[] b_iso88591 = "中".getBytes("ISO8859-1");byte[] b_unicode = "中".getBytes("unicode");
将分别返回“中”这个汉字在GBK、UTF-8和ISO8859-1编码下的byte数组表示,此时b_gbk的长度为2,b_utf8的长度为3,b_iso88591的长度为1,b_unicode 的长度为4(系统的的unicode采用的是big-endian就是前面是两个字节来表示这个的,unicode采用的都是两个字节编码,所以后面是4个字节 )。
而与getBytes相对的,可以通过new String(byte[], decode)的方式来还原这个“中”字时,这个new String(byte[], decode)实际是使用decode指定的编码来将byte[]解析成字符串。
String s_gbk = new String(b_gbk,"GBK");
String s_utf8 = new String(b_utf8,"UTF-8");
String s_iso88591 = new String(b_iso88591,"ISO8859-1");通过打印s_gbk、s_utf8和s_iso88591,会发现,s_gbk和s_utf8都是“中”,而只有s_iso88591是一个不认识的字符,为什么使用ISO8859-1编码再组合之后,无法还原“中”字呢,其实原因很简单,因为ISO8859-1编码的编码表中,根本就没有包含汉字字符,当然也就无法通过"中".getBytes("ISO8859-1");来得到正确的“中”字在ISO8859-1中的编码值了,所以再通过new String()来还原就无从谈起了。
因此,通过String.getBytes(String decode)方法来得到byte[]时,一定要确定decode的编码表中确实存在String表示的码值,这样得到的byte[]数组才能正确被还原。
有时候,为了让中文字符适应某些特殊要求(如http header头要求其内容必须为iso8859-1编码),可能会通过将中文字符按照字节方式来编码的情况,如
String s_iso88591 = new String("中".getBytes("UTF-8"),"ISO8859-1"),这样得到的s_iso8859-1字符串实际是三个在ISO8859-1中的字符,在将这些字符传递到目的地后,目的地程序再通过相反的方式String s_utf8 = new String(s_iso88591.getBytes("ISO8859-1"),"UTF-8")来得到正确的中文汉字“中”。这样就既保证了遵守协议规定、也支持中文。