常见的编码表
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:447441478@qq.com">宋进宇</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:447441478@qq.com">宋进宇</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 ) );
}
}
}