参考链接:
https://baike.baidu.com/item/%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81/8446880?fr=aladdin#7
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
http://blog.csdn.net/r0ck_y0u/article/details/51883955
一.字符编码
字符编码的发展史:ASCII->Unicode->UTF-8
1.ASCII:ASCII码可以表示所有的英语字符(字母、数字、标点符号等)。ASCII码是7位编码(0-127),但由于计算机基本处理单位为字节(1字节=8位),所以一个ASCII字符占一个字节。
2.Unicode:因为一个ASCII字符只能表示256个字符,显然是存在着局限的(如不能用来表示中文)。而且不同的语言有不同的字符,为了让世界上所有的字符都有一个唯一的编码值(如果一个编码值对应多个字符,就会出现歧义),就出现了Unicode码。Unicode码可以容纳100多万个符号,每个符号的编码都不一样。但是Unicode码的缺点是效率不高,比如UCS-4(Unicode的标准之一)规定用4个字节存储一个符号,那么每个英文字母前都必然有三个字节是0,原本只需1个字节现在却用了4个字节,这对存储和传输来说都很耗资源。
3.UTF-8:为了提高Unicode的编码效率,于是就出现了UTF-8编码。UTF-8可以根据不同的符号自动选择编码的长短。在UTF-8中,一个英文占1个字节,一个中文占3个字节。
二.string库
相关api:http://cloudwu.github.io/lua53doc/manual.html#pdf-string.sub
在lua中,string库都是针对单字节字符编码的。在UTF-8中,因为英语字符都是单字节字符,所以使用string库处理英语字符是没有问题的;但是中文字符是多字节字符,如果使用string库去处理是不行的。
--UTF-8编码,一个中文占3个字节
local a1 = "你好啊a"
print(string.byte(a1,1,4))--第1到第4个字节
print(string.len(a1))--字节总数
local startIndex, endIndex = string.find(a1, "你好")
print(startIndex .. " " .. endIndex)--第1到第6个字节
print("----------------------------------------------------")
local test = "泰"
local test2 = "法?"
print(string.len(test))
print(string.byte(test,1,10))
print(string.byte(test2,1,10))
--string.gsub的第二个参数为正则表达式,?表示匹配0个至1个
--字节230179176中的230179被替换成989898
local str = string.gsub(test, test2, function()
print("gsub success!")
return "bbb"
end)
print(str)
print(string.byte(str,1,10))
print(string.byte("b",1,10))
输出如下:
三.中文处理
先来测试一下中文是怎样匹配的:
--为了方便输出中文,这里使用ANSI编码
--在ANSI编码中,1个中文占2个字节
local test = "泰ab"
local result
print(string.byte(test,1,10))--泰:204169 a:97 b:98
print(type(string.byte(test,1,10)))--数字
--string.gsub 逐字节匹配
print("1.")
result = string.gsub(test, "[204169]", "c")
print(result)--[204169]:2,0,4,1,6,9的集合,因此匹配失败
print("2.")
result = string.gsub(test, "[\204169]", "c")
print(result)
print(string.byte(result,1,10))--第1个字节204匹配成功
print(string.byte("゛",1,10))--c:99 ゛:16997 b:98
print("3.")
result = string.gsub(test, "[\204\169]", "c")
print(result)--匹配成功2次
print("4.")
result = string.gsub(test, "[\204][\169]", "c")
print(result)--匹配成功1次,将原字符串中的中文替换了
输出如下:
UTF8的编码规则:
1.字符的第一个字节范围:(0-127)、(194-244)
2.字符的第二个字节及以后范围(针对多字节编码,如汉字):(128-191)
3.(192,193和245-255)不会出现在UTF8编码中
根据以上规则就可以得出处理中文的方法了:
--获取字符数
function GetWordCount(str)
local _,count = string.gsub(str, "[^\128-\193]", "")
return count
end
--将字符串转为table
function GetWordTable(str)
local temp = {}
for uchar in string.gmatch(str, "[%z\1-\127\194-\244][\128-\191]*") do
temp[#temp+1] = uchar
end
return temp
end
--utf8
local test = "泰ab好了."
print(GetWordCount(test))
local testT = GetWordTable(test) --%z:匹配0 *:表示0个至任意多个
for i=1,#testT do
print(testT[i])
end
四.敏感字处理
敏感字的处理主要体现在取名、聊天上,如果字符串中含有敏感字,则需要将其替换成“*”。一开始我使用的string.gsub方法,但是发现敏感字中有不少是带有特殊符号,从而使整个字符串变成了一个正则表达式了,发生了正则匹配的错误,而正确的做法应该是直接跟敏感字进行对比。后来采用的是string.find方法,因为它可以关闭正则匹配。
local sensitiveWordConfig = {"法?"};
function GetWordCount(str)
local _, count = string.gsub(str, "[^\128-\193]", "")
return count;
end
--内部接口:将字符串中的敏感字替换成*(替换一个)
function ReplaceSensitiveWord(originStr, sensitiveWord)
local resultStr = originStr;
--1:从索引1开始搜索 true:关闭模式匹配
local startIndex, endIndex = string.find(originStr, sensitiveWord, 1, true);
if (startIndex and endIndex) then
local strLen = string.len(originStr);
local maskWordCount = GetWordCount(sensitiveWord);
local maskWord = "";
for i=1,maskWordCount do
maskWord = maskWord .. "*";
end
-- print(string.format("startIndex: %d endIndex: %d", startIndex, endIndex));
-- print(string.format("strLen: %s maskWord: %s", strLen, maskWord));
if (startIndex == 1) then
resultStr = maskWord .. string.sub(originStr, endIndex + 1, -1);
elseif (endIndex == strLen) then
resultStr = string.sub(originStr, 1, startIndex - 1) .. maskWord;
else
local str = string.sub(originStr, 1,startIndex - 1);
local str2 = string.sub(originStr, endIndex + 1, -1);
resultStr = str .. maskWord .. str2;
end
end
return resultStr;
end
--内部接口:将字符串中的敏感字替换成*(替换所有)
function ReplaceSensitiveWordAll(originStr, sensitiveWord)
local str = originStr;
local str2 = ReplaceSensitiveWord(originStr, sensitiveWord);
while (str ~= str2) do
str = str2;
str2 = ReplaceSensitiveWord(str2, sensitiveWord);
end
return str2;
end
--内部接口:是否有该敏感字
function HasSensitiveWord(originStr, sensitiveWord)
local startIndex, endIndex = string.find(originStr, sensitiveWord, 1, true);
if (startIndex and endIndex) then
-- print("敏感字:" .. sensitiveWord);
return true;
else
return false;
end
end
--外部接口:敏感字替换
function ReplaceMaskWord(content)
for k,v in pairs(sensitiveWordConfig) do
content = ReplaceSensitiveWordAll(content, v);
end
return content;
end
--外部接口:是否有敏感字
function HasMaskWord(content)
for k,v in pairs(sensitiveWordConfig) do
if (HasSensitiveWord(content, v)) then
return true;
end
end
return false;
end
print(ReplaceSensitiveWord("法?123法?", "法?"));
print(ReplaceSensitiveWordAll("法?123法?", "法?"));
print(HasSensitiveWord("12中法?3文", "法?"));
print(ReplaceMaskWord("1法?法?2"));
print(HasMaskWord("1法?法?2"));
string.gsub()
- 原型:string.gsub (s, pattern, repl [,m])
- 解释:这个函数会返回一个替换后的副本,原串中所有的符合参数
pattern
的子串都将被参数repl
所指定的字符串所替换,如果指定了参数m
,那么只替换查找过程的前m
个匹配的子串,参数repl
可以是一个字符串、表、或者是函数,并且函数可以将匹配的次数作为函数的第二个参数返回,接下来看看参数repl
的含义: - 如果参数
repl
是一个常规字符串,成功匹配的子串会被repl
直接替换,如果参数repl
中包含转移字符%
,那么可以采用%n
的形式替换,当%n
中的n
取值1-9时,表示一次匹配中的第n个子串,当其中的n
为0时,表示这次匹配的整个子串,%%
表示一个单独的%
。 - 如果参数
repl
是一个表,那么每次匹配中的第一个子串将会作为整个表的键,取table[匹配子串]来替换所匹配出来的子串,当匹配不成功时,函数会使用整个字符串来作为table的键值。 - 如果参数
repl
是一个函数,那么每一次匹配的子串都将作为整个函数的参数,取function(匹配子串)来替换所匹配出来的子串,当匹配不成功时,函数会使用整个字符串来作为函数的参数。如果函数的返回值是一个数字或者是字符串,那么会直接拿来替换,如果它返回false
或者nil
,替换动作将不会发生,如果返回其他的值将会报错。
元字符 | 描述 | 表达式实例 | 完整匹配的字串 |
---|---|---|---|
字符 | |||
普通字符 | 除去%.[]()^$*+-?的字符,匹配字符本身 | Kana | Kana |
. | 匹配任意字符 | Ka.a | Kana |
% | 转义字符,改变后一个字符的原有意思。当后面的接的是特殊字符时,将还原特殊字符的原意。%和一些特定的字母组合构成了lua的预定义字符集。%和数字1~9组合表示之前捕获的分组 | K%wna %%na%% (a)n%1 | Kana %na% ana |
[...] | 字符集(字符类)。匹配一个包含于集合内的字符。[...]中的特殊字符将还原其原意,但有下面几种特殊情况 1. %],%-,%^作为整体表示字符']','-','^' 2. 预定义字符集作为一个整体表示对应字符集 3. 当]位于序列的第一个字符时只表示字符']' 4. 形如[^...],[...-...]有特定的其他含义 | [a%]na [%a]na [%%a]na []]na [%]]na [a-]na | %na wna wna ]na ]na -na |
[...-...] | -表示ascii码在它前一个字符到它后一个字符之间的所有字符 | [a-z]a | na |
[^...] | 不在...中的字符集合。 | [^0-9]na [^^0-9]na | Kna Kna |
重复(数量词) | |||
* | 表示前一个字符出现0次或多次 | [0-9]* [a-z]*9* | 2009 na |
+ | 表示前一个字符出现1次或1次以上 | n+[0-9]+ | n2009 |
? | 表示前一个字符出现0次或1次 | n?[0-9]+ | 2009 |
预定义字符集 | |||
%s | 空白符[ \r\n\t\v\f] | an[%s]?9 | an 9 |
%p | 标点符号 | an[%p]9 | an.9 |
%c | 控制字符 | ||
%w | 字母数字[a-zA-Z0-9] | [%w]+ | Kana9 |
%a | 字母[a-zA-Z] | [%a]* | Kana |
%l | 小写字母[a-z] | - | |
%u | 大写字母[A-Z] | - | |
%d | 数字[0-9] | - | |
%x | 16进制数[0-9a-fA-F] | - | |
%z | ascii码是0的字符 | - | |
分组 | |||
(...) | 表达式中用小括号包围的子字符串为一个分组,分组从左到右(以左括号的位置),组序号从1开始递增。 | ab(%d+) (%d+)%1 | ab233 123123 |
边界匹配(属于零宽断言) | |||
^ | 匹配字符串开头 | ^(%a)%w* | abc123 |
$ | 匹配字符串结尾 | %w*(%d)$ | abc123 |
%b | |||
%bxy | 平衡匹配(匹配xy对)。这里的x,y可以是任何字符,即使是特殊字符也是原来的含义,匹配到的子串以x开始,以y结束,并且如果从x开始,每遇到x,计算+1,遇到y计数-1,则结束的y是第一个y使得计数等于0。就是匹配成对的符号,常见的如%b()匹配成对的括号 |
需要用到的lua底层函数:
string.sub():Lua string.sub() - 简书
string.gsub():Lua string.gsub() - 简书
string.find():Lua string.find() - 简书