char与String

本文探讨了在Java中使用char类型处理Unicode字符的局限性,由于Unicode字符集的扩展,16位的char类型已不足以表示所有字符。建议避免在编程时直接使用char,而是倾向于处理字符串作为抽象数据类型。同时,文章提到了敏感信息存储的安全问题,推荐使用char数组而非String来存储密码等敏感信息,以增强安全性。
摘要由CSDN通过智能技术生成

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

在《java核心技术》中:

在java中,char类型描述了 UTF-16编码中的一个代码单元。
我们强烈建议不要在程序中使用char类型,除非确实需要处理UTF-16代码单元。最好将字符串作为抽象数据类型处理。

在这里插入图片描述

To understand the char type, you have to know about the Unicode encoding scheme. Unicode was invented to overcome the limitations of traditional character encoding schemes. Before Unicode, there were many different standards: ASCII in the United States, ISO 8859-1 for Western European languages, KOI-8 for Russian, GB18030 and BIG-5 for Chinese, and so on. This causes two problems. A particular code value corresponds to different letters in the various encoding schemes. Moreover, the encodings for languages with large character sets have variable length: some common characters are encoded as single bytes, others require two or more bytes.

Unicode was designed to solve these problems. When the unification effort started in the 1980s, a fixed 2-byte width code was more than sufficient to encode all characters used in all languages in the world, with room to spare for future expansion—or so everyone thought at the time. In 1991, Unicode 1.0 was released, using slightly less than half of the available 65,536 code values. Java was designed from the ground up to use 16-bit Unicode characters, which was a major advance over other programming languages that used 8-bit characters.

Unfortunately, over time, the inevitable happened. Unicode grew beyond 65,536 characters, primarily due to the addition of a very large set of ideographs used for Chinese, Japanese, and Korean. Now, the 16-bit char type is insufficient to describe all Unicode characters.

We need a bit of terminology to explain how this problem is resolved in Java, beginning with JDK 5.0. A code point is a code value that is associated with a character in an encoding scheme. In the Unicode standard, code points are written in hexadecimal and prefixed with U+, such as U+0041 for the code point of the letter A. Unicode has code points that are grouped into 17 code planes. The first code plane, called the basic multilingual plane, consists of the “classic” Unicode characters with code points U+0000 to U+FFFF. Sixteen additional planes, with code points U+10000 to U+10FFFF, hold the supplementary characters.

