从String源码看Java中的编码

简介: 从String源码的一个构造方法说起public String(int[] codePoints, int offset, int count) {}what?codePoints是什么鬼?为了看懂这个源码,有必要了解一个这个codePoints(代码点)的相关知识,其实整个String源码都会不少的涉及的java编码的相关知识,比如indexOf(int ch, int fromIndex)。

从String源码的一个构造方法说起

public String(int[] codePoints, int offset, int count) {}

what?codePoints是什么鬼?为了看懂这个源码,有必要了解一个这个codePoints(代码点)的相关知识,其实整个String源码都会不少的涉及的java编码的相关知识,比如indexOf(int ch, int fromIndex)

img_b72bf222324c35fef5c1ea1e2c387845.png
一脸懵逼

为什么会有Unicode

​ 学C/C++的时候我们知道了ASCII码,但这个能表示的字符有限,后来又出现了一些乱七八糟的编码表,Unicode就是企图统一一下编码而产生的。

Unicode的介绍

​ Unicode的第一个版本是用2个字节来编码所有的字符的,因为编码者们认为2^16=65536能容纳世界上所有语言,后来他们发现他们错了,哈哈哈,第二个版本就用4个字节来编码所有字符,这个后面说。

img_bea3dbc3aaed270db06e274576c2d1cb.jpe

先以第一个版本来说,用2个字节来编码所有字符(这个要很清晰,不然会有点懵),正好UTF-16也是这么弄的,大家误解就以为Unicode就是UTF-16。这里会涉及两个步骤,一个步骤是字符与编码一一对应的问题,如a对97,b对98如此;另一个是如何将编码的二进制这些01串保存起来的问题,这就实现了UTF(unicode transformation format),有UTF-8,UTF-16……

UTF-8与UTF-16

​ UTF16很好理解,在第一个版本的Unicode中,就是2个字节保存一个字符。UTF8就不同,它可能用1个/2个/3个来表示。那我怎么知道它用来多少个自己来表示呢?这就需要一个规定:

  1. 0开头的,就表示1个字节表示一个字符,即0xxx xxxx,如0101 0011
  2. 110x xxxx 10xx xxxx这种表示把2个字节当成一个单元,表示一个字符。
  3. 1110 xxxx 10xx xxxx 10xx xxxx这种表示3个字节当成一个单元,表示一个字符。

由上面我们可以看出UTF-8需要判断每个字节中的开头标志信息,所以如果一当某个字节在传送过程中出错了,就会导致后面的字节也会解析出错.而UTF-16不会判断开头标志,即使错也只会错一个字符,所以容错能力强.

​ 从上面可以看出,当1个字节表示一个字符时,能表示2^7=128个字符,2个字节表示时能表示2048个字符,3个单元表示时能表示65536个字符。由于"汉"的编码27721大于2048了所有两个字节还不够,只能用三个字节来表示。

接着看看第二个版本

​ 相关规定:

  • Unicode 统一编码 0x000000-0x10ffff,其中0x0000-0xffff为基本多语言平面字符

  • bmp 基本多语言平面字符,对应Unicode中0x0000-0xffff

  • Unicode码空间为U+0000到U+10FFFF,一共有17个平面,每个平面可容下65536个code point。也就是17 * 65536=1,114,112。但是其中的U+D800-U+DFFF作为UTF-16编码代理区保留,也就是它们不会作为code point分配给字符,保留数目是8 * 256=2048。

    看到这里,我们可以知道:

  • 如果在BMP级别中,那么16bits(一个代码单元)就足够表示出字符的Unicode值。

  • 那不在的呢,就属于增补字符了。那就是需要4个字节来表示,那4个字节为啥不是能表示0x00000000-0xffffffff个字符呢,只能表示到0x10ffff呢,这就有点像UTF-8的意思了,占用几个位置来表示我需要用4个字节来显示这个字符,那么需要占用多少位呢?因为只需要表示0x10 ffff - 0x10000 = 0xf ffff即可,就是20位,也就是说,虽然你给我分配4个字节32位这么多,但我只需要20位就足够表示0x10000~0x10FFFF,那剩下12位怎么办呢,这样吧,每2个字节用6位作为代理区,剩下10位用作编码,这就是U+D800-U+DFFF作为UTF-16编码代理区保留的规定了。

  • 那为什么是0xD800~0xDFFF呢,我们来看看,假设我们是编码者,我们现在要留一段来做代理区,假设我做代理区的起点选了0xD800吧,对应的是1100 1000 0000 0000,那我把剩下10位编码上去,就能表示:1101 1000 0000 0000~1101 1011 1111 1111即0xD800~ 0xDBFF,这个就算高位代理区了,然后同理,0xDC00~ 0xDFFF就算地位代理区了。

  • 好了,BMP区域有2048个编码被用作代理区了,所以它们是不能用来表示任何字符的。

Last but not least

简要的讨论了一下以String构造方法引出的一点问题,其实java的编码还是蛮复杂的,一口气说太多太复杂,估计大脑cpu也不够用了。

目录
相关文章
|
8天前
|
存储 数据可视化 Java
【Java】Java swing 民宿管理系统 GUI(源码+可视化界面)【独一无二】
【Java】Java swing 民宿管理系统 GUI(源码+可视化界面)【独一无二】
|
4天前
|
前端开发 Java
成功解决:java.lang.String cannot be cast to java.lang.Integer
这篇文章记录了作者在使用Axios二次封装时遇到的一个Java类型转换问题,即前端传递的字符串参数不能直接转换为Integer类型,文章提供了正确的转换方法来解决这个问题。
成功解决:java.lang.String cannot be cast to java.lang.Integer
|
4天前
|
存储 安全 Java
"Java编码魔法:揭秘图片与文件的Base64神秘转换术,让数据在指尖跳跃!"
【8月更文挑战第16天】Base64编码在Java开发中常用于将二进制数据如图片转换为ASCII字符串以便传输。编码使用64个字符及等号填充,每3字节数据编码为4个字符。Java利用`java.util.Base64`类实现此功能:读取图片或文件为字节数组后进行编码。解码时将Base64字符串还原为字节数组并写入文件。需注意编码效率降低、不提供安全性及特殊字符兼容性等问题。掌握这些技巧有助于解决Web开发中的数据传输需求。
17 4
|
11天前
|
前端开发 Java 测试技术
综合案例【商品管理系统-Java基础版】(附完整源码)
综合案例【商品管理系统-Java基础版】(附完整源码)
47 9
|
7天前
|
Java Android开发
解决Android编译报错:Unable to make field private final java.lang.String java.io.File.path accessible
解决Android编译报错:Unable to make field private final java.lang.String java.io.File.path accessible
25 1
|
7天前
|
Java
Java——编码GBK的不可映射字符
Java——编码GBK的不可映射字符
16 1
|
11天前
|
XML Java 数据库连接
Mybatis java.lang.NumberFormatException: For input string: "1,2" 问题处理
【8月更文挑战第9天】Mybatis java.lang.NumberFormatException: For input string: "1,2" 问题处理
|
3天前
|
算法 安全 Java
深入解析Java多线程:源码级别的分析与实践
深入解析Java多线程:源码级别的分析与实践
|
8天前
|
存储 Java
【Java】Java学生成绩管理系统(源码+论文)【独一无二】
【Java】Java学生成绩管理系统(源码+论文)【独一无二】
|
8天前
|
SQL Java 数据库连接
【Java】Java Swing 图书管借阅管理系统(源码+论文)【独一无二】
【Java】Java Swing 图书管借阅管理系统(源码+论文)【独一无二】