来源:http://dwz.win/jqd

依照Java的文档, Java中的字符内部是以UTF-16编码方式表示的,最小值是 \u0000 (0),最大值是\uffff(65535), 也就是一个字符以2个字节来表示,难道Java最多只能表示 65535 个字符?

char: The char data type is a single 16-bit Unicode character. It has a minimum value of '\u0000' (or 0) and a maximum value of '\uffff' (or 65,535 inclusive).

from The Java™ Tutorials

首先,让我们先看个例子:

public class Main {
public static void main(String[] args) {
// 中文常见字
String s = "你好";
System.out.println("1. string length =" + s.length());
System.out.println("1. string bytes length =" + s.getBytes().length);
System.out.println("1. string char length =" + s.toCharArray().length);
System.out.println();
// emojis
s = "??";
System.out.println("2. string length =" + s.length());
System.out.println("2. string bytes length =" + s.getBytes().length);
System.out.println("2. string char length =" + s.toCharArray().length);
System.out.println();
// 中文生僻字
s = "?妹";
System.out.println("3. string length =" + s.length());
System.out.println("3. string bytes length =" + s.getBytes().length);
System.out.println("3. string char length =" + s.toCharArray().length);
System.out.println();
}
}

运行这个程序,你觉得输出结果是什么?

输出结果:

1. string length =2
1. string bytes length =6
1. string char length =2
2. string length =4
2. string bytes length =8
2. string char length =4
3. string length =3
3. string bytes length =7
3. string char length =3
我们知道,String.getBytes()如果不指定编码格式,Java会使用操作系统的编码格式得到字节数组,在我的MacOS中,默认使用UTF-8作为字符编码(locale命令可以查看操作系统的编码),所以在我的机器运行,String.getBytes()会返回UTF-8编码的字节数组。
String.length返回Unicode code units的长度。
String.toCharArray返回字符数组。
我们设置的字符串都是两个unicode字符,输出结果:
  • 普通的中文字:字符串的长度是2,每个中文字按UTF-8编码是三个字节,字符数组的长度看起来也没问题
  • emojis字符:我们设置了两个emojis字符,男女头像。结果字符串的长度是4, UTF-8编码8个字节,字符数组的长度是4
  • 生僻的中文字:我们设置了两个中文字,其中一个是生僻的中文字。结果字符串的长度是3, UTF-8编码7个字节,字符数组的长度是3
看起来字符串的字符数和我们预期的有点不一样,我们的字符串只有两个unicode字符, 可是输出结果有时候是2,有时候是3, 有时候是4,为什么呢?
这还得从Java的历史说起。
Java最初设计的Charactor用两个字节来表示unicode字符,这没有问题, 因为最初unicode中的字符还比较少, Java 1.1之前采用Unicode version 1.1.5, JDK 1.1中支持Unicode 2.0, JDK 1.1.7支持Unicode 2.1, Java SE 1.4 支持 Unicode 3.0, Java SE 5.0开始支持Unicode 4.0。
直到Unicode 3.0, Java用两个字节来表示unicode字符还没有问题,因为Unicode 3.0最多 49,259 个字符, 两个字节可以表示 65,535 个字符,还足够容的下所有的uicode3.0字符。
但是Unicode 4.0(事实上自Unicode 3.1), 字符集进行很大的扩充,已经达到了96,447 个字符,Unicode 11.0已经包含 137,374 个字符。
在Unicode中,为每一个字符对应一个编码点(一个整数),用 U+紧跟着十六进制数表示。所有字符按照使用上的频繁度划分为 17 个平面(编号为 0-16),即基本的多语言平面和增补平面。基本的多语言平面(英文为 Basic Multilingual Plane,简称 BMP)又称平面 0,收集了使用最广泛的字符。
这样一来,Java的Charactor的两个字节的设计,已经不足以容纳所有的Unicode 4的字符, 所以可能需要4个字节才能表示扩展字符,所以现在的Charactor代表的已经不再是一个字符 (代码点 code point), 而是一个代码单元(code unit)。
  • Code Point: 代码点,一个字符的数字表示。一个字符集一般可以用一张或多张由多个行和多个列所构成的二维表来表示。二维表中行与列交叉的点称之为代码点,每个码点分配一个唯一的编号数字,称之为码点值或码点编号,除开某些特殊区域(比如代理区、专用区)的非字符代码点和保留代码点,每个代码点唯一对应于一个字符。从U+0000 到 U+10FFFF。
  • Code Unit:代码单元,是指一个已编码的文本中具有最短的比特组合的单元。对于 UTF-8 来说,代码单元是 8 比特长;对于 UTF-16 来说,代码单元是 16 比特长。换一种说法就是 UTF-8 的是以一个字节为最小单位的,UTF-16 是以两个字节为最小单位的。
