常见的编码表
ASCII:美国标准信息交换码。
用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表
用一个字节的8位表示。
GB2312:中国的中文编码表。
GBK:中国的中文编码表升级,融合了更多的中文文字符号。
Unicode:国际标准码,融合了多种文字。
所有文字都用两个字节来表示,Java语言使用的就是unicode
UTF-8:一种变长的unicode码的实现方式,由1~4个字节表示。
字符编码
编码:字符串-->字节数组
解码:字节数组-->字符串
编码编错必挂,解码解错可以补救!
Unicode和UTF-8的关系
Unicode
世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。
可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。
Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。
UTF-8
UTF-8是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。UTF-8是Unicode的实现方式之一。
UTF-8的编码格式
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
代码演示:
import java.io.UnsupportedEncodingException; import org.junit.Test; /** * 2018年5月6日 上午8:11:54 * * @author <a href="mailto:[email protected]">宋进宇</a> * 演示编码和解码 */ public class EncodeAndDecode { ///////本次演示是在 UTF-8 环境下的/////// // 演示编码--把字符串编译成字节码 @Test public void encode() throws UnsupportedEncodingException { String str = "中国"; print( str.getBytes() ); // 采用默认码表 print( str.getBytes( "GBK" ) ); //采用指定码表进行编码,会有异常,为了逻辑看起来清晰直接抛 //观察结果 可以发现 同一个字符串 通过不同码表 进行编码,最后的 码值 是不一样的 //////////演示编码出错能否补救////////// byte[] bytes = str.getBytes( "ISO8859-1" );//西欧码表又称 拉丁1 print( bytes ); /* 输出结果: 63 63 * 可以发现 不同的 汉字 通过 拉丁1 码表 进行编码 得出的字节码 是两个相同的 码值 * 即使把得出的码值又通过 拉丁1 码表 进行解码,也是解析不出来的 */ System.out.println( new String( bytes, "ISO8859-1" ) ); //输出结果:?? //综上: 编码 出错是 无法补救 的!!! } /** * 打印字节数组 * * @param bytes * 字节数组 */ private void print(byte[] bytes) { for (byte b : bytes) { System.out.print( b + " " ); } System.out.println(); } // 演示解码--把解析字节码,把字节数组转换成字符串 @Test public void decode() throws UnsupportedEncodingException { // 通过上面演示编码的打印结果,进行解码 byte[] b = { -28, -72, -83, -27, -101, -67 }; //"中国"在UTF-8码表中对应的字节码 System.out.println( new String( b ) ); // 采用默认码表 System.out.println( new String( b, "GBK" ) ); //采用指定码表进行解码,会有异常,为了逻辑看起来清晰直接抛 /* * 输出结果: * 中国 * 涓浗 * * 观察发现出现乱码了 */ ///////////演示解码出错能否补救////////// String str = "涓浗"; byte[] bytes = str.getBytes( "GBK" ); System.out.println( new String( bytes ) ); /* 观察输出结果发现,采用指定码表去解码时, * 当 解码 的 码表 与 编码 的 码表 不同时, * 虽然 解析 结果错误,但是可以通过 错误的结果以解码时的码表,进行从新编码, * 生成 字节码 ,然后 通过 生成的字节码 以最原先 编码的码表 进行 解码, * 在输出结果就可以知道 这样是还原的,可以补救。 */ } }
字符串截取
在java中,字符串“abcd”与字符串“ab你好”的长度是一样,都是四个字符。
但对应的字节数不同,一个汉字占两个字节。
定义一个方法,按照指定的字节数来取子串。
如:对于“ab你好”,如果取三个字节,那么子串就是ab与“你”字的半个,那么半个就要舍弃。如果取四个字节就是“ab你”,取五个字节还是“ab你”。
import java.io.UnsupportedEncodingException; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import org.junit.Test; /** * 2018年5月6日 上午8:55:51 * @author <a href="mailto:[email protected]">宋进宇</a> * 练习:字符串截取 在java中,字符串“abcd”与字符串“ab你好”的长度是一样,都是四个字符。 但对应的字节数不同,一个汉字占两个字节。 定义一个方法,按照指定的字节数来取子串。 如:对于“ab你好”,如果取三个字节,那么子串就是ab与“你”字的半个, 那么半个就要舍弃。如果取四个字节就是“ab你”,取五个字节还是“ab你”。 */ public class CutString { //先测试一下 在不同 码表 下汉字对应的 字节码 //包含 汉字 的 常用码表 有: "GBK" 和 "UTF-8" 两种 @Test public void testCode() throws UnsupportedEncodingException{ String str = "ab你好"; print( str.getBytes( "GBK" ) ); //一个汉字两字节,且码值全为负 print( str.getBytes( "UTF-8" ) ); //一个汉字三个字节,且码值全为负 //测试非常陌生的 汉字 print( "鯡".getBytes( "GBK" ) ); //一个汉字两字节,第一字节为负,第二个为正 print( "鯡".getBytes( "UTF-8" ) ); //一个汉字三个字节,且码值全为负 /* 综上:GBK码表中一个汉字两字节,第一个字节为负,第二个字节不一定 * UTF-8码表中一个汉字三个字节,且码值全为负 */ } /** * 打印字节数组 * @param bytes 字节数组 */ private void print(byte[] bytes) { for (byte b : bytes) { System.out.print( b + " " ); } System.out.println(); } @Test public void testGBK() throws UnsupportedEncodingException{ String str = "ab你好鯡a汉字12"; for (int i = 0; i <= str.getBytes( "GBK" ).length; i++) { System.out.println( cutStringInGBK( str, i ) ); } } private static String cutStringInGBK(String str,int len){ //如果被切割的字符串为null 就返回 null if ( str == null ) { return null; } //先把字符串编译成字节码 try { byte[] bytes = str.getBytes( "GBK" ); /* 同观察 GBK 码表 的字节码 规律可以得出 : * 可以从字节数组的len-1开始找,并且统计字节值负个数, * 找到第一个字节值为非负数时就停止,如果统计的个数为奇数 说明 要舍弃最后一个字节,否则就不用舍弃 */ //如果 切割的长度小于0 则返回""; if ( len < 0 ) { return ""; } //如果 切割的长度大于 字节数组 则返回原字符串 if ( len > bytes.length ) { return str; } int count = 0; for (int i = len-1; i >= 0; i--) { if (bytes[i]<0) { count++; } else { break; } } return new String( bytes, 0, len-(count%2), "GBK" ); } catch (UnsupportedEncodingException e) { throw new RuntimeException( "该字符串不支持通过GBK码表进行编码", e ); } } private static String cutStringInUTF8(String str,int len){ //过程跟 GBK码表下 差不多 //如果被切割的字符串为null 就返回 null if ( str == null ) { return null; } //先把字符串编译成字节码 try { byte[] bytes = str.getBytes( "UTF-8" ); //如果 切割的长度小于0 则返回""; if ( len < 0 ) { return ""; } //如果 切割的长度大于 字节数组 则返回原字符串 if ( len > bytes.length ) { return str; } int count = 0; for (int i = len-1; i >= 0; i--) { if (bytes[i]<0) { count++; } else { break; } } return new String( bytes, 0, len-(count%3), "UTF-8" ); } catch (UnsupportedEncodingException e) { throw new RuntimeException( "该字符串不支持通过UTF-8码表进行编码", e ); } } //查看 系统 配置信息 @Test public void testSystemPropertys(){ Properties properties = System.getProperties(); Set<Entry<Object, Object>> entrySet = properties.entrySet(); for (Entry<Object, Object> entry : entrySet) { System.out.println( entry ); } } /** * 字符串截取 ,如果不是完整的汉字就舍弃 * @param str 被截取的字符串 * @param len 截取的长度 * @return 截取后的字符串 */ public static String cutString(String str,int len){ //防护 空指针异常 if ( str == null ) { return null; } if ( System.getProperty( "file.encoding" ).equalsIgnoreCase( "GBK" ) ) { return cutStringInGBK( str, len ); } else if ( System.getProperty( "file.encoding" ).equalsIgnoreCase( "UTF-8" ) ) { return cutStringInUTF8( str, len ); } return ""; } public static void main(String[] args) { String str = "ab你好鯡asd中文1223汉字sadsa"; for (int i = 0; i < str.getBytes().length; i++) { System.out.println( "len=" + i + ":" + cutString( str, i ) ); } } }