Lua中获取字符串长度

首先是关于字符长度的一些结束(可以不看)

在 Lua 中,获取字符串长度我们一般使用 #str(不建议使用 string.len(str) )!

local str = "abc"
local len = #str
print(len)  -- 3
 
str = "你们好"
len = #str
print(len)  -- 9

疑惑:

这里就出现了一个问题:为啥字符串 abc 的长度为 3,而字符串 你们好 的长度却是 9 呢?难道是哪里出问题了?当然不是!

其实这是字符编码导致的,在使用 UTF-8 字符编码的情况下,一个中文字符一般占 3 个字节,所以 3 个中文字符自然就是 9 个字节咯!

那么问题来了,现在我需要不管是中文字符还是其他字符,长度都为 1 该咋整呢?

查找资料了解了具体原因:

不同的编码格式占字节数是不同的,UTF-8编码下一个中文所占字节也是不确定的,通常是3个字符,可能是2个、4个字节;

出于效率考虑,于是又弄了一个UTF-16,不严谨地来说它等价于Unicode原生编码,它统一采用双字节表示一个字符

下面是Unicode和UTF-8转换的规则

Unicode 
||
UTF-8 

0000 - 007F 
||
0xxxxxxx 

0080 - 07FF 
||
110xxxxx 10xxxxxx 

0800 - FFFF 
|| 1110xxxx 10xxxxxx 10xxxxxx

例如"汉"字的Unicode编码是6C49

6C49在0800-FFFF之间,所以要用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 1100 0100 1001,将这个比特流按三字节模板的分段方法分为0110 110001 001001,依次代替模板中的x,得到:

1110-0110 10-110001 10-001001,即E6 B1 89,这就是其UTF8的编码

GBK、GB2312收编的汉字占2个字节,严格地用iso8859-1无法表示汉字,只能转为问号

回到Lua中获取字符串长度问题:

这里记录两种方案:

方案一

-- 获取字符串的长度(任何单个字符长度都为1)
--由于编码格式的原因,【#字符串】 的方式获取中文时是字节数量,所以按照视觉效果来说会觉得返回有误
function getStringLength(inputstr)
    if not inputstr or type(inputstr) ~= "string" or #inputstr <= 0 then  --inputstr不为nil、类型为字符串、且长度不为0
        return nil
    end
    local length = 0  -- 字符的个数
    local i = 1       --累计的每个字符的字节数,如果 i = 0,那么跳出while条件就是  i >= #intutstr 或 i == #inputstr
    while true do     --这里我们是通过获取一个字符的头字节来判断是几字节的,如汉字的头字节ASCII是大于223的,所以直接跳过后面2个字节的判断,byteCount = 3
        local curByte = string.byte(inputstr, i)  --获取单个字节的ASCII码
        local byteCount = 1                       --单个字符的字节数,根据ASCII判断字节数
        if curByte > 239 then
            byteCount = 4  -- 4字节字符
        elseif curByte > 223 then
            byteCount = 3  -- 汉字,3字节
        elseif curByte > 128 then
            byteCount = 2  -- 双字节字符
        else
            byteCount = 1  -- 单字节字符
        end
        -- local char = string.sub(inputstr, i, i + byteCount - 1)
        -- print(char)  -- 打印单个字符
        i = i + byteCount
        length = length + 1
        if i > #inputstr then
            break
        end
    end
    return length          --返回字符个数
end
 
local str = "I think,故我在.bmp"
local len = getStringLength(str)  --获取字符串字符长度
print(string.format("Number of characters: %s\nNumber of bytes: %s", len, #str))

以上代码输出结果如下:

 

注:这里可能不是很能理解上面的循环中的跳过条件,在上述示例中,当 “i”的值大于字符串的字节数时会跳出循环,而 “i” 的值时根据每一个字符的头字节来判断一个字符的字节长度,前面也说了,汉字的头字节是1110xxxx开头,也就是头字节的ASCII值是大于223的,竟然已经知道了这个字符是汉字,那么我们不再进行后面两个字节的判断,所以 “i” 的值也就 + 3(因为byteCount = 3),这样就直接跳到了下一个字符的头字节判断。这里要知到,字符和字节是不同的概念,字符可以由多个字节组成,下面我再通过图的方式来说明一下

1. 首先输出我们示例字符串的每个字节

local str = "I think,故我在.bmp"  --示例字符串
print(str:byte(1, #str))         --输出每个字节的值

2. 我们会得到下面数据

 可以明确的看到,英文和符号 “字符” 的头 “字节” ASCII码都是小于小于128的,也就是只占 1 字节

而中文 “字符” 的头 “字节” ASCII都是大于223的,说明占了3字节,所以我们也不在判断中文 “字符” 的其他 “字节”

如果不理解 “字符” 和 “字节”,可以参考:相关文章1   相关文章2

 

方案二

-- 计算 UTF8 字符串的长度,每一个中文算一个字符
function utf8len(input)
    local len  = string.len(input)  --这里获取到的长度为字节数,如示例长度为:21,而我们肉眼看到的长度应该是15(包含空格)
    local left = len                --将字节长度赋值给将要使用的变量,作为判断退出while循环的字节长度
    local cnt  = 0                  --将要返回的字符长度
    local arr  = {0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc}  --用来判断是否满足字节长度的列表
    while left ~= 0 do              --遍历每一个字符
        --获取字节的ASCII值,这里的 “-” 代表反向对应的索引,-left:input反着第left
        --假设字符串字符input长度是:21,left的值是:21,那string.byte(input, -left)就是第一个字节的ASCII值
        local tmp = string.byte(input, -left)  --看上面两行
        local i   = #arr                       --获取判断列表的长度,同时作为字节长度
        while arr[i] do                        --循环判定列表
            if tmp >= arr[i] then              --判定当前 “字符” 的 头“字节” ACSII值符合的范围
                left = left - i                --字符串字节长度 -i,也就是 减去字节长度
                break                          --结束判断
            end
            i = i - 1                          --每次判断失败都说明不符合当前字节长度
        end
        cnt = cnt + 1                          --“字符” 长度+1
    end
    return cnt                                 --返回 “字符” 长度
end
 
local str = "I think,故我在.bmp"
local len = utf8len(str)  --获取字符串字符长度
print(string.format("Number of characters: %s\nNumber of bytes: %s", len, str:len()))

注:方案二与方案一原理差不多,都是根据每个字符的头 “字节” 来判断字节长度,不同的是方案二是先遍历一个判断列表,根据符合 / 不符合条件来判断字节长度,如果一开始就符合了范围,那么字节长度就是判断列表的长度 “5”了,不过显然我们的示例字符串最大的字节长度也就 “3”

 

相关参考文章:文章1     文章2

posted @ 2022-05-18 17:43  青丝·旅人  阅读(3150)  评论(0编辑  收藏  举报