// 例子1int i = 30000;char c = (char)i;System.out.println(i);System.out.println(c);

结果如下:

30000

A君啥玩意儿,怎么30000会变成了一个田字?

B君如果30000是一个田字,为啥打印i时不会变成田呢?

小兔子乖乖:不急不急,我们一起来抽丝剥茧。先来看这两个打印的不同。

打印i最终调用的方法是:

// 例子1-调用1public static String toString(int i) {    if (i == Integer.MIN_VALUE)        return "-2147483648";    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);    char[] buf = new char[size];    getChars(i, size, buf);    return new String(buf, true);}

左右滑动查看完整代码

代码中把i拆成一个一个字符,共5个字符,组成字符数组char[],形成一个新的字符串输出,当然了,每个字符都小于10。

打印c最终调用的方法是:

// 例子1-调用2public static String valueOf(char c) {    char data[] = {c};    return new String(data, true);}

左右滑动查看完整代码

代码直接把该字符变成字符数组,然后形成一个新的字符串输出。

所以i和c输出的根本区别在于,i输出的字符数组中每一个字符都小于10,而c输出的字符数组,就只有一个字符,它的值是30000。

A君:那是不是大于10就会出问题呢?

B君:不对吧,我试了下面的例子,并不是A君你说的那样。

// 例子2int j = 9;int k = 57;System.out.println("字符打印:" + (char)j + "!");System.out.println("字符打印:" + (char)k + "!");

左右滑动查看完整代码

结果如下:

字符打印:

字符打印:9!

这非常奇怪哟,我用整型9来转换,变成一个不知名的空格出来,然后使用整型57反而转成整型9打印呢?

小兔子乖乖:这是因为char是采用unicode编码存储的,整形转成字符时,整形的值为字符的unicode编码值,而打印的值,是unicode编码对应的表示值,例如整型57的16进制为0039,对应数字9,整型9的16进制仍为9,对应表示值HT,是一种特殊字符,部分关系截图如下:

java 输出的中文是乱码 java输出的汉字变成问号_System

A君哈哈,那我知道了,第一个例子中的i为30000,16进制为7530,在unicode编码的映射关系中,表示为“田”。

小兔子乖乖Bingo。那好了,那啥叫编码呢?

B君:这个我知道,编码是计算机编程中很重要的一部分,计算机认识的只有0和1,也就是二进制值。一个byte为8位,可以表示256种状态,可看作256种信息,可世界的信息量太大了,需要使用各种组合和结构来表示信息,像常见的ASCII、Unicode、gbk及UTF-8等。

例如ASCII 码一共规定了128个字符的编码,比如空格SPACE是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的一位统一规定为0。而128个字符能够表示的内容太少了,需要增加位数表示更多的字符,就会出现两个字节、三个字节等等表示,例如UTF-8、UTF-16等。而unicode是国际通用的编码及表示的对应表,很多国家的编码都支持unicode,例如utf-8就是实现了unicode。

A君:说那么一大堆,头疼,能不能说的简单点呀?

小兔子乖乖:可以,就是说各种编码格式实际是各种编制手段,世界那么大,语言那么多,无可厚非啦。

B君:我再解说下,就例如加密手段,加密后就是一堆看不懂的符号,利用同一种加密手段进行解密,就可以获取正确的信息。只不过这个加密变成了编码,力求能编制出更多的字符信息。

A君:哦,那我大概明白了,我翻阅了下资料,在Java当中,char是采用了UTF-16实现。而且强转时特别留意赋值关系,是unicode值,而非表现值。

那有些系统不支持utf-8、utf-16,只支持gbk/gb2312怎么办呢?

小兔子乖乖:那得理清一个道理,那就是编码,就是采取一定的编制形式,使之计算器能够识别并运行,而编制之后,别的编码套路可解不了,强制去解就会产生通常所说的乱码,所以转换需要同等条件下才能做到。

Utf-8编码读取转换成java内置编码(java能够识别处理的语言,可以认为就是中文),再转换成gbk,即中文转变成gbk。

例如,需要实现GBK编码转成UTF8怎么做呢:

FileUtils.writeLines(new File(utf8FilePath), "UTF-8", FileUtils.readLines(gbkFile, "GBK")); 

即从文件中采取正确的GBK编码读取,然后采取UTF-8写入另一个文件中。

一般,某种编码格式转成字节数组,都是需要按特定的编码进行编制,形成合符要求的二进制数组,否则就会形成乱码或者按系统编码去编制。

好了,我们再进行下一个环节,先举一个例子:

A(UTF-8)数据传给B(ISO-8859-1),再传给C(UTF-8),能够拿到正确的数据吗?

// 例子3String testA = "华软";System.out.println("原A字符串:" + testA);byte[] arr = testA.getBytes("UTF-8");String testB = new String(arr, "ISO-8859-1");System.out.println("B直接转成ISO-8859-1:" + testB);arr = testB.getBytes("ISO-8859-1");String testC = new String(arr, "UTF-8");System.out.println("B继续编码后传递给C按UTF-8解析:" + testC);

左右滑动查看完整代码

结果如下:

A字符串:华软

B直接转成ISO-8859-1:åŽè½¯

B继续编码后传递给C按UTF-8解析:华软

