[转帖]为什么 Java 内部使用 UTF-16 表示字符串?
https://www.jianshu.com/p/957b249a02d8
背景
许多年前 Unicode 的提出者天真地以为 16 位定长的字符可以容纳地球上所有仍具活力的文字,Java 设计者也深以为然。
参考 Unicode 设计,Java 设计者认为完全可以设计一个双字节数据类型来表达所有 Unicode 字符,于是便有了今天的原始数据类型 char。
但后来发现 65,536 个字符根本不足以表达所有文字,Java 5.0 版本既要支持 Unicode 4.0 同时要保证向后兼容性,不得不开始使用 UTF-16 作为内部编码方式,
UTF-16 编码
Unicode 基本多文种平面(BMP U+0000 to U+FFFF)涵盖了几乎所有现代语言,以及繁多的特殊符号,Java 允许使用单个 char 来表示 BMP 内的字符,此时的编码值等于 Unicode 代码点(code point),这是Java 最初的Unicode 实现,这种编码方式又称之为 UCS-2。
Enough talk, show me the code !
我们尝试打印位于 BMP 平面内的上箭头符号。
首先,查询得知上箭头符号对应的 code point 是 0x2191,直接赋值给 char然后打印:
char ch = 0x2191;
System.out.println(ch);
输出:
↑
那么,如何表示辅助多文种平面(SMP U+010000 to U+10FFFF)内的字符呢?
Unicode 从 BMP 平面保留两片连续区域用于表示 SMP 平面内的字符,即可以继续与 UCS-2 编码保持兼容,又能减少空间浪费,毕竟使用 SMP 的场合并不多。
这两片区域分别是 0xD800–0xDBFF (高代理区域)、0xDC00–0xDFFF (低代理区域),编码方式如下:
- 将代码点减去 0x10000,仅保留低 20 位;
- 将高 10 位加上 0xD800,得到高代理;
- 将低 10 位加上 0xDC00,得到低代理;
高代理和低代理共同组成一个代理串(Surrogate Pair)唯一地标识 Unicode SMP 平面上的任一代码点。
Enough talk, show me the code !
我们来试试打印 Emoji 笑脸
int lowBits = 0x1F600 - 0x10000;
// 由于char 的长度为 16 位,采用代理对方式表示(surrogate pair)必须使用两个 char,并使用 String 包装
char highSurrogate = (char) ((lowBits >> 10) + 0xD800);
char lowSurrogate = (char) ((lowBits & 0x3FF) + 0xDC00);
System.out.println(new String(new char[]{highSurrogate, lowSurrogate}));
输出:
Java Character 类提供很丰富的静态方法实现 Unicode 相关操作,如下所见:
// 将代理对转成对应 Unicode code point
Character.toCodePoint(char high, char low)
// 判断 code point 所需字符数
Character.charCount(int codePoint)
// 判断 code point 是否合法
// 判断是否为高位代理(High Surrogate)
Character.isHighSurrogate(char ch)
// 获取高位代理(High Surrogate)
Character.highSurrogate(char ch)
// 判断是否为低位代理(Low Surrogate)
Character.isLowSurrogate(char ch)
// 获取低位代理(Low Surrogate)
Character.lowSurrogate(char ch)
UTF-16 转换 UTF-8
Java String 类支持任意编码方式转换,其中就包括 UTF-8 编码:
String.getBytes("UTF-8")
但该方法缺点也很明显,无法重用已有的 buffer,有些场合下可能十分不便。下面是 Google 实现的UTF-8 编码方法,可以供大家参考:
public class GoogleUTF8 {
public static int encodeUtf8(CharSequence in, byte[] out, int offset, int length) {
int utf16Length = in.length();
int j = offset;
int i = 0;
int limit = offset + length;
// Designed to take advantage of
// https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
for (char c; i < utf16Length && i + j < limit && (c = in.charAt(i)) < 0x80; i++) {
out[j + i] = (byte) c;
}
if (i == utf16Length) {
return j + utf16Length;
}
j += i;
for (char c; i < utf16Length; i++) {
c = in.charAt(i);
if (c < 0x80 && j < limit) {
out[j++] = (byte) c;
} else if (c < 0x800 && j <= limit - 2) { // 11 bits, two UTF-8 bytes
out[j++] = (byte) ((0xF << 6) | (c >>> 6));
out[j++] = (byte) (0x80 | (0x3F & c));
} else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) && j <= limit - 3) {
// Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
out[j++] = (byte) ((0xF << 5) | (c >>> 12));
out[j++] = (byte) (0x80 | (0x3F & (c >>> 6)));
out[j++] = (byte) (0x80 | (0x3F & c));
} else if (j <= limit - 4) {
// Minimum code point represented by a surrogate pair is 0x10000, 17 bits,
// four UTF-8 bytes
final char low;
if (i + 1 == in.length()
|| !Character.isSurrogatePair(c, (low = in.charAt(++i)))) {
throw new UnpairedSurrogateException((i - 1), utf16Length);
}
int codePoint = Character.toCodePoint(c, low);
out[j++] = (byte) ((0xF << 4) | (codePoint >>> 18));
out[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 12)));
out[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 6)));
out[j++] = (byte) (0x80 | (0x3F & codePoint));
} else {
// If we are surrogates and we're not a surrogate pair, always throw an
// UnpairedSurrogateException instead of an ArrayOutOfBoundsException.
if ((Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE)
&& (i + 1 == in.length()
|| !Character.isSurrogatePair(c, in.charAt(i + 1)))) {
throw new UnpairedSurrogateException(i, utf16Length);
}
throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + j);
}
}
return j;
}
}
参考链接
- https://docs.oracle.com/javase/specs/jls/se6/html/lexical.html
- https://docs.oracle.com/javase/tutorial/i18n/text/unicode.html
- https://softwareengineering.stackexchange.com/questions/174947/why-does-java-use-utf-16-for-internal-string-representation
- https://www.oracle.com/technetwork/articles/javase/supplementary-142654.html
[转帖]为什么 Java 内部使用 UTF-16 表示字符串?的更多相关文章
- Java内部锁的可重用性(Reentrancy)
Java提供了强制原子性的内部锁机制:synchronized块.但是内部锁是可重入的,当线程试图获得它自己占有的锁时,请求会成功. 简单的说,就是在一个synchronized方法内部调用本类的其他 ...
- Java内部DNS查询实现和参数设置
一.Java内部DNS查询 Java使用域名查询时,用的自己内部的域名实现机制,最后都是交给InetAddress去做DNS解析. 源码分析参考:http://blog.arganzheng.me/p ...
- Java利用递归算法统计1-6的数组排列组合数
Java利用递归算法统计1-6的数组排列组合数 1.设计源码 /** * @Title:ArrayCombination.java * @Package:com.you.data * @Descrip ...
- c#:对两个字符串大小比较(不使用c#/java内部的比较函数),按升序排序
题目:首先需要实现一个函数:两个字符串大小比较(不得使用c#/java系统函数)的自定义函数:之后对一个字符串数据进行按升序排序(在排序过程中使用字符串大小比较时,使用自定义的字符串大小比较函数). ...
- java byte数组与16进制间的相互转换
java byte数组与16进制间的相互转换 CreationTime--2018年6月11日15点34分 Author:Marydon 1.准备工作 import java.util.Array ...
- 20145325张梓靖 《Java程序设计》第16周课程总结
20145325张梓靖 <Java程序设计>第16周课程总结 实验报告链接汇总 实验一 "Java开发环境的熟悉" 实验二 "Java面向对象程序设计&quo ...
- 20145219 《Java程序设计》第16周课程总结
20145219 <Java程序设计>第16周课程总结 每周读书笔记(即学习总结)链接汇总 第0周问卷调查 第1周读书笔记 第2周读书笔记 第3周读书笔记 第4周读书笔记 第5周读书笔记 ...
- Java中char转为16进制
Java中char转为16进制 char a = '0'; String hexStr = Integer.toHexString(a); System.out.println(hexStr);
- Java内部静态类与内部非静态类
Java内部静态类与内部非静态类 把类看成一个属性,稍微容易理解一些:在main方法中,不会去直接引用一个非static的变量,对于类也一样. 学习了:http://blog.csdn.net/zer ...
- Java入门篇(五)——字符串/String类
前面在举例时有出现过String的例子,当时肯定有一部分朋友不知道这个是做什么用的.其实String类是Java中一个比较特殊的类,字符串即String类,它不是Java的基本数据类型之一,但可以像基 ...
随机推荐
- 某RBAC管理系统审计
某RBAC管理系统审计 前言 这个管理系统的审计我去年就开始了但烂尾了,那时候太热闹了log4j2,cs的cve反制等等.这个都给忘了,所以本篇可能有些图有点老,现在就是旧图没一个个换遇到的新的就加上 ...
- Mybatis源码3 CachingExecutor, 二级缓存,缓存的实现
Mybatis CachingExecutor, 二级缓存,缓存的实现 一丶二级缓存概述 上一章节,我们知道mybaits在构造SqlSession的时候,需要让SqlSession持有一个执行器,如 ...
- 文心一言 VS 讯飞星火 VS chatgpt (18)-- 算法导论4.1 5题
五.使用如下思想为最大子数组问题设计一个非递归的.线性时间的算法.从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组.若已知 A[1..j]门的最大子数组,基于如下性质将解扩展为 ...
- 掌握ROMA Compose,报表清单不秃头
摘要:在没有ROMA Compose之前,完成一个跨数据源的关联查询是一个十分艰巨的任务. 1. ROMA Compose为何诞生 试想这样一个场景,主管让刚入职的小沛明天下班前给他发一份报表.小沛兴 ...
- 想学AI开发很简单:只要你会复制粘贴
摘要:本次实践基于 mobilenetV2 实现猫狗图像分类,贯穿了数据集获取及处理.预训练模型微调及迁移.端侧部署及推理等环节和知识点,体会到了 MindSpore 简单的开发体验和全场景快速部署的 ...
- 看MindSpore加持下,如何「炼出」首个千亿参数中文预训练语言模型?
摘要:千亿参数量的中文大规模预训练语言模型时代到来. 本文分享自华为云社区< MindSpore开源框架加持,如何「炼出」首个千亿参数.TB级内存的中文预训练语言模型?>,原文作者:che ...
- “互联网+”大赛之智慧校园 赛题攻略:你的智慧校园,WeLink帮你来建
摘要:本赛题的核心就是借助华为云WeLink的中台服务能力/开发工具等,结合学校的具体的高价值场景,开发出WeLink小程序,方便师生的学习与生活. 本文分享自华为云社区<"互联网+& ...
- 火山引擎ByteHouse:如何用OLAP引擎提升数字营销效果?
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 随着市场竞争的加剧,企业对数字营销投入的效果监测和优化需求日益增强,营销实时监控也成为企业提升运营效率的重要手 ...
- VMware NAT 模式 虚拟机网络电缆被拔出,连不上网
检查服务 VMnetDHCP,VMware NAT Service 服务是否已启动,启动后可以正常使用网络
- 24校招,Moka测试开发工程师一面
前言 大家好,今天回顾一下楼主当时参加moka测试开发工程师的面试 对其中一些重要问题,我也给出了相应的答案 过程 自我介绍 挑一个项目,详细介绍你在其中担任的职责 如何安排工作的,有什么成果? 回归 ...