Java的字符在内部以UTF-16编码方式来表示,String.length返回的是Code Unit的长度,而不再是Unicode中字符的长度。对于传统的BMP平面的代码点,String.length和我们传统理解的字符的数量是一致的,对于扩展的字符,String.length可能是我们理解的字符长度的两倍。
有可能你会问, 对于一个UTF-16编码的扩展字符,它以4个字节来表示,那么前两个字节会不会和BMP平面冲突,导致程序不知道它是扩展字符还是BMP平面的字符?
其实是不会的, 幸运的是, 在BMP平面中, U+D800到U+DFFF之间的码位是永久保留不映射到Unicode字符,UTF-16就利用保留下来的0xD800-0xDFFF区块的码位来对辅助平面的字符的码位进行编码。
UTF-16编码中,辅助平面中的码位从U+10000到U+10FFFF,共计FFFFF个,需要20位来表示。第一个整数(两个字节,称为前导代理)要容纳上述20位的前10位,第二个整数(称为后尾代理)容纳上述20位的后10位。

前导代理的值的范围是0xD800到0xDBFF,后尾代理的0xDC00~0xDFFF。可以看到前导代理和后尾代理的范围都落在了BMP平面中不用来映射的码位,所以不会产生冲突,而且前导代理和后尾代理也没有重合。

这样我们得到两个字节的,就可以直接判断它是否是BMP平面的字符,还是扩展字符中的前导代理还是后尾代码。
国外的有些用户用emojis字符做自己的昵称,导致有些系统不能正确的显示出来,这是因为这些系统粗暴的使用Charactor来表示,在显示的时候截断的时候有时候可能不是在正确的代码点上进行截断。
我们在进行字符串截取的时候,比如String.substring有可能会踩到一些坑,尤其经常使用的emojis字符。
自 Java 1.5 java.lang.String就提供了Code Point方法, 用来获取完整的Unicode字符和Unicode字符数量:
  • public int codePointAt(int index)
  • public int codePointBefore(int index)
  • public int codePointCount(int beginIndex, int endIndex)
注意这些方法中的index使用的是code unit值。

参考文档

https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Character.html
http://www.oracle.com/us/technologies/java/supplementary-142654.html
https://stackoverflow.com/questions/2533097/java-unicode-encoding
https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html
https://zh.wikipedia.org/wiki/Unicode
https://codeahoy.com/2016/05/08/the-char-type-in-java-is-broken/
https://zh.wikipedia.org/wiki/UTF-16
https://wiki.sei.cmu.edu/confluence/display/java/STR50-J.+Use+the+appropriate+method+for+counting+characters+in+a+string
http://stn.audible.com/abcs-of-unicode/#common-unicode-mistakes-in-java-apps

- END -

关注Java技术栈微信公众号,在后台回复关键字:Java,可以获取一份栈长整理的 Java 最新技术干货。

最近干货分享

公司不用 Spring Boot,果断离职了!

Java 12 骚操作, switch居然还能这样玩!

Java 12 骚操作, String居然还能这样玩

Spring Boot 1.x 正式退役,2.x大步向前

分享一份 2019 最新 Java 架构师学习资料

点击「阅读原文」加入栈长的战队~