B君:看了一遍代码和结果,我都猜出来啦,虽然B不知道A是UTF-8,但是我就当做正常信息去解析,然后再次编码,并没有破坏字节数组的结构,导致C能够正常解析出A的内容,

小兔子乖乖:可喜可贺,你的解释是正确的,但全部都是如此吗?

B君:你这么问,肯定不是啦,有例子讲讲吗?

小兔子乖乖:我们再看一个例子。

// 例子4String testA = "华软";System.out.println("原A字符串:" + testA);byte[] arr = testA.getBytes("UTF-8");String testB = new String(arr, "GBK");System.out.println("B直接转成GBK:" + testB);arr = testB.getBytes("GBK");String testC = new String(arr, "UTF-8");System.out.println("B继续编码后传递给C按UTF-8解析:" + testC);

左右滑动查看完整代码

结果如下:

A字符串:华软

B直接转成GBK:鍗庤蒋

B继续编码后传递给C按UTF-8解析:华软

A君:咦,这不还是一样吗?耍我和B君呢?

小兔子乖乖:No,不急不急,我们再来。

// 例子5String testA = "华软君";System.out.println("原A字符串:" + testA);byte[] arr = testA.getBytes("UTF-8");System.out.println("原字节数组:");printHex(arr);String testB = new String(arr, "GBK");System.out.println("B直接转成GBK:" + testB);arr = testB.getBytes("GBK");System.out.println("转成GBK后数组:");printHex(arr);String testC = new String(arr, "UTF-8");System.out.println("B继续编码后传递给C按UTF-8解析:" + testC);

左右滑动查看完整代码

结果如下:

A字符串:华软君

原字节数组:

e5 8d 8e e8 bd af e5 90 9b

B直接转成GBK:鍗庤蒋鍚

转成GBK后数组:

e5 8d 8e e8 bd af e5 90 3f

B继续编码后传递给C按UTF-8解析:华软?

B君:咦,还真出现问题了,难道和奇偶有关系?

小兔子乖乖:Bingo,是的。按答案的字节数组,GBK是两个字节组成一个字符的,两两匹配,而原字节UTF-8的汉字是三个字节组成一个字符的,“华软君”有9个字节,而GBK最终解析是前8个字节匹配了,然后第9个字节无法识别,采用3f的16进制编码来代替未知?,即破坏了原字节数组的结构组成。

A君:那问题又来了,为什么ISO-8859-1没问题呢。

B君:啊,我想起来了,因为ISO-8859-1是单字节组成的,当然没问题啦,怎么解析都可以。所以中间商想打印字符串又不知道编码格式的,最好采用ISO-8859-1做转换,避免破坏字节数组的结构。

小兔子乖乖:说的太好了,我们再来看GBK和UTF-8反过来的例子。

// 例子6String testA = "华软君";System.out.println("原A字符串:" + testA);byte[] arr = testA.getBytes("GBK");System.out.println("原字节数组:");printHex(arr);printBytes(arr);String testB = new String(arr, "UTF-8");System.out.println("B直接转成UTF-8:" + testB);arr = testB.getBytes("UTF-8");System.out.println("转成UTF-8后数组:");printHex(arr);printBytes(arr);String testC = new String(arr, "GBK");System.out.println("B继续编码后传递给C按GBK解析:" + testC);

左右滑动查看完整代码

结果如下:

A字符串:华软君

原字节数组:

bb aa c8 ed be fd

 10111011 10101010 11001000 11101101 10111110 11111101

B直接转成UTF-8:�����

转成UTF-8后数组:

ef bf bd ef bf bd ef bf bd ef bf bd ef bf bd

 11101111 10111111 10111101 11101111 10111111 10111101 11101111 10111111 10111101 11101111 10111111 10111101 11101111 10111111 10111101

B继续编码后传递给C按GBK解析:锟斤拷锟斤拷锟

A君:哇,好离谱,感觉全线崩溃,怎么会造成这个样子?

B君:是呀是呀,好奇怪。

小兔子乖乖:莫慌,我们一步一步分析,UTF-8是变成字节的编制结构,组成部分可以是1-6字节,而汉字基本上是3字节组成而已,而且UTF-8有特殊的组成规则,如果是一个字节,则最高位为0,如果是两个字节的,首字节最高位为11,其余字节最高位为10,如果是三个字节的,首字节最高位为111,其余字节最高位为10,以此类推。

例如读取第一个字节10111011,非规范字节,会自动转变成占位符ef bf bd,由此引起一系列的转换错误,答案中的“锟斤拷”等等大家熟悉了吗?似乎是大家的老朋友了,可是这位老朋友可不理解你,导致你日思夜想排查问题,所以为了不再见这位老朋友,还是改掉这种做法吧。

当然了,也并非只有上述错误,还有其他各种各样的错误,但万变不离其宗,只要摸透里面的小九九,就可以顺利的把这个BUG虫子抓进监狱啦。而在实际编程中,前后端编码接收转换,文件编码格式,数据库编码格式等等,无处不在,只要理清编码中的原理,任何问题都会迎难而解。

A君:哦,原来如此,难怪。

B君:嗯,得赶紧看看我写的代码有没虫子,赶紧去抓抓。

小兔子乖乖:好了,饿了,吃饭去,走起吧。



java 输出的中文是乱码 java输出的汉字变成问号_System_02

java 输出的中文是乱码 java输出的汉字变成问号_java 输出的中文是乱码_03