Java中的字符编码

字符与编码

人类使用的是字符,计算机存储的是比特,要想在计算机中存储字符就必须进行编码。从字符到比特序列的过程为编码,比特序列到字符的过程为解码。

字符集与编码格式

字符集是字符的集合。可以认为是一个数字到字符的映射表,比如1对应‘大’,2对应‘家’,3对应‘好’,那么“大家好”编码后就为1 2 3,1 2 3解码后就是“大家好”,只要根据这张表就能完成编解码。
编码格式是字符集的具体实现,如上面的字符集,1、2、3具体怎么存储在计算机中呢,用一个字节吗?如果此字符集有5000个字符,怎么存储呢?按照简单可以直接每个字符表示为两个字节,还可以利用压缩的原理,将使用频率最高的128个字符编号为0-127,然后存储在一个字节中,其他的用2个字节,这样可以提高存储效率。这两种具体实现就是同一种字符集的两种编码格式。
字符集与编码格式是一对多关系,可以认为是抽象和实现的关系。

常见编码格式

ASCII

美国信息交换标准代码,用于显示现代英语。也是大家最熟悉的编码格式,编码范围0x00-0x7F,每个字符存储1个字节。


ascii表.png

ISO-8859-1

它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入96个字母及符号,以适应西欧国家的使用,在西欧非常流行。

GBK

中文常用编码格式,在ascii码的基础上加入2w多个汉字,每个汉字占两个字节。

上述这些即是字符集也是编码格式,Unicode字符集拥有多种编码格式。

Unicode

每种语言都有自己的字符集,这让互联网上的交流充满了乱码,为了使世界上大部分字符统一字符集,于是Unicode就诞生了。Unicode包含了10w多个字符,达到了字符集统一的目的。Unicode拥有多种编码格式。

UTF-8

Unicode的一种编码格式,主要针对存储、传输设计,每个字符用1-4个字节表示,节省存储空间,广泛应用与网络通信。

UTF-16

Unicode的另一种编码格式,每个字符用两个字节表示,后来加入了一些新字符后有一些字符需要用多个字符表示。

Unicode的常用编码格式的比较
unicode不同编码格式.jpg

编码的兼容性与不可逆性

字符在编码和解码时必须使用相同的编码格式才能还原,否则可能出现乱码。编码后的是字节流,要想解码后得到原字符,则必须保证解码格式按此字节流能恢复成原字符,也就是编码格式要能兼容编码格式。
如果编码后的字节流在解码的编码格式中此字节流有效,就会出现乱码但可逆,无效且更改了字节流则会出现乱码并且不可逆。
上面的常见编码格式中,除了UTF-16不兼容ASCII,其他的都兼容。汉字编码格式。

Java中的编码

java中各阶段编码格式要求:


java编码概括.jpg

可以看出Java源文件不限编码格式,Class字节码文件采用UTF-8编码格式,在虚拟机内存中采用UTF-16编码,输出的时候按照需要获得各种编码格式。Class文件采用UTF-8编码是因为UTF-8存储效率更高,适合存储与传输,运行时采用UTF-16是因为以前UTF-16还是定长编码,读取效率更高,现在这个优势已经消失了。

JVM外乱码

这类乱码出现在jvm运行时之前,具体来说是编译阶段中的输入阶段。
从Java源码文件到Java Class文件,中间会经过Java源码编译器(例如javac或ECJ)的编译。
也就是说,是Java源码编译器负责将Java源码文件的编码转换为最终的UTF-8。
导致乱码的不是Java源码编译器的“编码”(写出UTF-8)的过程,而是“解码”(读入Java源码内容)的过程。
这类乱码只需要将源码格式统一为UTF-8一般就能避免。

JVM运行时乱码

在java程序运行时对字符的编码解码所用的编码格式不兼容所导致的乱码现象,这也是主要的乱码原因。

Java 中String的编码转换

首先需要说明的是,Java中的String都是UTF-16编码的。
看String的两个方法:

public byte[] getBytes()
Encodes this String into a sequence of bytes using the platform's default charset, storing the result into a new byte array.
 
new String(byte[], charsetName)
Constructs a new String by decoding the specified array of bytes using the specified charset. The length of the new String is a function of the charset, and hence may not be equal to the length of the byte array.

getBytes()就是编码过程,将字符按照编码格式转换成字节流,String(byte[], charsetName)就是将byte字节流按照charsetName指定的编码格式转换成字符。注意charsetName是指byte[]字节流的编码格式,而不是把byte[]转换成charsetName编码

默认编码格式

getBytes()不传字符集参数时是按操作系统的默认编码格式获取字节流。
默认编码格式:

System.out.println(Charset.defaultCharset());

可能是UTF-8、GBK等,取决于操作系统,并且是jvm已启动就确定的,不可更改。

编码转换

字符串编码转换函数:

/**
    * 将一个字符串由一种编码格式转换到另一种编码格式
    * @param oldStr 待转换的字符串
    * @param oldCharset oldStr对应的编码格式,null表示用默认编码格式
    * @param newCharset 新的编码格式,null表示用默认编码格式
    * @return 新的字符串
    * @throws UnsupportedEncodingException
    */
String changeCharset(String oldStr,String oldCharset,String newCharset) throws UnsupportedEncodingException {
    if(oldCharset==null)
        oldCharset=Charset.defaultCharset().name();
    if(newCharset==null)
        newCharset=Charset.defaultCharset().name();
    return new String(oldStr.getBytes(oldCharset), newCharset);
}

乱码示例:

    @Test
    public void defaultChartTest() throws UnsupportedEncodingException {
        System.out.println("默认字符集:"+Charset.defaultCharset().name());
        String s="你好,大家好";
        String sIso=changeCharset(s,null,"ISO-8859-1");
        System.out.println(sIso);

        String sDef=changeCharset(sIso,"ISO-8859-1",null);
        System.out.println(sDef);
    }

输出:

默认字符集:UTF-8
ä½ å¥½,大家好
你好,大家好

本人机器的默认字符集是UTF-8,首先取得字符串的UTF-8编码的字节流,然后按照ISO-8859-1进行解码,两者不兼容所以得到的是乱码,要想将此乱码恢复,必须按照ISO-8859-1进行解码还原字节流(可能还原不成功,就是出现信息丢失,不可逆),因为此字节流的有意义的编码是UTF-8,所以按照UTF-8解码就能得到原字符串。
总的来说,注意编码格式和解码格式保持一致,就能避免乱码问题,重点在字符集、编码格式和乱码产生的原因。

参考资料

1.java编译器编码和JVM编码问题
2.深入分析 Java 中的中文编码问题
3.Java编码浅析(注意区分三个概念)
4.Java字符编码根本原理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容