1. 程式人生 > 其它 >Lua中獲取字串長度

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