一个 Java 字符串到底有多少个字符?的更多相关文章

  1. Java字符串中有多少个字符多少个char、字节

    Java 中Char是两个字节,Char在Java中也被称为代码单元(Code Unit) . Java中的字符与代码点(Code Unit)一 一对应,而可能对应一个或者两个 代码单元 字符串的le ...

  2. 一个 Java 对象到底有多大?

    阅读本文大概需要 2.8 分钟. 出处:http://u6.gg/swLPg 编写 Java 代码的时候,大多数情况下,我们很少关注一个 Java 对象究竟有多大(占据多少内存),更多的是关注业务与逻 ...

  3. 一个Java对象到底占用多大内存?

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

  4. 一个Java对象到底占用多大内存

    在网上搜到了一篇博客讲的非常好,里面提供的这个类也非常实用: import java.lang.instrument.Instrumentation; import java.lang.reflect ...

  5. 一个Java对象到底占多大内存

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

  6. 一个Java对象到底占多大内存?(转)

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

  7. 【转】一个Java对象到底占多大内存?

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

  8. 一个Java字符串中到底有多少个字符?

    依照Java的文档, Java中的字符内部是以UTF-16编码方式表示的,最小值是 \u0000 (0),最大值是\uffff(65535), 也就是一个字符以2个字节来表示,难道Java最多只能表示 ...

  9. 提供一个Java字符串转整型数组的方法

    package edu.yuliang.Data_Structure_Basics; import java.util.Scanner; public class new_string { publi ...

随机推荐

  1. osi7层模型及线程和进程

    端口的作用: 在同一台电脑上,为了让不同 的程序分离开来! http:网站默认端口是80 https:网站默认端口是443 osi七层模型: 1.应用层:软件 2.表示层:接收数据 3.会话:保持登录 ...

  2. sqlserver数据库脱机和分离的区别

    脱机和分离的区别: 分离和脱机都可以使数据库不能再被使用,但是分离后需要附加才能使用,而脱机后只需联机就可以用了. 附加数据库报错: 无法打开物理文件 XXX.mdf".操作系统错误 5:& ...

  3. postman-鉴权

    概念 Cookie和鉴权的区别,cookie一般指缓存在本地的数据:鉴权一般指验证用户是否拥有访问系统的权利 鉴权分类 Basic auth:基础鉴权,数据没有加密可明文显示,一般在测试环境使用,不在 ...

  4. Java框架之MybatisSQL注入漏洞

    一.SQL注入漏洞基本原理 在常见的web漏洞中,SQL注入漏洞较为常见,危害也较大.攻击者一旦利用系统中存在的SQL注入漏洞来发起攻击,在条件允许的情况下,不仅可以获取整站数据,还可通过进一步的渗透 ...

  5. new Date(str)返回 Invalid date问题

    var date=new Date($("input[name='mettingTime']").val().replace(/-/g, "/")); var ...

  6. size_t是什么?

    在32位编译器下size_t可看做unsigned int: 在64位编译器下size_t可看做unsigned long long: sizeof返回的数据类型就为size_t. C++之size_ ...

  7. 手把手教你搞定个推iOS推送SDK集成

    以下是一位开发者在集成个推iOS推送SDK过程中的真实经历. 作者:Ezreallp 一次偶然的机会,公司的项目要用到推送,我自己本来就很懒,不愿意去弄整套APNS的流程,刚好之前跟朋友聊起过他们的产 ...

  8. NSDate 那点事

    转载自:http://my.oschina.net/yongbin45/blog/150114 NSDate对象用来表示一个具体的时间点. NSDate是一个类簇,我们所使用的NSDate对象,都是N ...

  9. 在 mac 系统上安装 python 的 MySQLdb 模块

    在 mac 系统上安装 python 的 MySQLdb 模块 特别说明:本文主要参考了Mac系统怎么安装MySQLdb(MySQL-Python) 第 1 步:下载 MySQL-python-1.2 ...

  10. Microsoft Visual Studio 2013 Language Pack

    Microsoft Visual Studio 2013 Language Pack Microsoft Visual Studio 2013 各版本语言包下载地址: https://my.visua ...