随遇而安

随遇而安 关注TA

额,假装这里有签名...

随遇而安

随遇而安

关注TA

额,假装这里有签名...

  • 加入社区3,279天
  • 写了837,964字

该文章投稿至Nemo社区   Java  板块 复制链接


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

发布于 2018/07/06 18:25 1,644浏览 0回复 2,890

原文出处: BuquTianya

我们上次在《细说Java中的字符和字符串(一)》说了Java中char和中文字符之间的关系,说明了char能不能存储一个中文字符,以及如何判断Java的字符串是否包含中文字符。
这次再看一下MySQL数据库中VARCHAR(N)中的N表示什么,什么会限制N的大小。然后,从Java的String源代码分析一下中文字符在Java字符串里是如何存储的。

VARCHAR(N)中的N代表什么?

和第一篇类似,我们依旧从官方文档寻找最有权威的答案。我们从这里可以找到VARCHAR(N)中N代表什么的答案:https://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html

由于原文比较长,这里先把关键片段贴一下:
这里写图片描述

答案就在这段文字里,我们翻译一下原文:VARCHAR 或者VARBINARY类型的列最大可存储的字节数不能超过一行的最大可存储字节数,也就是65535字节。如果VARCHAR类型的列存储的是多字节类型的字符,那么可存储的最大字符数相对就会变少。比如:utf8mb4类型的字符一个字符最大可以占到4个字节,所以一个VARCHAR类型的字段最多可以存储16383个字符(65535/4)。

重点看比如的文字,可以看出来MySQL的VARCHAR(N)中的N代表的是多少个字符(Character),而Character的对于不同的编码又代表什么呢?

一起看一下另一篇MySQL官网文档的内容:https://dev.mysql.com/doc/refman/8.0/en/charset-unicode.html

这里写图片描述

VARCHAR(N)的N值不论是哪种编码,都对应到了字符级别,也就是这个列可以存下N个字符。MySQL的utf8和Java里的utf8不一样,MySQL的实现里utf8最大只允许存储3个字节。从上一篇我们知道,utf8编码最长有4个字节,那么4个字节的utf8字符MySQL的utf8类型是存不下的。

所以,当需要存储emoji表情符的时候,我们需要用utf8mb4类型的字符集才能存下。MySQL的utf8mb4字符集才能包含全部utf8字符,而MySQL的utf8字符集是一个不完全版本的utf8字符集。

总结一下:MySQL中VARCHAR(N)字段类型里边的N代表最大可以存多少个字。如果选的是中文编码,比如utf8编码,那么N代表最大可以存多少个汉字。

VARCHAR(N)中的N可以是多大?

N的最大值主要限制因素是MySQL的最大行大小,原文地址在:https://dev.mysql.com/doc/refman/8.0/en/column-count-limit.html

MySQL的行大小最大不能超过65535bytes,是说MySQL的一行里所有列占用字节数的和不能超过65535bytes。BLOB和TEXT比较特别,他们只占用9个字节大小,真实数据存储在行外。

MySQL的uft8类型最多占3个字节,MySQL允许的最大字符数是21844(65535/3-1),utf8mb4最多占4个字节,所以允许的最大字符数是16382(65535/4-1)。这个数字有一点点偏差,因为VARCHAR类型需要1到2个字节存储length,所以最大字符数可以按照减1计算。

从Java String分析Java的char和中文的关系

String有三类构造函数,一类是传入byte[],一类是传入char[],一类是传入int[],如下:

public String(byte bytes[], int offset, int length);//字节流,转成字符串
public String(char value[], int offset, int count);//字符流,转成字符串。较少用,一般用在双字节编码的字节串转String。
public String(int[] codePoints, int offset, int count);//代码点,转成字符串

String内部数据存储使用的是char[]存储,byte 是8字节,char是16字节,int是32字节。所以byte[]参数的构造函数会把byte[]解析成正确的代码点,也就是int类型,再转换成char[]类型存到String内部。

所以,我们这里只分析int[]类型入参的构造函数,来看一下String是如何把int类型的代码点转成char[]存起来的。从这里,也能看出来一个2个字节的Unicode和一个2个以上字节的Unicode代码点是怎么存到String里的。

    public String(int[] codePoints, int offset, int count) {
        //....
        // Pass 1: 计算int[]转成char[]后的长度
        int n = count;
        for (int i = offset; i < end; i++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))//如果是BMP范围的代码点,那么计数加1
                continue;
            else if (Character.isValidCodePoint(c))//如果是超出BMP范围的有效代码点,那么计数加2
                n++;
            else throw new IllegalArgumentException(Integer.toString(c));
        }

        // Pass 2: 使用int[]的值填充char[]
        final char[] v = new char[n];

        for (int i = offset, j = 0; i < end; i++, j++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))//如果是BMP范围的代码点,直接加入char[]
                v[j] = (char)c;
            else//如果是超出BMP范围的有效代码点,那么转成2个char,存入char[]
                Character.toSurrogates(c, v, j++);
        }

        this.value = v;
    }
本文标签
 {{tag}}
点了个评