不建议在 Java 程序中使用 char 数据类型

简介: 有了Unicode 字符集后,我们要考虑的就是以什么样的方式对这些字符进行传输和存储,这就是 Unicode 编码的实现方式,我们称为 Unicode 转换格式(Unicode Transformation Format,简称 UTF)。我们熟悉的 UTF-8、 UTF-16 等就是不同的 Unicode编码实现方式。

好了,看了标题我知道你有疑问,这里我得承认算并半个标题党吧。


事情是这样的:


这里有段程序,你跑一下,结果可能跟你想的不一样


 public static void main(String[] args) {
    String str = "䕫";
    System.out.println(str.length());
  }


你可能认为字符串长度应该是1吧,为什么会是2呢?这里其实就是所谓的『坑』,说到这个坑,话就有些长了,我们先看一些关于字符的概念。


以下的基础知识我相信大多数开发的同学都知道,如果你明白直接跳过就好。



Unicode 字符集的出现就是为了统一编码所谓字符集就是一个由众多不同的字符组成的集合。


Unicode 字符集对每一个字符都分配了一个唯一的 代码点(code point) 用来标识字符本身。


所谓代码点就是一个添加了 U+ 前缀的十六进制整数,如字母 A 的代码点就是 U+0041。


有了Unicode 字符集后,我们要考虑的就是以什么样的方式对这些字符进行传输和存储,这就是 Unicode 编码的实现方式,我们称为 Unicode 转换格式(Unicode Transformation Format,简称 UTF)。我们熟悉的 UTF-8、 UTF-16 等就是不同的 Unicode编码实现方式


码点如何转换成UTF的几种形式呢?


32.jpg


如上图所示

  • UTF-32采用的定长四字节则是32位
  • UTF-8是变长的编码方案,可以有1,2,3,4四种字节组合
  • UTF-16是一种变长的2或4字节编码模式

在 Unicode 字符集诞生之初,采用 UCS-2(2-byte Universal Character Set) 这种定长的编码方式对 Unicode 字符集进行编码,这种方式采用 16 bit 的长度来进行字符编码,所以最多可以对 2^16 = 65536 个字符进行编码(编码范围从 U+0000 ~ U+FFFF)。在当时的情况下,设计者们用了不到一半的数量就对所有字符进行了编码,并且认为剩余的空间足够用于未来新增字符的编码。


不幸的是,随着中文、日文、韩文等表意文字不断的加入,Unicode 字符集中的字符数量很快超过了 16 位所能编码的最大字符数量,于是设计者们对 Unicode 字符集进行了新的设计。


新的设计将字符集中的所有字符分为 17 个 代码平面(code plane)。其中 U+0000 ~ U+FFFF 这个代码点范围被划定为 基本多语言平面(Basic MultilingualPlane,简记为 BMP,如下图第一个花花绿绿的那个),其余的字符分别划入 16 个 辅助平面(Supplementary Plane),代码点范围为 U+10000 ~ U+10FFFF,这些处于辅助平面的字符我们称作 增补字符(supplementary characters)。


33.jpg


在 Unicode 字符集中的字符被重新划分到不同平面后,需要注意:


BMP 范围内的字符和 UCS-2 下的字符编码基本保持一致,但是 BMP 中的 U+D800 ~ U+DFFF 部分被留空,不分配给任何字符,作用是用于给辅助平面内的字符进行编码。

不是每个平面内的每个位置都被分配给了指定的字符,原因是:特殊用途,如 BMP 中的 U+D800 ~ U+DFFF 部分;


  • 作为保留空间
  • 没有足够的字符


回答程序输出长度为2而不是1的问题


我们使用的字符其实不是普通字符,而是增补字符,我们知道 Java 中 char 的长度永远是 16 位,如果我们在字符串中使用了增补字符,那就意味着需要 2 个 char 类型的长度才能存储,对于 String 底层存储字符的数组 value 来说,就需要 2 个数组元素的位置。我们再看一下String 类length方法的源码:


