按字节长度截取字符串
平时我们截取字符串一般都是按照可见字符个数直接用substring()方法截取,非常方便且实用,只要当前String对象本身不是乱码我们便能拿到想要的长度。这样的截取方法可以解决大部分应用场景下的问题。

但是也有部分场景是这样操作不能解决的,那就是按字节大小截取指定字符串。涉及到字节数截取,很容易想到一个问题:编码问题,大部分支持中文的编码都是以两个字节来表示一个中文字符,也有用3个字节表示中文的utf-8编码,针对这些不同编码的字符串截取就要考虑到有可能截取到残缺中文的问题。

其实也不是什么大问题,只要正确地得到了字节数组或者字符串(与当前软件运行环境相对)就好办了。因为知道了字符编码就能结合当前编码特点进行截取,避免出现截取残缺中文的情况。下面针对gbk(在简体中文Windows操作系统中,ANSI 编码就代表 GBK 编码)和utf-8这两种比较常见的编码给出个demo,其他编码也是类似的。
package org.devsong;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

public class EncodingTest {

    public static void main(String[] args) throws IOException {
        // 工作空间决定其编码,当前测试环境为utf-8,且保存源码文件为utf-8
        String str = "你好啊hello";

        // 为方便测试,这里只是简单获取byte数组传入,实际中byte可能从外部传入,比如网络等
        // utf-8: 截取 8 个字节的长度,很明显容不下 3 个汉字,所以只截取了 2 个汉字: 你好
        System.out.println(cutStrWithUTF8(str.getBytes("utf-8"), 8));
        // gbk: 截取 8 个字节的长度: 你好啊he
        System.out.println(cutStrWithGBK(str.getBytes("gbk"), 8));
    }

    public static String cutStrWithUTF8(byte[] utf8Bytes, int byteCnt) throws UnsupportedEncodingException {
        int zhCnt = 0;
        int cutNum = 0;
        if(byteCnt <= 0) {
            return "";
        }
        if(byteCnt >= utf8Bytes.length) {
            return new String(utf8Bytes, "utf-8");
        }
        // UTF-8中,汉字以3个字节表示,且3个字节的ASCII码都大于128, 即为 负
        for (int i = 0; i < byteCnt; i++) {
            if (utf8Bytes[i] < 0) { // 判断是否为汉字
                zhCnt++;
            }
        }
        //utf-8中一个汉字是由两个字节组成的 
        if (zhCnt % 3 == 0) {
            // 如果汉字的字节数能被 3 整除,则说明当前长度截取不会截到不完整的汉字
            cutNum = byteCnt;
        } else if (zhCnt % 3 == 2) {
            // 若余量为 2, 则按当前长度截取会存在残缺的汉字,所以此处截取字节数减 2,当然也可以加 1,这里看实际需求和个人喜好
            cutNum = byteCnt - 2;
        } else {
            // 若余量为 1,则也会截取到不完整的汉字,所以减一,亦可加 二,理由同上。总之只要汉字字符个数能被三整除就不会存在残缺汉字字符的情况
            cutNum = byteCnt - 1;
        }

        return new String(utf8Bytes, 0, cutNum, "utf-8");
    }

    public static String cutStrWithGBK(byte[] gbkBytes, int byteCnt) throws UnsupportedEncodingException {
        
        if(byteCnt <= 0) {
            return "";
        }
        if(byteCnt >= gbkBytes.length) {
            return new String(gbkBytes, "gbk");
        }
        
        int zhCnt = 0;
        for (int i = 0; i < byteCnt; i++) {
            // gbk中,汉字以 2 个字节表示,且 2 个字节的ASCII码都大于128, 即为 负
            if (gbkBytes[i] < 0) { // 判断是否为汉字
                zhCnt++;
            }
        }
        
        int cutNum = byteCnt;
        if (zhCnt % 2 != 0) {
            // gbk中一个汉字是由两个字节组成的 
            // 若余量不等于0,也就是余量为1,则也会截取到不完整的汉字,所以减一。总之只要汉字字符个数能被三整除就不会存在残缺汉字字符的情况
            cutNum = byteCnt - 1;
        }

        return new String(gbkBytes, 0, cutNum, "gbk");
    }

}
执行结果:
你好
你好啊he

注意
上面用到的一个String对象方法getbytes();这个方法有不少人存在误解,这个方法有个重载的版本是可以指定字符编码的,并不是只能获取与当前工作空间相对的编码字节数组。另外有人用如下方式将utf-8字符串转换为gbk字符串然后输出:
String str = "你好hello";
String str_gbk = new String(str.getBytes("utf-8"), "gbk");  //   
System.out.println(str_gbk);
然后说中文乱码表示转换成功了,如果我理解无误的话,我觉得上述做法很可笑。上述方法明显是错误str.getBytes("utf-8")以utf-8编码规则(一个中文3个字节)获取为byte数组; new String(xxx, "gbk")是将当前符合utf-8编码规则的字节数组以gbk编码规则(一个中文2个字节)存储为字符串,很明显,这样一来必然乱码,然而乱码的同时真的达到转化编码的目的了吗?很明显没有达到,而是进行了一个错误的转换。

以上只是我个人看法,若您在阅读过程中发现问题或您有不同见解,欢迎给我留言。
 
【原创内容,转载请注明出处】
It's
欢迎访问本站,欢迎留言、分享、点赞。愿您阅读愉快!
*转载请注明出处,严禁非法转载。
https://www.devsong.org
QQ留言 邮箱留言
头像
引用:
取消回复
提交
涂鸦
涂鸦
热门