前言
上次对计算机中的“字符集”和“编码”分别进行了总结,并指出二者之间的区别,不要搞混了,不清楚的再回到上一章看一下。今天再总结下java中是如何使用字符集(主要是Unicode字符集,其他常用字符集都只有一种编码规则),以及是如何使用utf-8、utf-16、utf-32对Unicode字符集进行编码的。
java中的char类型
java中的char类型占用两个字节、用于定义字符,这些字符只覆盖了Unicode字符集中的第0个平面中定义的符号(该平面中定义的符号 都是地球人最常用的65536个),也就是说其他16个平面中的符号是没办法有java的char类型表示的。
char c0 = 'A'; char c1='天'; char c2='星'; char c3 = 'XXX';//编译错误
提示:这个字iteye无法识别,导致文章提交被截断,文中所有’XXX’都是表示这个字。
前三个char类型赋值没有问题,第四个赋值直接编译错误。’XXX’这个字的Unicode码值是:10 10001000 10111011,可以看出使用两个字节是放不下的。如果使用utf-16进行编码,编码后的二进制值如下(需要4个字节,编码格式可以参考上一篇文章):
1101100 00110001 01101110 010111011
这也就是为什么不能把'XXX'赋值给java char类型的原因。
Java中字符串转字节
Java中的字符串(String)在网络传输或者存储硬盘的真实内容是 通过字符集编码转换后的二进制数字。在String类中定义了几个getBytes重载方法,来获取字符串对应的字节数组,并且默认使用的是Unicode字符集的utf-8编码:测试代码如下:
byte[] bytes = "XXX".getBytes(); System.out.println("默认编码:"+new BigInteger(1,bytes).toString(2)); byte[] bytes3 = "XXX".getBytes("utf-8"); System.out.println("使用utf-8编码:"+new BigInteger(1,bytes3).toString(2));
打印内容为:
默认编码:11110000101010001010001010111011 使用utf-8编码:11110000101010001010001010111011
可以发现使用不带参数和带"utf-8"参数的结果是完全一致的。即可说明:getBytes不带参数的默认方法使用的是Unicode字符集的utf-8编码规则进行编码的。"XXX"不是常用的汉字,这里使用了4字节,常用汉字使用utf-8编码一般三个字节。
在上一章中讲到过utf-16是使用2或者4字节进行存储,我们来看下在java语言中的表现:
byte[] bytes1 = "XXX".getBytes("Unicode"); System.out.println("使用Unicde字符集的默认编码:"+new BigInteger(1,bytes1).toString(2)); byte[] bytes2 = "XXX".getBytes("utf-16"); System.out.println("使用utf-16编码:"+new BigInteger(1,bytes2).toString(2)); byte[] bytes4 = "XXX".getBytes("ASCII"); System.out.println("使用ASCII字符集:"+new BigInteger(1,bytes4).toString(2));
打印结果为:
使用Unicde字符集的默认编码:111111101111111111011000011000101101110010111011 使用utf-16编码:111111101111111111011000011000101101110010111011 使用ASCII字符集:111111
注意这里区别:
调用getBytes("Unicode")方法 表示使用Unicode字符集的默认编码方式进行编码。这个默认编码方式一般由操作系统指定,我的是win7,显示跟utf-8编码相同;
调用getBytes("utf-16")方法 表示使用默认Unicode字符集的utf-16编码方式进行编码。
另外我们上一章说过,utf-16使用2或者4个字节存储,"XXX"不在第0平面,应该占用4个字节。但实际打印出来的是11111110 11111111 11011000 01100010 11011100 10111011,一共6个字节,怎么多了两个字节呢?我们还可以发现无论是什么字符串,通过调用getBytes("utf-16")方法,打印出来的前面都会固定多两个字节:11111110 11111111,换算成16进制就是0x FEFF。这里有一个大端序和小端序的概念。
大端序和小端序
前一篇文章也提到过utf-8、utf-16、utf-32有一个区别就是一次最少读入的字节数,utf-8是一次最少读入一个字节(8个bit),utf-16是2两字节,utf-32是4个字节。其中utf-16和utf-32一次需要读入多个字节,根据读取顺序的不同,分为大端序和小端序。
大端序:简单的理解就是从左往右依次读入两个(utf-16)或者4个(utf-32)字节
小端序:简单的理解就是从右往做反向依次读入两个(utf-16)或者4个(utf-32)字节
大端序编码:UTF-16Be、UTF-32Be;小端序:UTF-16Le、UTF-32Le;另外在java中还可以使使用UnicodeBig、UnicodeLittle,表示使用默认的UTF-16大、小端序编码,与UTF-16Le、UTF-32Le基本等效,区别就是UnicodeBig、UnicodeLittle的前面会自动加上两字节,用于表示大小端序。
UnicodeBig自动在编码后的最前面加上:1111111011111111 换成16进制为 FEFF;
UnicodeLittle自动在编码后的最前面加上:1111111111111110 换成16进制为FFFE。
测试代码如下:
byte[] bytes2 = "XXX".getBytes("utf-16"); System.out.println("使用utf-16编码:"+new BigInteger(1,bytes2).toString(2)); byte[] bytes6 = "XXX".getBytes("utf-16le"); System.out.println("使用utf-16小端序编码:"+new BigInteger(1,bytes6).toString(2)); byte[] bytes7 = "XXX".getBytes("utf-16be"); System.out.println("使用utf-16大端序编码:"+new BigInteger(1,bytes7).toString(2)); byte[] bytesx = "XXX".getBytes("utf-32be"); System.out.println("使用utf-32大端序编码:"+new BigInteger(1,bytesx).toString(2)); byte[] bytesy = "XXX".getBytes("utf-32le"); System.out.println("使用utf-32小端序编码:"+new BigInteger(1,bytesy).toString(2)); byte[] bytesz = "XXX".getBytes("utf-32"); System.out.println("使用utf-32编码:"+new BigInteger(1,bytesz).toString(2)); byte[] bytes8 = "XXX".getBytes("UnicodeBig"); System.out.println("使用Unicode大端序编码:"+new BigInteger(1,bytes8).toString(2)); byte[] bytes9 = "XXX".getBytes("UnicodeLittle"); System.out.println("使用Unicode小端序编码:"+new BigInteger(1,bytes9).toString(2));
运行结果为:
使用utf-16编码:111111101111111111011000011000101101110010111011 使用utf-16小端序编码:1100010110110001011101111011100 使用utf-16大端序编码:11011000011000101101110010111011 使用utf-32大端序编码:101000100010111011 使用utf-32小端序编码:10111011100010000000001000000000 使用utf-32编码:101000100010111011 使用Unicode大端序编码:111111101111111111011000011000101101110010111011 使用Unicode小端序编码:111111111111111001100010110110001011101111011100
用BigInteger没有打印出完整的二进制,它把前面是0的全部去掉了。另外我们可以发现在在不指定大小端序的情况下默认是使用的大端序,在上一节中直接使用getBytes("utf-16")或者getBytes("Unicode")时就可以看出,前面多的两个字节刚好就是用来表示默认是“大端序”FEFF。
为什么要有大小端序呢,原因很简单:不同的操作系统的不同实现罢了,有些操作系统默认是大端序(比如我的win7下),有些默认又是小端序。所以最好指定清楚,否则容易引起乱码。
另外在window下utf-8还有带与不带BOM的区别,也就是在字符串的最前面会多出FEFF,在其他系统上显示就会有乱码,在linux操作系统下很少见到使用,这里就不深入了。
Java解码--字节数组转字符串
Java中解码,直接调用String的带字节数组的构造方法解码,生成人类能识别的符号。如果人类不能识别就是我们所谓的乱码。同时该构造方法还有另外一个参数,表示使用的字符集编码,看一个例子:
byte[] b16 = "天星".getBytes("utf-16"); String n8=new String(b16,"utf-8"); System.out.println("使用utf-8解码 utf-16编码的字符串:"+n8); byte[] b8 = n8.getBytes("utf-8"); String n16 = new String(b8,"utf-16"); System.out.println("还原:"+n16);
这个例子首先使用utf-16编码对字符串进行编码;
然后使用了utf-8编码进行解码,此时由于编解码不一致,发现打印出来有乱码;
接着试图把这个乱码还原回去,再使用utf-16进行解码。
也许你期望的应该是没有乱码,可以还原,但真实的打印结果如下:
使用utf-8解码 utf-16编码的字符串:��Y)f 还原:뷯뾽天星
发现还是有乱码,没有真正还原。这是为什么呢?这其实就是默认大端序会加两个字节引起的的问题,我们把程序改下,指定在16进制时使用大端序:
byte[] b16 = "天星".getBytes("utf-16Be"); String n8=new String(b16,"utf-8"); System.out.println("使用utf-8解码 utf-16编码的字符串:"+n8); byte[] b8 = n8.getBytes("utf-8"); String n16 = new String(b8,"utf-16Be"); System.out.println("还原:"+n16);
运行结果:
使用utf-8解码 utf-16编码的字符串:Y)f 还原:天星
发现已经神奇的还原了。
导致乱码的根本原因是编码和解码使用了不同的字符集,或者相同的字符集下使用了不同的编码规则进行编解码。但反过来确不成立,也就是说在有些情况下即便是使用了不同的字符集或编码规则进行编解码,也不会出现乱码,比如我上一个例子。这就要求我们对字符集的编码规则要相当熟悉,并能灵活运用。
理论上在编码时只要可以保证数据不丢失,都可以先还原回去,再使用正确的字符集编码进行解码,可以得到正常的结果。比如下面的例子:
byte[] b16 = "天星".getBytes("ASCII"); String n8=new String(b16,"utf-8"); System.out.println("使用utf-8解码 utf-16编码的字符串:"+n8); byte[] b8 = n8.getBytes("utf-8"); String n16 = new String(b8,"ASCII"); System.out.println("还原:"+n16);
运行结果是乱码,主要原因就是在"天星".getBytes("ASCII")这一步产生了数据丢失,后面再牛逼的人也无力回天了。但如果把“天星”改成“abc”是可以还原的,因为这些字符本身就是ASCII中定义的字符。这也算是英文的优势吧。
总结
关于java中的字符集和编码就总结到这里,这次总结的起因是要分析 恶意用户的恶意请求参数。他使用了utf-16进行编码,而我们日志服务器默认是使用utf-8进行解码,从而导致乱码。但如果熟悉了我上面讲解的内容,相信你也可以把真实的内容还原回来。当然中间还使用另外一些加密手段,这里就不再一一详解了。
提示:这个字iteye无法识别,导致文章提交被截断,文中所有’XXX’都是表示这个字。
相关推荐
在探讨Java字符集和编码之前,我们先了解一下为什么在Java编程中需要关注字符集和编码。Java作为一种广泛应用的编程语言,其内部采用的是Unicode编码,这使得Java能够很好地支持全球化的应用开发。然而,在实际的...
在JAVA开发中,正确处理字符集编码至关重要,以避免乱码和数据不一致的问题。 一、ISO8859-1与ASCII ISO8859-1是一种单字节编码标准,通常用于西欧语言,其编码范围为0-255。在ISO8859-1中,ASCII字符集是其子集,...
本文将围绕“Java字符集编码简记”这一主题,深入探讨相关知识点,并结合标签“源码”和“工具”,探讨在实际开发中如何运用和处理字符编码问题。 首先,我们需要理解字符集的概念。字符集是一系列符号的集合,例如...
Java中的字符编码转换是编程实践中一个至关重要的概念,尤其是在处理多语言环境和跨平台交互时。Java通过统一采用UTF-16编码格式在JVM内部处理字符,简化了字符操作的复杂性。UTF-16是一种变长的Unicode编码,它可以...
### Java字符集编码问题详解 #### 一、引言 在Java编程中,字符集编码问题是一个常见且重要的议题。由于不同的系统、平台以及网络环境中可能存在多种字符编码格式,这导致了在处理文本数据时可能会遇到编码不一致...
在Java编程语言中,理解和掌握字符集编码是至关重要的,特别是在处理各种文本数据时。本文主要探讨了编码字符集和字符集编码的区别,这对于软件开发人员来说是基础且必要的知识。 首先,我们要区分两个概念:编码...
在Java编程中,正确地处理文件的字符集编码至关重要,特别是在读取或写入含有非ASCII字符(如中文、日文、韩文等)的文件时。`cpdetector`是Java中一个常用的库,用于自动检测文件的字符集编码。这个库能够帮助...
Java中的字符集是一个重要的概念,尤其对于处理多语言文本或者跨平台的数据交换至关重要。Java语言内部使用Unicode编码,具体来说是UTF-16格式,这意味着每个`char`类型变量能够表示一个Unicode字符,通常占据两个...
### Java字符串的编码转换 在Java中,处理不同字符集之间的字符串转换是一项常见任务。尤其是在处理国际化应用时,理解并掌握各种字符编码格式变得尤为重要。下面将介绍几种常见的字符编码格式以及如何在Java中实现...
通用的文件字符编码集判断需要借助第三方包cpdetector.jar 使用Cpdetector jar包检测文件编码需要依赖antlr-2.7.7.jar、chardet-1.0.jar、jargs-1.0.jar三个jar包 本下载资源一站式全包含,并附带亲测有效的片段...
字符编码检测和转换 附件中:FileEncodeDetector.java 此文件可以检测指定文件的编码格式 public static String getFileEncode(File file) {...} 附件中:FileCharsetConverter.java 此文件可以实现两个编码的相互...
需要注意的是,`URLEncoder`默认使用ISO-8859-1编码,但大多数现代Web应用都使用UTF-8编码,因此在编码和解码时要确保字符集的一致性,否则可能会导致解码错误。 总结一下,`URLDecoder`和`URLEncoder`的主要用途是...
### Java中的字符集编码入门(五):Java代码中的字符编码转换Part1 #### 核心知识点概述: 本文档深入探讨了Java编程语言中字符集编码的基础知识,并着重讲解了字符编码转换的基本原理及其在Java代码中的应用。...
这些类支持多种字符集编码,如UTF-8、UTF-16、ISO-8859-1等,这使得Java程序能够轻松地在不同字符集之间进行转换,从而支持全球化的应用开发。 #### 七、结论 理解Unicode和字符集对于现代软件开发来说是至关重要...
在Java开发中,连接Oracle数据库是一项常见的任务,尤其是在处理特定字符集如American ASCII7时,开发者需要对字符编码有深入的理解。Oracle数据库支持多种字符集,包括ASCII,它是最基础的7位字符集,包含32个控制...
Mysql数据库中的字符集编码问题是许多开发者经常遇到的一个问题,特别是在JAVA项目中。解决这个问题需要从多方面入手,包括服务器、数据库、数据表和连接等四个层次。这篇文章将详细介绍如何解决Mysql数据库乱码问题...
在Java这样的编程语言中,处理字符集和编码是非常重要的。Java内部使用Unicode编码,这意味着在内存中,所有的字符串都是以Unicode编码的形式存在。当读取或写入文件,或与其他系统进行通信时,Java需要进行编码转换...
基本字符集主要包含在Java运行时库`rt.jar`中,涵盖了常见的国际标准和一些特定编码方式。具体包括以下几种: - **ASCII**:即美国信息交换标准代码(American Standard Code for Information Interchange),这是...
Java默认使用Unicode编码,这是目前最广泛接受的字符集,包含几乎世界上所有语言的字符。Unicode有多种不同的编码形式,如UTF-8、UTF-16等。UTF-8是最常用的变体,它使用1至4个字节来表示一个字符,根据字符的不同...