Java在编译到执行过程的编码问题
一、两个字符编码的参数
javac和java是JDK自带的工具,其中javac是编译工具,java工具启动Java虚拟机并执行java程序。这两个工具都带有设置字符编码的选项。本文讨论字符编码选项的使用场景,和出现乱码的原因。
javac 的字符编码选项
javac -encoding CharSet XXXX.java //CharSet为XXXX.java文件的字符编码。
javac编译器根据-encoding后跟随的字符编码,解析.java文件。-encoding不设置的时候,使用系统默认字符集解析.java文件。Windows的默认字符集是GBK。
无论之前的.java文件采用什么编码,编译后的.class文件都使用UTF-8编码。
如果-encoding指定的字符编码与.java文件的字符编码不一致,不一定编译失败,会给后续乱码埋下隐患。
JVM的字符编码选项
java -Dfile.encoding= CharSet XXXX //XXXX为class文件,CharSet是本地设备支持的字符集。
-Dfile.encoding并不是设置JVM虚拟机内存字符编码的。JVM虚拟机内存的字符编码是无法设置的,都是UTF-16,这个参数设置的是JVM输入流、输出流默认使用的编码/解码方式。
- 当我们不手动设置JVM参数-Dfile.encoding时,系统默认字符集则取决于语言环境和底层操作系统(Windows的CMD下是GBK,Linux下则跟设置的语言环境有关)
- 当我们手动设置JVM参数-Dfile.encoding=xxx,如果xxx是不支持的字符集,则默认使用’UTF-8’编码。
也可以通过Charset.defaultCharset()返回此 JVM的charset。默认 charset 在虚拟机启动时决定,通常根据语言环境和底层操作系统的 charset 来确定。
Charset.defaultcharset()
指的是JVM输入流、输出流默认使用的编码/解码方式。
public static Charset defaultCharset() {if (defaultCharset == null) {synchronized (Charset.class) {String csn = AccessController.doPrivileged(new GetPropertyAction("file.encoding"));Charset cs = lookup(csn);if (cs != null)defaultCharset = cs;elsedefaultCharset = forName("UTF-8");}}return defaultCharset;
}
看到上面说可以通过设置系统属性file.encoding来设置默认字符集,那么有些朋友就想通过在运行时,通过System.setProperty("file.encoding", "GBK");来动态改变字符集,虽然可以通过System.setProperty("file.encoding","GBK")修改属性值,但仅仅是修改了file.encoding这个属性值,并不会影响Charset.defaultCharset()。因为JVM启动时就已经设置了Charset.defaultcharset()。
我们可以验证一下Windows环境下的默认字符集
import java.nio.charset.Charset;
public class CharsetTest {public static void main(String[] args) {System.out.println(Charset.defaultCharset());}
}
注意要使用命令行的方式编译运行
javac CharsetTest.java
java CharsetTest
为什么不在idea直接测试呢?
因为idea可以设置字符集,具体如图
你设置的字符集不同,结果自然不一样。
这里再说明一下 sun.jnu.encoding 和 file.encoding 的区别
sun.jnu.encoding 影响文件名的创建,而 file.encoding 则影响到文件内容。
二、Java在编译到执行过程涉及到的字符编码
这里其实并不是UTF-8,是一种modified UTF-8,这里就姑且认为是UTF-8。
上图是.java文件从编译到执行的几个过程,这几个过程涉及到字符编码,具体解释如下:
①:A.java就是一个文本文件(以某种编码格式来存储:UTF-8、GBK、ISO-8859-1等),java编译器要解析这个文本文件并编译生成.class文件。而要想解析它,就必须知道它的编码方式。如果指定的编码与文件的编码不一致,要么编译失败,要么导致class文件中的字符乱码。
②:以不同编码方式编码的A.java经过Java编译器编译生成了同一个相同的A.class,编码格式为UTF-8。
③:Java虚拟机以二进制字节流的形式加载A.class,A.class中的字符编码是UTF-8,加载到JVM虚拟机内存中后,字符编码是UTF-16。
④:输出结果,如代码中指定了字符集,则按照代码中指定的字符集输出到设备中。如果代码中未指定字符集,则按照JVM启动时 -Dfile.encoding指定的字符集,如果没有指定-Dfile.encoding为默认的字符集输出到设备中。
从上图可以理解不管采用那种格式的源文件,只要正确告诉编译器源文件的编码格式,编译器就会得到正确的结果。同时只要告诉JVM正确的输出流需要的编码格式,JVM就可以返回正确编码格式的输出流。
那么要想不产生乱码就需要注意如下两个环节:
- 告诉编译器你java源文件的编码格式。
- 告诉jvm你显示或者构造字符串输出流时的希望的编码。
JVM中运行时数据都是使用UTF-16进行编码的。为什么JVM使用的是UTF-16,而不适用兼容性更好的UTF-8呢?
这是因为历史原因导致JVM运行时的数据使用UTF-16的编码。在Unicode最初诞生的时候,由于当时只有一个16位长的基本多文种平面,也就是只有0~65535的空间,两个字节刚好够用。这也解释了为什么Java中char是两个字节。
但是到了2001年,中国人大举入侵ISO和Unicode委员会,用已经颁布的GB18030-2000为基础,在Unicode 3.1标准中一口气加入了42711个CJK扩展字符,整个Unicode字符集一下增大到94205个字符,2个字节放不下了,UTF-16原来是变长编码的事也被人想起来了(中国人偷笑,GB系列从第一天起就是变长编码)。从此UTF-16就变得很尴尬,它一来存储空间利用率不高,二来又是个变长编码无法直接访问其中的码元。但是完全放弃UTF-16成本太高,所以现在JVM的运行时数据依然是UTF-16编码的。
由于成本问题不能放弃UTF-16,但是UTF-8的兼容性和流行程度,又使得JVM必须做点什么来使得其内部数据不会被编码方式影响,于是就有了这个modified UTF-8。
modified UTF-8
在通常用法下,Java程序语言在通过InputStreamReader和OutputStreamWriter读取和写入串的时候支持标准UTF-8。在Java内部,以及Class文件里存储的字符串是以一种叫modified UTF-8的格式存储的。
modified UTF-8 大致和 UTF-8 编码相同,但是有以下三个不同点:
- 空字符(null character,U+0000)使用双字节的0xc0 0x80,而不是单字节的0x00。
- 仅使用1字节,2字节和3字节格式,而UTF-8支持更多的字节
- 基本多文种平面之外的补充字符以代理对(surrogate pairs)的形式表示
使用双字节空字符保证了在已编码字符串中没有嵌入空字节。因为C语言等语言程序中,单字节空字符是用来标志字符串结尾的。当已编码字符串放到这样的语言中处理,一个嵌入的空字符将把字符串一刀两断。
modified UTF-8在没有超过前三个字节表示的时候,和UTF-8编码方式一样,但是超过以后会以代理对(surrogate pairs)的形式表示。至于为什么要以代理对形式表示。这个是因为JVM的默认编码是UTF-16导致的。
一开始的Unicode只有一个可以用16位长完全表示的基本多文种平面,所以Java中的字符(char)为16位长,一个char可以存所有的字符。后来Unicode增加了很多辅助平面,两个字节存不下那些字符,但是为了向后兼容Java不可能更改它的基本语法实现,于是对于超过U+FFFF的字符 (从0x10000到0x10FFFF,称作扩展字符)就需要用两个16位长数据来表示。modified UTF-8由UTF-16格式的代理码元来代替原先的Unicode码元作为字符编码表的码元。
UTF-16格式的代理码元编码规则如下:
1)先将UTF-16补充码的数值减去0x10000;
2)将减掉之后的数值分为两个10比特的数值,假设高10位的值表示为Vh,低10位的值表示为Vl;
3)对于数值对中第一个16位的双字节来说,用0xD800加上高10位的值Vh;
4)对于数值对中第二个16位的双字节来说,用0xDC00加上低10位的值Vl。
数值对中的每一个16位的值,MUTF-8都会使用3个字节对其进行编码。由于每个UTF-16的补充字符都需要用两个16位的值对来表示,所以MUTF-8编码过后会使用6个字节。
这里可以计算一下,UTF-8要表示16位的数值需要三个字节,是这样表示的,1110xxxx 10xxxxxx 10xxxxxx,其中1和0是标识位,x代表具体的Unicode值.
JVM把UTF-16编码出来的16位长的数据(2字节,操作系统用8位长的数据,即1字节)作为最小单位进行信息交换。这样的话既不改变原来JVM中的编码规则,又减少了很多扩展字符从UTF-8转码到UTF-16时的运算量,是不是很刺激?正常从UTF-8转到UTF-16要先读出Unicode值,然后进行计算代理码元才能得到.
modified UTF-8保证了一个已编码字符串可以一次编为一个UTF-16码,而不是一次一个Unicode码位,使得所有的Unicode字符都能在Java上显示。
不过也不是没有缺点的,使用modified UTF-8进行解码解出来的是UTF-16编码编出来的数据,而UTF-16处理扩展字符需要两个16位长表示。也就是说,要用两个代理码元共同表示一个Unicode码位。原本使用UTF-8编码只需要最多4个字节就能存储一个Unicode码位,使用modified UTF-8编码后却需要6个字节来存储两个代码单元。
总结起来就是,modified UTF-8是对UTF-16的再编码,modified UTF-8和UTF-8是两种完全不同的编码。
所以JVM无需解码UTF-16的数据,modified UTF-8代理码元会处理这个映射关系
参考文章
java jvm 编码_Javac和JVM的字符编码问题_weixin_39950764的博客-CSDN博客
Java 编译、运行过程中的字符编码 - 简书 (jianshu.com)
字符、编码和Java中的编码 - 简书 (jianshu.com)
MUTF-8(Modified UTF-8) - 腾讯云开发者社区-腾讯云 (tencent.com)
Java在编译到执行过程的编码问题相关推荐
- 执行引擎的工作过程、Java代码编译和执行的过程、解释器、JIT编译器
执行引擎概述 执行引擎是Java虛拟机核心的组成部分之一. "虚拟机"是-一个相对于"物理机"的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接 ...
- JVM学习笔记(二)------Java代码编译和执行的整个过程
Java代码编译是由Java源码编译器来完成,流程图如下所示: Java字节码的执行是由JVM执行引擎来完成,流程图如下所示: Java代码编译和执行的整个过程包含了以下三个重要的机制: ● Java ...
- 60.Java 代码编译和执行的整个过程
60.Java 代码编译和执行的整个过程 60.Java 代码编译和执行的整个过程 Java 代码编译是由 Java 源码编译器来完成,流程图如下所示: Java字节码的执行是由 JVM 执行引擎来完 ...
- JVM原理(Java代码编译和执行的整个过程+JVM内存管理及垃圾回收机制)
转载注明出处: http://blog.csdn.net/cutesource/article/details/5904501 JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.e ...
- Java 代码编译和执行的整个过程
一.简述 Java代码编译和执行的整个过程包含了三个重要的机制: ①Java 源码编译机制: ②类加载机制: ③类执行机制 二.Java 源码编译机制 Java 代码编译是由 Javac 编译器来完成 ...
- 机器语言,汇编语言,高级语言,c,java语言编译到运行过程
机器语言: 计算机的硬件作为一种电路元件,它的输出和输入只能是有电或者没电,也就是所说的高电平和低电平,所以计算机传递的数据是由"0" 和"1"组成的二进 ...
- Java 版本、语言规范、API、JDK、IDE、Java 源程序编译、执行原理(跨平台性根本原因)、特殊字符用法、8 大数据类型小结
文章目录 前言 一.三大版本类型 二.Java 语言规范 三.应用程序接口(API) 四.Java 开发工具包(JDK) 五.集成开发环境(IDE) 六.Java 运行环境(JRE) 七.Java 源 ...
- javascript 编译与执行过程
Javascript预编译和执行过程 1. 在执行前会进行类似"预编译"的操作:首先会创建一个当前执行环境下的活动对象,并将那些用 var申明的变量设置为活动对象的属性,但是此时这 ...
- 初探 Go 的编译命令执行过程
引言 Go 语言这两年在语言排行榜上的上升势头非常猛,Go 语言虽然是静态编译型语言,但是它却拥有脚本化的语法,支持多种编程范式(函数式和面向对象).Go 语言最最吸引人的地方可能是其原生支持并发编程 ...
最新文章
- Centos 7 解压文件
- 计算机硬件技术 教案,教案07-计算机硬件技术基础.doc
- python3教程-Python3 教程
- 算法--360面试:使用递归实现:a0=1,a1=1;a2=a0+a1;a3=a1+a2...以此类推,求a30
- 基于geopandas的空间数据分析——空间计算篇(下)
- C++const与#define 相比,有何优点?
- 洛谷P2497:基站建设(splay、斜率优化)
- 计蒜客---函数规律
- eclipse图标含义
- 让li不显示超出内容,显示... (编程方法和CSS方法)
- 凸优化第七章统计估计 7.3 最优检测器设计及假性检验
- 博弈论 | 三姬分金与囚徒困境
- android微信摇一摇(抽奖)
- 信息学奥赛一本通 铲雪车
- android 电池检测软件,AccuBattery手机电池损耗检测软件
- 如果面试时直接怒怼面试官
- ESP8266连接阿里云(二)烧录MQTT固件
- swoole的初步学习
- 亚马逊如何创业?身为小白的我适合创业亚马逊吗
- python实现输入一个字符串,输出每个字符的ASCLL码形成的列表
热门文章
- Oracle “ORA-00942: 表或视图不存在 “的原因和解决方法
- 算法总结——大整数乘法
- Java到底好不好学
- 了解Panda3D引擎的配置变量
- C# WPF MVVM框架搭建
- openldap + samba为openldap添加smb属性----群晖synology
- implicit declaration of function 警告解决方法 (函数的隐式说明)
- ..\Watch\alarm.c(149): error: #268: declaration may not appear after executable statement in block
- 限制guest账号访问硬盘权限
- Java键盘输入一个int数组