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”