文章目录

  • 1. 前言
  • 2. 解析
  • 1. 字节与字符区别
  • 2. java中的char
  • 3. 代理项(Surrogate)
  • 4. 截取字符串
  • 5. 将string插入数据库中
  • 1. mysql与oracle中varchar的区别
  • 2. 演示两者varchar区别
  • 3. 将错误信息入库(mysql、oracle)
  • 4. 演示(将字符串插入数据库中)
  • 3. 总结


1. 前言

  1. 在参与工作的时候,发现有时候会把一些错误信息落库操作,但错误信息的长度又是没办法提前预知其大小,因此,默认会在存储错误信息字段上设置默认值,但也会因为偶然事情,导致错误信息长度大于该字段定义的长度,从而入库操作失败。
  2. 为了解决上述问题,必然会需要对错误信息进行字符串substring截取操作
  3. 通常截取字符串,方式如下:
String str = "错误信息";
String errMsg = str.substring(0, "具体字段大小值");
xxxDao.insert(errMsg);

但会不会出现截取字符串出现乱码的情况呢?如下:
当一个错误信息String存储的是这个表情:😆

public class demo {
    public static void main(String[] args) {
        String str = "\uD83D\uDe06";
        System.out.println(str);
    }
}

spring字符串判空 spring字符串截取_字符串


现要求只要保留错误信息长度只有1:

public class demo {
    public static void main(String[] args) {
        String str = "\uD83D\uDe06";
        System.out.println("str的输出:" + str);
        System.out.println("str原有的长度:" + str.length());
        System.out.println("str截取长度为1的时候: " + str.substring(0, 1));
    }
}

spring字符串判空 spring字符串截取_spring字符串判空_02


从上图可以知道,这截取的字符串已不是人可以看得懂的了。

  1. 问题总结一句话:
    要如何正确截取字符串呢

2. 解析

1. 字节与字符区别

  1. 字节:是计算机信息技术用于计量存储容量的一种计量单位,通常情况下:一个字节等于八位。通俗来说:字节就是单位
  2. 字符:是指计算机中使用的字母、数字、字和符号。如:A,1,小明等,这些都可以叫字符。
  3. 通常常用字符集是gbk、utf8
  4. gbk中:一个英文字符由一个字节组成,一个中文字符由两个字节组成。
  5. uft8中:一个英文字符由一个字节组成,一个中文字符由三个字节组成。

2. java中的char

  1. 在java中,String字符串其实是由一个个字符组成的,而在java中char代表一个字符,它是采用 UTF-16 来表示字符,将字符定义为固定宽度的 16 位实体,也就是所谓的2个字节(8位为1字节)
  2. 这就说明在java中char类型的变量可以存储一个中文汉字
  3. char 这个类型是 16位的。它可以有65536种取值,即65536个编号,每个编号可以代表1种字符
    但实际上这些编号还远远不够用,比如表情,特殊生僻字等等,因此需要更加多编号代表其他字符,原本一个char可以代表一种字符,现在用一下排列组合用两个char代表另一种字符,这样子就可以多很多很多种情况,很多种编号。

3. 代理项(Surrogate)

  1. 代理项(Surrogate),是一种仅在 UTF-16 中用来表示补充字符的方法。在 UTF-16 中,为补充字符分配两个 16 位的 Unicode 代码单元:
  1. 第一个代码单元,被称为高代理项代码单元前导代码单元
  2. 第二个代码单元,被称为低代理项代码单元尾随代码单元
  1. 在 UTF-16 中用其2048个来作为代理项,
  1. 编号为 U+D800 至 U+DBFF 的规定为「High Surrogates」,共1024个。
  2. 编号为 U+DC00至 U+DFFF 的规定为「Low Surrogates」,也是1024个
  3. 它们两两组合出现,就又可以多表示1048576种字符。
  1. 如果丢失一个高位代理Surrogates或者低位代理Surrogates,就会出现乱码
  2. 特殊字符是由 高位代理项+低位代理项 构成。

4. 截取字符串

  1. 设当前有个字符串String str = ”表情:😆“;
    现需求:分别截取长度最大为 1、2、3、4的字符串出来,不允许有乱码
public class demo {
    public static void main(String[] args) {
    	// 1. str 字符串长度为5,即😆是占两个长度(char)
        String str = "表情:\uD83D\uDe06";
        System.out.println("str的输出:" + str);
        System.out.println("str原有的长度:" + str.length());
        System.out.println("str截取长度为1的时候: " + str.substring(0, 1));
        System.out.println("str截取长度为2的时候: " + str.substring(0, 2));
        System.out.println("str截取长度为3的时候: " + str.substring(0, 3));
        // 2. 这里输出的时候乱码了,因为之前所说的:
        // 如果丢失一个高位代理Surrogates或者低位代理Surrogates,就会出现乱码
        System.out.println("str截取长度为4的时候: " + str.substring(0, 4));
    }
}

因此在截取字符串的时候,不能想当然的根据长度截取,应考虑到高低位代理项的问题:

public class demo {
    public static void main(String[] args) {
        String str = "表情:\uD83D\uDe06";
        System.out.println("str的输出:" + str);
        
        // 设要截取的长度为4
        int length = 4;
        // 找到第四个字符
        // length - 1 是因为下标从0开始的
        char c = str.charAt(length - 1);
        // 判断其是否为高位代理
        if (Character.isHighSurrogate(c)) {
            // 若为高位代理项,说明会把两个char的字符截取到一半出现乱码
            // 因此要去掉该高位代理项
            // 这里 length - 1 是因为substring(0,3) 包左不包右
            // 因此 只有 0 1 2下标的char字符 只有三位
            System.out.println(str.substring(0, length - 1));
        } else {
            // 若不为高位代理项,有可能是低位代理项
            // 但是截取都到了低位代理项,说明前面已经包含高位代理项,不会乱码
            // 还有可能就是一个char的字符,肯定不会乱码
            System.out.println(str.substring(0, length));
        }

    }
}

