引入:在学习javaWeb时,碰到需要将字节数组和String类型做相互转换的,如使用Base64编码时。那么,我们知道String其实提供了API:getBytes() 将字符串转换为字节数组,而通过构造器new String(byte[]) 又可以将字节数组重新转化为字符串,对吧?但我们经常需要跟客户端做交互,此时很容易在这两个转换之间发生乱码问题。所以今天,我们一起来解决这个问题吧!
首先,我们需要明白,要使 字节数组 -> 字符串,或 字符串 -> 字节数组,两个过程来去自如而不乱码,就要求这两个互逆过程所使用的字符集必须统一,才能保证互相转换时不出现乱码。
对于 String.getBytes(String charsetName):可以传入参数charsetName,即字符集的名称,那么就可以根据该字符集对该字符串进行转化成字节数组。
同理,new String(byte[] buffer, String charsetName):也可以传入参数charsetName设置字符集,那么JVM就会根据该字符集将该buffer字节数组转化成一个字符串后返回。
然而,实际上呢我们可以不传入字符集参数,那么在字符串和字节数组进行相互转换时,JVM会采用平台的默认字符集对两者进行转换。而这个默认的字符集到底为何,是我们今天要讨论的重点:
- 首先,我们先测试第一种情况,运行一个最简单的Java程序,不需要Tomcat服务器,只依赖JVM,代码如下:
public class StringTest {
@Test
public void test(){
//获取当前系统的默认字符集
System.out.println("当前系统的默认字符集:" + System.getProperty("file.encoding"));
//测试文本
String str = "我是测试文本";
//使用默认字符集,对字符串和字符数组进行相互转化
byte[] bytes = str.getBytes();
String result = new String(bytes);
System.out.println("转化后的结果为:" + result);
}
}
结果截图如下:
可以看到,首先,当前系统的默认字符集是UTF-8。我们知道中国的计算机的默认字符集一般都是GBK。那么为什么我这个是UTF-8呢?
其实呢,是因为我在IDEA中设置了当前运行的这个项目的默认编码方式,为UTF-8。所以我运行时获取到的系统默认字符集就是UTF-8。
现在我改了该项目的默认编码方式为GBK,再次运行查看结果。
运行结果如下所示:
很明显,此时的系统的默认字符集已然发生了改变!而且我们还发现,这时候转发出来的字符串,仍然正确!!!
所以,我们先给出第一个结论:系统的默认字符集,即为该项目的默认编码字符集。
接着,当我们试图改变代码,按默认的字符集将字符串转化为字节数组,然后指定“UTF-8”将字节数组转化为字符串,查看运行结果如下:
结果表明,UTF-8此时不奏效,那么是为什么呢?
我们将UTF-8改成GBK,再次查看结果:
它又能行了!说明此时,如果我们不指定编码方式,那么字符串和字节数组之间的相互转化,默认采用的字符集是GBK,和我们的系统的默认字符集(也就是该项目的默认字符集) 保持一致。
那么,会不会是巧合呢?
我们将系统的字符集设置为UTF-8,然后实验如下:
- 项目的字符集设置为UTF-8,用默认字符集将字符串转化为字节数组,仍用GBK字符集将字节数组转化为字符串,实验结果如下:
结果再一次乱码! - 再来一次实验,此时指定UTF-8字符集将字节数组转化为字符串,实验结果如下:
运行结果再一次正确了!
经过以上的四个实验,我们其实已经可以得出一个结论:
当我们在字符串和字节数组之间做转化时,若没有显式指定字符集,那么转化时会自动使用系统的默认字符集,而这个系统的默认字符集,其实就是这一整个项目的默认编码字符集,也就是System.getProperty(“file.encoding”) 的属性值。
这个结论呢,其实在JDK源码中也已经说明了:
/**
* Constructs a new {@code String} by decoding the specified array of bytes
* using the platform's default charset (翻译:使用平台的默认字符集解码指定的字节数组,构造一个新的{@code String}) .
* The length of the new {@code String} is a function of the
* charset, and hence may not be equal to the length of the
* byte array.
*
* <p> The behavior of this constructor when the given bytes are not valid
* in the default charset is unspecified. The {@link
* java.nio.charset.CharsetDecoder} class should be used when more control
* over the decoding process is required.
*
* @param bytes
* The bytes to be decoded into characters
*
* @since JDK1.1
*/
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
这就完了?NO NO NO ! 还有更有趣的东西在后面呢!
刚刚我们是做了第一种实验,测试了只依赖JVM进行运行的最基本的Java程序。
那么接下来,我们测试下在Tomcat中运行的Java程序的情况如何:
先上代码:
public class StringServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取当前系统的默认字符集
System.out.println("当前系统的默认字符集:" + System.getProperty("file.encoding"));
//测试文本
String str = "我是测试文本";
//使用默认字符集,对字符串和字符数组进行相互转化
byte[] bytes = str.getBytes();
String result = new String(bytes, "UTF-8");
System.out.println("转化后的结果为:" + result);
}
}
方法体中的代码都一样,唯一的区别是这是个Servlet程序,需要依赖Tomcat容器运行。
此时呢,整个项目的编码集,是UTF-8。
OK,那么接下来,我们运行,查看结果:
项目的字符集明明是UTF-8,可运行结果却告诉我们,系统的默认字符集是GBK!是不是开始疑惑了?别急,我们开头的时候讲过,国内的计算机的默认字符集,基本都是GBK,对吧?
我们可以打开谷歌浏览器的控制台,请求服务器时,查看请求头信息,如下:
可以看到,划线一行,告诉了服务器,当前的操作系统是“windows”,而我们国内的"windows"的默认字符集,就是GBK!
所以呢,这里我们得出另一个结论:当我们的Java程序是运行在Tomcat容器上时,我们的系统默认字符集就是GBK,无关整个项目的默认编码字符集。那么,此时的String和byte[]的相互转化,默认使用的是哪个呢?
我们将刚刚的代码,用默认字符集转化字符串成字节数组,然后指定“GBK”字符集,将字节数组再次转化为字符串,查看运行结果如下:
诶,又能行了!说明此时字符串和字节数组之间的相互转化,默认采用的字符集是GBK,也就是仍然和系统的默认字符集即 System.getProperty(“file.encoding”) 的属性值保持一致。
OK,那么所有的实验都演示完毕,收最后的结论:
无论是运行在Tomcat上的Java程序,还是只运行在JVM上的普通Java程序,他们在进行字符串和字节数组的相互转换过程中,若没有显式指定字符集,那么它们会采用系统的默认字符集。
其中,运行在Tomcat容器的Java程序使用的系统字符集是GBK;
而只运行在JVM上的普通Java程序使用的系统字符集呢,与该Java项目的整体字符集保持一致。
重中之重:但不管该Java程序是运行在Tomcat还是只运行在JVM,可以确定的一点就是,它使用的系统默认字符集,就是System.getProperty(“file.encoding”)的属性值!!!