zxl
/
CTT
forked from Cal/CTT
1
0
Fork 0
CTT/Unity/Assets/Hotfix/Logic/Behaviour/Game/Helper/IllegalWordHelper.cs

333 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System;
using System.Collections;
using System.Collections.Generic;
namespace ET
{
/// <summary>
/// 此算法思想来源于“http://www.cnblogs.com/sumtec/archive/2008/02/01/1061742.html”,经测试,检测"屄defg东正教dsa SofU ckd臺灣青年獨立聯盟daoiuq 样什么J& b玩意 日你先人"这个字符串并替换掉敏感词平均花费2.7ms
/// </summary>
public static class IllegalWordHelper
{
/// <summary>
/// 存了所有的长度大于1的敏感词汇
/// </summary>
static HashSet<string> wordsSet = new HashSet<string>();
/// <summary>
/// 存了某一个词在所有敏感词中的位置超出8个的截断为第8个位置
/// </summary>
static byte[] fastCheck = new byte[char.MaxValue];
/// <summary>
/// 存了所有敏感词的长度信息“Key”值为所有敏感词的第一个词敏感词的长度会截断为8
/// </summary>
static byte[] fastLength = new byte[char.MaxValue];
/// <summary>
/// 保有所有敏感词汇的第一个词的记录,可用来判断是否一个词是一个或者多个敏感词汇的“第一个词”,且可判断以某一个词作为第一个词的一系列的敏感词的最大的长度
/// </summary>
static byte[] startCache = new byte[char.MaxValue];
static char[] dectectedBuffer = null;
static string SkipList = " \t\r\n~!@#$%^&*()_+-=`¥……【】、{}|;':\",。、《》?\\αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩①②③④⑤⑥⑦⑧⑨⑩⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇≈≡≠=≤≥<>≮≯∷±+-×÷/∫∮∝∞∧∨∑∏∪∩∈∵∴⊥∥∠⌒⊙≌∽√§№☆★○●◎◇◆□℃‰€■△▲※→←↑↓〓¤°#&@\︿_ ̄―♂♀┌┍┎┐┑┒┓─┄┈├┝┞┟┠┡┢┣│┆┊┬┭┮┯┰┱┲┳┼┽┾┿╀╁╂╃└┕┖┗┘┙┚┛━┅┉┤┥┦┧┨┩┪┫┃┇┋┴┵┶┷┸┹┺┻╋╊╉╈╇╆╅╄";
static BitArray SkipBitArray = new BitArray(char.MaxValue);
/// <summary>
/// 保有所有敏感词汇的最后一个词的记录,仅用来判断是否一个词是一个或者多个敏感词汇的“最后一个词”
/// </summary>
static BitArray endCache = new BitArray(char.MaxValue);
unsafe public static void Init(string[] badwords)
{
if (badwords == null || badwords.Length == 0)
return;
int wordLength = 0;
int maxWordLength = int.MinValue;
for (int stringIndex = 0, len = badwords.Length; stringIndex < len; ++stringIndex)
{
if (string.IsNullOrEmpty(badwords[stringIndex]))
continue;
string strBadWord = OriginalToLower(badwords[stringIndex]);
///求得单个的敏感词汇的长度
wordLength = strBadWord.Length;
maxWordLength = Math.Max(wordLength, maxWordLength);
fixed (char* pWordStart = strBadWord)
{
for (int i = 0; i < wordLength; ++i)
{
///准确记录8位以内的敏感词汇的某个词在词汇中的“位置”
if (i < 7)
fastCheck[*(pWordStart + i)] |= (byte)(1 << i);
else///8位以外的敏感词汇的词直接限定在第8位
fastCheck[*(pWordStart + i)] |= 0x80;///0x80在内存中即为1000 0000因为一个byte顶多标示8位故超出8位的都位或上0x80截断成第8位
}
///缓存敏感词汇的长度
int cachedWordslength = Math.Min(8, wordLength);
char firstWord = *pWordStart;
///记录敏感词汇的“大致长度超出8个字的敏感词汇会被截取成8的长度“key”值为敏感词汇的第一个词
fastLength[firstWord] |= (byte)(1 << (cachedWordslength - 1));
///缓存出当前以badWord第一个字开头的一系列的敏感词汇的最长的长度
if (startCache[firstWord] < cachedWordslength)
startCache[firstWord] = (byte)(cachedWordslength);
///存好敏感词汇的最后一个词汇的“出现情况”
endCache[*(pWordStart + wordLength - 1)] = true;
///将长度大于1的敏感词汇都压入到字典中
if (!wordsSet.Contains(strBadWord))
wordsSet.Add(strBadWord);
}
}
/// 初始化好一个用来存检测到的字符串的buffer
dectectedBuffer = new char[maxWordLength];
/// 记录应该跳过的不予检测的词
fixed (char* start = SkipList)
{
char* itor = start;
char* end = start + SkipList.Length;
while (itor < end) SkipBitArray[*itor++] = true;
}
}
unsafe static string OriginalToLower(string text)
{
fixed (char* newText = text)
{
char* itor = newText;
char* end = newText + text.Length;
char c;
while (itor < end)
{
c = *itor;
if ('A' <= c && c <= 'Z')
{
*itor = (char)(c | 0x20);
}
++itor;
}
}
return text;
}
unsafe static bool EnsuranceLower(string text)
{
fixed (char* newText = text)
{
char* itor = newText;
char* end = newText + text.Length;
char c;
while (itor < end)
{
c = *itor;
if ('A' <= c && c <= 'Z')
{
return true;
}
++itor;
}
}
return false;
}
/// <summary>
/// 过滤字符串,默认遇到敏感词汇就以'*'代替
/// </summary>
/// <param name="text"></param>
/// <param name="mask"></param>
/// <returns></returns>
unsafe public static string Filter(string text, string mask = "*")
{
Dictionary<int, int> dic = DetectIllegalWords(text);
///如果没有敏感词汇,则直接返回出去
if (dic.Count == 0)
return text;
fixed (char* newText = text, cMask = mask)
{
char* itor = newText;
Dictionary<int, int>.Enumerator enumerator = dic.GetEnumerator();
///开始替换敏感词汇
while (enumerator.MoveNext())
{
///偏移到敏感词出现的位置
itor = newText + enumerator.Current.Key;
for (int index = 0; index < enumerator.Current.Value; index++)
{
///屏蔽掉敏感词汇
*itor++ = *cMask;
}
}
enumerator.Dispose();
}
return text;
}
///// <summary>
///// 判断text是否有敏感词汇,如果有返回敏感的词汇的位置,利用指针操作来加快运算速度,暂时独立出一个函数出来,不考虑代码复用的情况
///// </summary>
///// <param name="text"></param>
///// <returns></returns>
//unsafe public static bool IllegalWordsExistJudgement(string text)
//{
// if (string.IsNullOrEmpty(text))
// return false;
// fixed (char* ptext = text, detectedStrStart = dectectedBuffer)
// {
// ///缓存字符串的初始位置
// char* itor = (fastCheck[*ptext] & 0x01) == 0 ? ptext + 1 : ptext;
// ///缓存字符串的末尾位置
// char* end = ptext + text.Length;
// while (itor < end)
// {
// ///如果text的第一个词不是敏感词汇或者当前遍历到了text第一个词的后面的词则循环检测到text词汇的倒数第二个词看看这一段子字符串中有没有敏感词汇
// if ((fastCheck[*itor] & 0x01) == 0)
// {
// while (itor < end - 1 && (fastCheck[*(++itor)] & 0x01) == 0) ;
// }
// ///如果有只有一个词的敏感词,且当前的字符串的“非第一个词”满足这个敏感词,则先加入已检测到的敏感词列表
// if (startCache[*itor] != 0 && (fastLength[*itor] & 0x01) > 0)
// {
// return true;
// }
// char* strItor = detectedStrStart;
// *strItor++ = *itor;
// int remainLength = (int)(end - itor - 1);
// int skipCount = 0;
// ///此时已经检测到一个敏感词的“首词”了,记录下第一个检测到的敏感词的位置
// ///从当前的位置检测到字符串末尾
// for (int i = 1; i <= remainLength; ++i)
// {
// char* subItor = itor + i;
// /// 跳过一些过滤的字符,比如空格特殊符号之类的
// if (SkipBitArray[*subItor])
// {
// ++skipCount;
// continue;
// }
// ///如果检测到当前的词在所有敏感词中的位置信息中没有处在第i位的则马上跳出遍历
// if ((fastCheck[*subItor] >> Math.Min(i - skipCount, 7)) == 0)
// {
// break;
// }
// *strItor++ = *subItor;
// ///如果有检测到敏感词的最后一个词,并且此时的“检测到的敏感词汇”的长度也符合要求,则才进一步查看检测到的敏感词汇是否是真的敏感
// if ((fastLength[*itor] >> Math.Min(i - 1 - skipCount, 7)) > 0 && endCache[*subItor])
// {
// ///如果此子字符串在敏感词字典中存在则记录。做此判断是避免敏感词中夹杂了其他敏感词的单词而上面的算法无法剔除故先用hash数组来剔除
// ///上述算法是用于减少大部分的比较消耗
// if (wordsSet.Contains(new string(dectectedBuffer, 0, (int)(strItor - detectedStrStart))))
// {
// return true;
// }
// }
// else if (i - skipCount > startCache[*itor] && startCache[*itor] < 0x80)///如果超过了以该词为首的一系列的敏感词汇的最大的长度,则不继续判断(前提是该词对应的所有敏感词汇没有超过8个词的)
// {
// break;
// }
// }
// ++itor;
// }
// }
// return false;
//}
/// <summary>
/// 判断text是否有敏感词汇,如果有返回敏感的词汇的位置,利用指针操作来加快运算速度
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
unsafe public static Dictionary<int, int> DetectIllegalWords(string text)
{
var findResult = new Dictionary<int, int>();
if (string.IsNullOrEmpty(text)) return findResult;
if (EnsuranceLower(text)) text = text.ToLower();
int bufferLength = dectectedBuffer.Length;
if (text.Length > bufferLength) dectectedBuffer = new char[bufferLength << 1];
fixed (char* ptext = text, detectedStrStart = dectectedBuffer)
{
///缓存字符串的初始位置
char* itor = (fastCheck[*ptext] & 0x01) == 0 ? ptext + 1 : ptext;
///缓存字符串的末尾位置
char* end = ptext + text.Length;
while (itor < end)
{
///如果text的第一个词不是敏感词汇或者当前遍历到了text第一个词的后面的词则循环检测到text词汇的倒数第二个词看看这一段子字符串中有没有敏感词汇
if ((fastCheck[*itor] & 0x01) == 0)
{
while (itor < end - 1 && (fastCheck[*(++itor)] & 0x01) == 0) ;
}
///如果有只有一个词的敏感词,且当前的字符串的“非第一个词”满足这个敏感词,则先加入已检测到的敏感词列表
if (startCache[*itor] != 0 && (fastLength[*itor] & 0x01) > 0)
{
///返回敏感词在text中的位置以及敏感词的长度供过滤功能用
findResult.Add((int)(itor - ptext), 1);
}
char* strItor = detectedStrStart;
*strItor++ = *itor;
int remainLength = (int)(end - itor - 1);
int skipCount = 0;
///此时已经检测到一个敏感词的“首词”了,记录下第一个检测到的敏感词的位置
///从当前的位置检测到字符串末尾
for (int i = 1; i <= remainLength; ++i)
{
char* subItor = itor + i;
/// 跳过一些过滤的字符,比如空格特殊符号之类的
if (SkipBitArray[*subItor])
{
++skipCount;
continue;
}
///如果检测到当前的词在所有敏感词中的位置信息中没有处在第i位的则马上跳出遍历
if ((fastCheck[*subItor] >> Math.Min(i - skipCount, 7)) == 0)
{
break;
}
*strItor++ = *subItor;
///如果有检测到敏感词的最后一个词,并且此时的“检测到的敏感词汇”的长度也符合要求,则才进一步查看检测到的敏感词汇是否是真的敏感
if ((fastLength[*itor] >> Math.Min(i - 1 - skipCount, 7)) > 0 && endCache[*subItor])
{
///如果此子字符串在敏感词字典中存在则记录。做此判断是避免敏感词中夹杂了其他敏感词的单词而上面的算法无法剔除故先用hash数组来剔除
///上述算法是用于减少大部分的比较消耗
if (wordsSet.Contains(new string(dectectedBuffer, 0, (int)(strItor - detectedStrStart))))
{
int curDectectedStartIndex = (int)(itor - ptext);
findResult[curDectectedStartIndex] = i + 1;
itor = subItor;
break;
}
}
else if (i - skipCount > startCache[*itor] && startCache[*itor] < 0x80)///如果超过了以该词为首的一系列的敏感词汇的最大的长度,则不继续判断(前提是该词对应的所有敏感词汇没有超过8个词的)
{
break;
}
}
++itor;
}
}
return findResult;
}
}
}