spring字符串判空 spring字符串截取_字符串_03

5. 将string插入数据库中

1. mysql与oracle中varchar的区别

  1. 设在两种数据库中存储的编码都是utf8
  1. 在mysql中varchar(5):5代表5个字符,可以存储5个字符,即五个英文、或五个中文、只要存入的字符串长度是5都可以插入
  2. 在oracle中varchar(5):5代表5个字节,是字节,是字节。当然它也可以存储五个英文,但是最多只能存储1个中文,因为一个中文占3个字节,还有两个字节的位置,也就是最多再存储2个英文。
  3. 字节与字符的区别,上文有解释,简单来说就是字符 > 字节
  4. mysql不理这个字符占多少个字节,它理到底可以存多少字符

2. 演示两者varchar区别

  1. 在mysql中,准备表与代码如下:
  2. 在oracle中,表与代码都不变:
    在执行第一句:userDao.insert_data(“小明吃雪糕”),则报错如下:

    执行第二句,第三句的是可以插入的:

    在执行最后一句:userDao.insert_data(“小明吃AB”);

3. 将错误信息入库(mysql、oracle)

  1. 在mysql中只理到底可以存多少字符只需要判断截取会不会截取到乱码即可
  2. 在oracle中由于是理到底可以存储多少个字节,要先将字符转按编码转换,然后再判断截取会不会截取到乱码
  3. 有的人不理解为什么要提及这个区别:
    本来str = ”小明的表情" 是长度为5,但是转utf8 则是 15了

4. 演示(将字符串插入数据库中)

  1. mysql中,因为是varchar(5)代表可以存储5个字符:
@SpringBootTest
class CsdnApplicationTests {

    @Autowired
    UserDao userDao;

    @Test
    public void mysql_test() {
        // 长度为5的str
        userDao.insert_data(handleString("表情:\uD83D\uDE06", 5));

        // 长度为6的str
        userDao.insert_data(handleString("他表情:\uD83D\uDE06", 5));

        // 长度很长很长的str
        userDao.insert_data(handleString("小明吃雪糕之后,他的表情是:\uD83D\uDE06", 5));
    }

    private static String handleString(String str, int length) {
        if (str.length() > length) {
            // 找到最后一位字符
            char c = str.charAt(length - 1);
            // 判断是否为高代理项
            if (Character.isHighSurrogate(c)) {
                // 若为高代理项,说明目前截取两个char的字符一半,有乱码
                // 因此 多截一个
                str = str.substring(0, length - 1);
            } else {
                // 到这里就是正常的char或者低代理项
                // 低代理项 则说明前面已经包括了高代理项
                // 因此不会有乱码,不用特殊处理
                str = str.substring(0, length);
            }
        }
        return str;
    }
}

spring字符串判空 spring字符串截取_System_04

  1. oracle中,因为是varchar(5)只能存储一个中文:
@SpringBootTest
class CsdnApplicationTests {
    
    @Autowired
    UserDao userDao;

    @Test
    public void oracle_test() {
        // 长度为 5的str
        userDao.insert_data(handleString("表情:\uD83D\uDE06",5));

        // 长度为 6的str
        userDao.insert_data(handleString("他表情:\uD83D\uDE06",5));

        // 长度很长很长的str
        userDao.insert_data(handleString("小明吃雪糕之后,他的表情是:\uD83D\uDE06",5));
    }

    private static String handleString(String str, int length) {
        // 若本身str就比length长很多,优先截取一次,优化下面转utf8的效率
        if (str.length() > length) {
            // 找到最后一位字符
            char c = str.charAt(length - 1);
            // 判断是否为高代理项
            if (Character.isHighSurrogate(c)) {
                // 若为高代理项,说明目前截取两个char的字符一半,有乱码
                // 因此 多截一个
                str = str.substring(0, length - 1);
            } else {
                // 到这里就是正常的char或者低代理项
                // 低代理项 则说明前面已经包括了高代理项
                // 因此不会有乱码,不用特殊处理
                str = str.substring(0, length);
            }
        }

        // 针对oracle的字节,优先转字符编码格式
        // 若转义之后 仍然是大于指定的length,则还需要继续截取,再截掉最后一位
        while (StandardCharsets.UTF_8.encode(str).limit() > length) {
            // 从上面已经截取过的str找到最后一位
            char c = str.charAt(str.length() - 1);
            // 判断是否是低代理项,
            if (Character.isLowerCase(c)) {
                // 因为转utf8的时候发现还是长了,需要再截掉一位
                // 而这一位如果是低代理项,则必须把前面的高代理项也去掉,不然会乱码
                // 因此多截取了一位
                str = str.substring(0, str.length() - 2);
            } else {
                // 如果是正常的或者是高代理项,则截掉一位就好了
                str = str.substring(0, str.length() - 1);
            }
        }
        return str;
    }
}

spring字符串判空 spring字符串截取_字符串截取_05


完美插入,并没有报错。

3. 总结

  1. 对于特殊的字符串,不能想当然的使用str.subString(0, length),因为还需要考虑两个char组成的字符,会不会截取到中间,导致乱码的问题。
  2. 理解什么是高位代理项、低位代理项。
  3. 对于数据库mysql、oracle的varchar的区别,在截取字符串的时候,oracle还需要加上转编码格式之后的比较长度大小。