/**
     * Returns the length of this string.
     * The length is equal to the number of <a href="Character.html#unicode">Unicode
     * code units</a> in the string.
     *
     * @return  the length of the sequence of characters represented by this
     *          object.
     */
public int length() {
return value.length;
    }


一切就明白了。java 的 String 内部用的 UTF-16 编码,String.length() 直接返回 code unit 的个数,也就是 Java 的 2 字节 char 的个数。


当然这里不是说绝对不要用char,只是坑多(上面只是其中一个,JDK9还有别的 ),建议少用而已。

相关文章
|
1月前
|
Java 流计算
利用java8 的 CompletableFuture 优化 Flink 程序
本文探讨了Flink使用avatorscript脚本语言时遇到的性能瓶颈,并通过CompletableFuture优化代码,显著提升了Flink的QPS。文中详细介绍了avatorscript的使用方法,包括自定义函数、从Map中取值、使用Java工具类及AviatorScript函数等,帮助读者更好地理解和应用avatorscript。
利用java8 的 CompletableFuture 优化 Flink 程序
|
11天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
35 4
|
9天前
|
存储 消息中间件 NoSQL
使用Java操作Redis数据类型的详解指南
通过使用Jedis库,可以在Java中方便地操作Redis的各种数据类型。本文详细介绍了字符串、哈希、列表、集合和有序集合的基本操作及其对应的Java实现。这些示例展示了如何使用Java与Redis进行交互,为开发高效的Redis客户端应用程序提供了基础。希望本文的指南能帮助您更好地理解和使用Redis,提升应用程序的性能和可靠性。
23 1
|
24天前
|
存储 Java 关系型数据库
[Java]“不同族”基本数据类型间只能“强转”吗?
本文探讨了不同位二进制表示范围的计算方法,重点分析了Java中int和char类型之间的转换规则,以及float与int类型之间的转换特性。通过具体示例说明了显式和隐式转换的条件和限制。
33 0
[Java]“不同族”基本数据类型间只能“强转”吗?
|
29天前
|
Java Maven 数据安全/隐私保护
如何实现Java打包程序的加密代码混淆,避免被反编译?
【10月更文挑战第15天】如何实现Java打包程序的加密代码混淆,避免被反编译?
43 2
|
1月前
|
安全 Java Linux
java程序设置开机自启
java程序设置开机自启
|
1月前
|
运维 Java Linux
【运维基础知识】Linux服务器下手写启停Java程序脚本start.sh stop.sh及详细说明
### 启动Java程序脚本 `start.sh` 此脚本用于启动一个Java程序,设置JVM字符集为GBK,最大堆内存为3000M,并将程序的日志输出到`output.log`文件中,同时在后台运行。 ### 停止Java程序脚本 `stop.sh` 此脚本用于停止指定名称的服务(如`QuoteServer`),通过查找并终止该服务的Java进程,输出操作结果以确认是否成功。
36 1
|
23天前
|
存储 Java 编译器
[Java]基本数据类型与引用类型赋值的底层分析
本文详细分析了Java中不同类型引用的存储方式,包括int、Integer、int[]、Integer[]等,并探讨了byte与其他类型间的转换及String的相关特性。文章通过多个示例解释了引用和对象的存储位置,以及字符串常量池的使用。此外,还对比了String和StringBuilder的性能差异,帮助读者深入理解Java内存管理机制。
18 0
|
1月前
|
Java 编译器 C++
【编程基础知识】Java基本数据类型
Java的基本类型包括五类九种,涵盖了整型、浮点型、字符型、布尔型和void。成员变量即使未初始化也有默认值,但局部变量必须显式初始化,否则编译会报错。示例代码展示了各种基本类型的默认值和极限值。
34 0
|
1月前
|
Java Python
如何通过Java程序调用python脚本
如何通过Java程序调用python脚本
27 0