85

细说 Java 中的字符和字符串( 一 )

 5 years ago
source link: http://www.importnew.com/29073.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一道经典问题

Java里的char类型能不能存储一个中文字符?

对于这道题,绝大多数的答案都是“可以存储”。给出的原因包括:

1. java中的char是unicode存储,unicode编码字符集中包含了汉字,所以可以存储中文;

2. java内部其实是使用的UTF-16的编码,所以是支持大部分非生僻汉字的;

3. 采用Unicode编码集,一个char占用两个字节,而一个中文字符也是两个字节,因此Java中的char是可以表示一个中文字符的;

4. Java的char只能表示utf­16中的BMP部分中文字符,不能表示扩展字符集里的中文字符;

那么,这个问题的终极答案到底是什么?

Java API中关于char的说明

原文地址: https://docs.oracle.com/javase/7/docs/api/java/lang/Character.html

char类型是按照Unicode规范实现的一种数据类型,固定16bit大小。现如今,
Unicode字符集已经进行了扩展,表示的范围已经超过了16bit。Unicode字符集的
数值范围扩大到了[U+0000,U+10FFFF]。

也就是说一个char能够存储16bit大小的数值,即2个字节。但是,就常用的UTF-8编码来说,我们都听说过他是用3或者4个字节来表示一个汉字的。就拿3个字节来算的话,一个char也存不下是不是?

我们继续看api文档的其他段落:

一个char值可以表示BMP范围内的Unicode字符。BMP表示[U+0000, U+FFFF]之间的Unicode字符。

而且,绝大部分的中文字符的Unicode范围是[0x4E00, 0x9FBB],恰好是在BMP范围内。

是不是说这里出现了破解不了的矛盾呢?UTF-8占用3到4个字节,char只能存2个字节(16bit),然而UTF-8中的几乎所有汉字都是在BMP范围内,也就是在char可存储的范围内,是不是矛盾了?

答案是不矛盾!关键点就在于接下来给出总结的第一条!

这里先给出总结,后续再给出解释:

1. char字符存储的是Unicode编码的代码点 ,也就是存储的是U+FF00这样的数值,然而我们在调试或者输出到输出流的时候,是JVM或者开发工具按照 代码点 对应的编码字符输出的。

2. 所以虽然UTF-8编码的中文字符是占用3个或者4个字节,但是对应的 代码点 仍然集中在[0x4E00, 0x9FBB],所以char是能够存下在这个范围内的中文字符的。

3. 但是对于超过16bit的Unicode字符集,也就是Unicode的扩展字符集,一个char是放不下的,需要两个char才能放下。

Unicode编码

Unicode的出现是对混乱的ANSI编码世界的一个大一统,因而也叫做统一码、万国码、单一码。Unicode编码把世界上常用的语言字符都进行了统一的编码,一个数值就代表一个字符,而且世界范围内公认。

ANSI的编码世界里,各中语言有自己的编码规范,同一个数值在不同的国家代表不同的字符。所以当文字在不同国家传递的时候(比如发邮件,看国外网页),问题就很大了,我明明写的是“爱我中华”,美国朋友看到的确实”°®ÎÒÖлª”,一定是一脸问号!

//=====模拟文字在不同编码语言间传递的过程=====
        //发帖子
        String s = "爱我中华";
        //编码成字节流,通过网络传入,或者存储到文件
        byte[] bytes = s.getBytes("GB2312");
        System.out.println(s);
        //国外朋友用自己电脑的编码方式解析字节流
        String s2 = new String(bytes, "ISO-8859-1");
        //oh! shit, wtf!        
        System.out.println(s2);

有了Unicode这个统一编码之后,全世界的计算机都能正确的解析到原始的字符,对于国内的文字信息,国外的朋友唯一要做的就是懂中文!

UTF-8只是Unicode编码的一种编码转换规范,也就是怎么存储Unicode代码点的方案之一。另外还有UTF-16和UTF-32等编码规范。Unicode为什么需要这么多编码规范?直接存储代码点行不行?

当然不行,存储了就需要解析比如”汉字”两个字的Unicode代码点是“0x6c49和0x5b57”也就是”6c495b57”。而且,Unicode的代码点还有3个字节的,比如”10FF3B”,对于一个很长的上述数字串该怎么解析?比如“10FF3B6c495b57”!

所以,需要某种编码方案来区分那几个数值是一个Unicode代码点,这种方案就是UTF-8、UTF-16、UTF-32这样的编码方案。

UTF-8编码和代码点对应关系

UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下:

Unicode编码(十六进制) UTF-8 字节流(二进制) 000000-00007F 0xxxxxxx 000080-0007FF 110xxxxx 10xxxxxx 000800-00FFFF 1110xxxx 10xxxxxx 10xxxxxx 010000-10FFFF 11110xxx10xxxxxx10xxxxxx10xxxxxx

有没有发现点什么?当一个字节表示一个字符时,二进制开头是0;当两个字节表示一个字符时,二进制开头是11;当3个字节表示一个字符时,二进制开头是111;依次类推!

UTF-8编码加入了多余的标识位来区分一个Unicode代码点!才会出现中文汉字集中在[0x4E00, 0x9FBB]范围的16bit数值内,UTF-8却需要3个字节存储的原因。

另一个经典问题

怎么判断Java字符串是否包含中文?

这个问题也很经典,一般我们可以查到的方法如下:

//代码来自HanLP自然语言处理库,git地址:https://github.com/hankcs/HanLP/blob/master/src/main/java/com/hankcs/hanlp/utility/TextUtility.java
    /**
     * 判断某个字符是否为汉字
     *
     * @param c 需要判断的字符
     * @return 是汉字返回true,否则返回false
     */
    public static boolean isChinese(char c)
    {
        String regex = "[\\u4e00-\\u9fa5]";
        return String.valueOf(c).matches(regex);
    }
//来源地址:https://blog.csdn.net/z69183787/article/details/53162069这里考虑进了CJK的扩展字符集


// GENERAL_PUNCTUATION 判断中文的“号  
    // CJK_SYMBOLS_AND_PUNCTUATION 判断中文的。号  
    // HALFWIDTH_AND_FULLWIDTH_FORMS 判断中文的,号  
    private static final boolean isChinese(char c) {  
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);  
        if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS  
                || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS  
                || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A  
                || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION  
                || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION  
                || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) {  
            return true;  
        }  
        return false;  
    }

总结一下,一般来说用第一种方法就足够了,扩展字符集用的比较少,另外从HanLP的github星数来说,这种方案的通用度还是可信的。

70

好了,先到这里。这里也留一个坑,mysql数据库里边的VARCHAR类型和Java的char类型是一种处理方式么?下一篇也会从String源代码的角度对这里的分析进行一个佐证。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK