文章目录
背景
我们知道在数据库中字符串类型有定长的char, 变长的varchar, 以及大字段 clob(oracle 中最大支持到 4GB) 等。正常情况下,像身份证号、手机号这类我们设计为char,地址设计为 varchar,还有长文本设计为 clob 基本就满足了日常需要。
我们知道表中一条记录是一个存储块,那么 clob 字段也会存储在这个块中吗?在 Oracle 中当 clob 字段长度不超过 4000 个字节时是这样的,超过 4000 会开辟新的空间来存储 clob。clob 大小按照 4000 的2的幂进行开辟,换句话说,当 clob 字段长度为 4001 时 clob 字段占用的大小为 8000, 当为 8001 时占用的大小为 16000,在字段更大时空间浪费情况更为严重。
那么能不能降大的 clob 字段拆成若干个小的字段呢?比如 4000,如果内容都是 ASCII 字符时相当容易,直接按照 4000 切分即可,那么包含中文呢?如何保证中文的可阅读性,又充分利用空间呢?
中文 UTF-8 编码
当前主流的编码方式为 UTF-8,这种编码方式能够容纳所有中文。UTF-8的编码规则如下:
由上图不难发现:单字节时候,该字节的二进制是 0 开头;多字节时候字节有1开头,并且该字符的第一个字节从高位期连续 1 的个数,表示该字符占用多少个字节,比如双字节字符第一个字节是 110开头,三字节字符第一个字节是1110 开头等等。
切分方案
基于上述 UTF-8 的编码理论,我们可以做如下切分方案(以切分4000个长度为例):
- 判断4000 边界字节是否为单字节,如果是直接切分;
- 多字节情况只需要从边界字节 4000 开始一次向前判断是否为多字节第一个字节即可:我们可以根据该字节的前两位是否为 0b11 进行判断,最多向左移动 5 个字节。
之所以不会出现向左移动 6 个字节是因为切分前先判断了是否恰好切分。
编码实现
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
String str2 = getRandomString(100000); // 随机生成字符串的个数
byte[] sbytes = str2.getBytes(StandardCharsets.UTF_8);
int length = 4000; // 要切分的长度
int start = 0;
int byteStart = 0b11;
int cur = start + length;
while(true){
if (start < sbytes.length && start + length >= sbytes.length){
int copyLenght = sbytes.length - start;
byte[] temp = new byte[copyLenght];
System.arraycopy(sbytes, start, temp, 0, copyLenght);
stringList.add(new String(temp));
break;
}
if(sbytes[cur - 1]