The UTF-16 encoding is a method of representing all Unicode code points in a variable length code. The characters in the basic multilingual plane are represented as 16-bit values, called code units. The supplementary characters are encoded as consecutive pairs of code units. Each of the values in such an encoding pair falls into an unused 2048-byte range of the basic multilingual plane, called the surrogates area (U+D800 to U+DBFF for the first code unit, U+DC00 to U+DFFF for the second code unit).This is rather clever, because you can immediately tell whether a code unit encodes a single character or whether it is the first or second part of a supplementary character. For example, the mathematical symbol for the set of integers has code point U+1D56B and is encoded by the two code units U+D835 and U+DD6B. (See http://en.wikipedia.org/wiki/UTF-16 for a description of the encoding algorithm.)

In Java, the char type describes a code unit in the UTF-16 encoding.

Our strong recommendation is not to use the char type in your programs unless you are actually manipulating UTF-16 code units. You are almost always better off treating strings (which we will discuss starting on page 51) as abstract data types.


翻文:
要理解char类型,您首先必须了解Unicode编码模式。Unicode的发明克服了传统的字符编码方案的局限性。在Unicode出现之前,有许多不同的标准:美国的ASCII编码,ISO8859-1 为西方欧洲语言编码,KOI-8 为俄罗斯编码方式,GB18030 BIG-5 是中国语言的编码方式,等等。这将导致两个问题:一个特定的代码值对应于不同的字母的各种编码方案。此外,与大字符集编码语言长度相比,一些常见的字符编码为一个字节,其他人需要两个或两个以上的字节。

Unicode旨在解决这些问题。当统一工作始于1980年代,一个固定的2字节代码已经足够宽度编码用于世界上所有的语言,所有的人物和空闲空间以及未来的扩展,而且当时的每个人也都这样认为。1991年,Unicode 1.0版本发布,Unicode字符使用了全部范围(65536)的略低于一半的代码值。在其他编程语言还在使用8位字符时,Java已经开始从头设计,选择使用16位Unicode字符,这是一个重大进步。

不幸的是,随着时间的推移,不可避免的发生了。Unicode增长超出65536个字符,主要是由于增加了一些非常大的象形文字用于中国,日本,韩国。甚至到现在为止,16位字符类型是不足以描述所有Unicode字符的。

我们需要一个术语,来解释这个问题在Java中是如何解决的。并且,这个解决方案是于从 JDK 5.0 版本开始的。在一个编码方案中,Code Point(代码点)和Code Value(代码值)是通过特征相关联的。在标准的Unicode编码中,Code Point都是用带 U+ 前缀的十六进制表示,比如:U+0041 的Code Point 就表示大写字母 A 。Unicode编码包含很多个 Code Point,这些 Code Point 又组成了17种不同的 Code Plane(代码位面)。第一个 Code Plane,被称为基本的多语种位面,由“经典”Unicode字符的Code Point从 U+0000 一直到 U+FFFF。还包括从 U+10000 到 U+10FFFF 的补充字符,组成了十六种额外的Code Plane。

utf-16 编码是在一个可变长度的编码方式,它代表了所有Unicode代码点的方法。人物的基本语言平面表示为16位值,这被称为代码单元。这些代码单元还需要不断的补充新的字符编码。在这一系列的编码中,任何一个值都存在与一个未使用过的2048字节的范围内的基本语言平面,这被称为代理区域。这是相当睿智的,因为你可以马上分辨出一个代码单元编码了一个字符,或者是否为第一或第二部分补充字符。例如,数学符号的整数集合的代码点为 U+1d56b ,和由两个代码编码单元 U+D835 和 U+DD6B 组成的。

在Java中,char类型也仅仅是描述 utf-16 编码的代码单元。

强烈建议不要使用char类型在程序中,除非你实际上是操纵 utf-16 编码单元。否则,你几乎总是能更好的处理字符串,作为抽象数据类型。

ps:Code Point(代码点)实际上代表的是一个真正的Unicode字符,即Unicode字符集上的码位值。


java强制采用UTF-16编码字符串,而16位最多能表示6万多个字符,但是Unicode字符有11万多个,这就引发了一个问题,java的char类型有些字符无法表示。这里就要涉及很多问题,其中比较复杂,我就以最简单的方式回答吧。

让我们先搞懂什么是UTF-16,什么是Unicode字符集

Unicode简单来说就是对每个字符编一个号,而UTF-16则是决定用什么方法编一个号,除了UTF-16,还有UTF-8,等等其他的方式。好,现在知道了什么是UTF-16,和Unicode之后。我们来解释一下为什么输出的长度是10,二不是9。

前面已经说了java的char类型无法表示所有的Unicode字符,那java是如何想要表示比如‘ ’这个字符呢?很简单,一个char不行,那就两个。这里我就不解释如何用两个char共32位表示,说起来太麻烦也很复杂。所以’ '是占用两个字节的,这里就会输出10而不是9。

别人遇到过的问题

最近项目中遇到一个问题,反复测试才发现问题出在了数据库中,由于使用了 Hibernate 这种ORM框架,因此,在java中写的 EntityBean 就可以直接通过ORM映射到Oracle数据库了,这也导致了很多的问题。

那么,到底为什么java里不推荐使用char类型呢?其实,1个java的char字符并不完全等于一个unicode的字符。char采用的UCS-2编码,是一种淘汰的UTF-16编码,编码方式最多有65536种,远远少于当今Unicode拥有11万字符的需求。java只好对后来新增的Unicode字符用2个char拼出1个Unicode字符。导致String中char的数量不等于unicode字符的数量。

然而,大家都知道,char在Oracle中,是固定宽度的字符串类型(即所谓的定长字符串类型),长度不够的就会自动使用空格补全。因此,在一些特殊的查询中,就会导致一些问题,而且这种问题还是很隐蔽的,很难被开发人员发现。一旦发现问题的所在,就意味着数据结构需要变更,可想而知,这是多么大的灾难啊。

尽量不要使用 String 来存储密码等敏感信息

官方:https://docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html#PBEEx

It would seem logical to collect and store the password in an object of type java.lang.String. However, here’s the caveat: Objects of type String are immutable, i.e., there are no methods defined that allow you to change (overwrite) or zero out the contents of a String after usage. This feature makes String objects unsuitable for storing security sensitive information such as user passwords. You should always collect and store security sensitive information in a char array instead.

我们通常使用一个 String 类型变量来保存用户提交的密码等敏感信息,但实际上这是不安全的做法。

从 String 类的签名可以看到,String 的对象都是不可变的,也就是说 String 对象一旦被创建就不能通过任何方法(除了使用反射)对它进行修改,直到其被垃圾回收器回收(这段时间这个 String 对象通常会存在于常量池中)。这也就意味着在 String 对象被创建到垃圾回收器对它进行回收的这段时间,一旦内存被 dump,那么密码等敏感信息将以明文的形式暴露。

另外,我们可能在编程中无意的将密码打印到了日志中,这也可能因为日志文件被盗取而导致敏感信息被泄露。最后,在 Java 官方文档中对基于密码的加密这部分也建议不使用 String 对象来保存密码。

那么我们该使用什么方式来保存密码这类敏感信息呢?正确的选择是使用 char 数组。因为使用数组我们能够在对敏感信息的业务逻辑处理完成后及时的将其设置为其他任何值,这样就可以清楚掉我们的密码信息。同样,如果我们无意中对密码进行了日志打印,那么 char 数组输出的也是内存地址而不是我们的敏感数据。

需要注意的是,即使使用 char 数组来保存敏感信息依然不能保证绝对的安全,因为在内存中可能还会存在这些数据的零散碎片。更加安全的做法是对保存的敏感信息进行 hash,且最好是加盐 hash,这样能够更进一步的提高信息的安全性。但不得不说,没有绝对的安全,只能更可靠的安全防护方式。

String tt = "我喜欢\uD834\uDF06这个字符";
System.out.println(tt.length());
System.out.println("string code points count = " + tt.codePoints().count());

String strPassword ="Unknown";
char[] charPassword = new char[] {'U','n','k','w','o','n'};
System.out.println("字符密码:"+ strPassword);
System.out.println("字符密码:"+ charPassword);
9
string code points count = 8
字符密码:Unknown
字符密码:[C@377dca04

参考网址:
1.https://blog.csdn.net/happylee6688/article/details/33306069
2.https://zhuanlan.zhihu.com/p/23654187
3.https://zhuanlan.zhihu.com/p/74737553

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值