博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
文本在内存中的编码(1)——乱码探源(4)
阅读量:5887 次
发布时间:2019-06-19

本文共 2636 字,大约阅读时间需要 8 分钟。

hot3.png

让我们从一个故事开始说起。话说北大是很有哲学传统的,当你准备踏进北大校门时,连门卫都会连问你三个终极哲学问题:

你是谁?你从哪里来?你要到哪里去?

那么这与我们的问题又有何关系呢?我觉得理解内存中的编码的关键在于理解String类型,因此我们也来探讨一下String的前世今生:String是谁(什么)?String从哪里来?String到哪里去?

当我们能够清晰地回答这三个终极问题时,对文本在内存中的编码也算理解得差不多了。

注:文中将用Java平台为例来探讨这些问题。

String是什么?

要回答这个问题,源码当然是最好的参考。

字符序列(CharSequence)

如果看String类型的声明:

public final class String    implements java.io.Serializable, Comparable
, CharSequence {        private final char value[]; // ...}

可以看到它实现了所谓的CharSequence接口,所以它是一个char序列,内部实质是一个char数组。

也即上述代码中的”char value[]“,(也许你觉得”char[] value“的写法更习惯一些,两者是等价的)

如果再看String的length方法,事实就更清楚了,实际上取的是char数组的长度:

public final class String    implements java.io.Serializable, Comparable
, CharSequence {        private final char value[]; // ... public int length() {        return value.length;    }}

到现在为止,可以这样看String:

char

现在新的问题是:什么是char呢?Java中的char是一种基础的数据类型,用于表示字符,长度为16位。

可以把char看作是无符号的16位短整型。

一个byte是8位,那么一个char就相当于两个byte了,所以也可以把String视作为byte数组。(这是毫无疑问的,事实上整个内存就是一个大大的byte数组)

那么一个String在内存中总是占据偶数个字节,具体地说是占用length()×2个字节。

当然,单独地拿出String中的一个byte出来是没有意义的,你总是需要两个两个一起地操作它们,但这并不妨碍我们把它看成byte数组,它可以说是有点特殊的byte数组。

容量问题

显然,由于char是16位固定长度,它的容量总是有限的,上限是216=65536,能表示0-65535(0x0000-0xFFFF)。

即便满打满算它也只能表示6万多个不同字符而已,另一方面,Unicode规划的字符空间高达100万以上,最新版本已经定义的字符也超过了10万。

规划的码点范围具体为U+0000-U+10FFFF。char能表示前面的U+0000-U+FFFF,对于U+10000-U+10FFFF则无能为力。

所以一个显然的事实就是单个的char无法表示所有的字符。比如下面的这个音乐符:

它的十进制的码点为119070,已经远超过65535,肯定不能放到char里,实验也可证实这一点:

萝卜太多,坑位不够,怎么办呢?

解决方案

一种方式自然是对char进行扩展,比如弄成32位的,不过这样会造成很大的内存浪费。

另一种方式就是对后面的那些字符使用两个char来表示,也即是所谓的代理对方式,需要注意的是不能跟单个char表示的字符冲突。

Java采用的就是这样方式,其后果是使得char无法与”抽象的字符“这一概念划上等号,一一对应的关系被打破了。

我们具体来看下是怎么做的。

首先char有256×256=65536个空间:

常用的字符都可以在这个空间内表示,包括绝大多数的汉字。

比如“a”分配到的编码是“0061”,而“你”分配到的是“4F60”。

那么一个字符串,比如“a你”就有两个char,内存中占4个字节:

然后对于那个音乐符而言,它的码点为U+1D11E,有5位,当然不能简单直接地分成0001和D11E两部分。

这样会与U+0001和U+D11E冲突。

所以首先要保留一些char,它们单个而言不代表任何抽象的字符,具体地说保留了D800-DFFF共2048个位置:

然后横竖弄成一张表,能够形成100多万种组合(1K=1024):

在这种表示方式下,U+1D11E对应的是D834和DD1E两个char:

具体的转换方式可见:

我们就用这两个char一起来表示这个字符。这个字符无法放到单个的char中,但它可以放到String中,因为String是char数组。

综述

以上其实就是UTF-16的编码方式。你经常能听到这样的说法,比如:Java平台在内存中使用Unicode编码。这其实说得很笼统,让我们把它说得更具体一些:

Java中的String类型在内存中使用UTF-16编码。

String以char作为它的构成单元,这样一个16位的char也称为UTF-16编码的一个代码单元(code unit)

通常,一个char对应一个抽象的字符,但也可能需要两个char构成一个所谓的代理对才能表示一个抽象的字符。

所以这也导致了一些尴尬的情况,对于一些抽象字符它的长度是2.

这与我们的直觉不符,又如下面的情况:

两个抽象字符,内部为3个char,所以长度是3,在内存中则占据了6个字节。你可能不是很喜欢这样的String类型,但事实就是这样。

另可见

其它选择

自然,你有很多的选择。如果你自己去实现一个语言平台,你当然也可以选择一个其它的编码,比如UTF-8,甚至是UTF-32作为String的内部编码。

考虑到UTF-32用四字节表示一个字符,通常一个int类型也是4字节,那么这种方式几乎可以认为是用一个int数组来保存字符。

明白了String是什么之后,在下一篇再继续探讨。我们将深入探讨String的构造,字节流和字符流以及编码间的转换等问题。

转载于:https://my.oschina.net/goldenshaw/blog/470946

你可能感兴趣的文章
C# System.Data.OracleClient requires Oracle client software version 8.1.7 or greater
查看>>
X-UA-Compatible兼容模式
查看>>
设计模式之观察者模式
查看>>
lucene-JE中文分词
查看>>
AWK手册
查看>>
项目简介
查看>>
驼峰风格字符串转换为下滑线风格字符串
查看>>
ios图片倒影效果的实现
查看>>
SPSS Modeler K-Means聚类结果评价
查看>>
easyui left-tabs 左侧的tabs
查看>>
图论中DFS与BFS的区别、用法、详解?
查看>>
Color.js增强你对颜色的控制
查看>>
x == (x = y) 不等于 (x = y) == x ?
查看>>
制作一个 JavaScript 小游戏
查看>>
GEC要上线了!
查看>>
HTML+CSS实现div的高度自适应填满剩余空间的7种方法
查看>>
关于svn遇到的问题
查看>>
Running CoreOS on Vagrant
查看>>
程序员的工作方法--接到一份工作后应该怎么做。
查看>>
安全的结束一个线程
查看>>