初始化项目

This commit is contained in:
皮蛋13361098506
2025-01-06 16:01:02 +08:00
commit 1b77f62820
575 changed files with 69193 additions and 0 deletions

View File

@@ -0,0 +1,240 @@
package dfaExUtil
/*
* 扩展DFA算法
*
* 一种二层树实现类DFA算法DFA为多层树结构go语言特性中的map结构过于“重量级”导致内存占用很大此外还可能存在大量相同字符结点
*
* 第一层map为所有字母/汉字作为keyvalue为第二层map
* 第二层map为第一层冲突字母/汉字的自定义hash作为keyvalue指示是否为敏感词结束标识
*
* 测试结果50万+的敏感词:
* 构造树耗时稍优于原始DFA
* 内存使用为原DFA的不到1/4原DFA占用495M内存此算法使用111M
* 查询效率比原DFA低10%~20%左右主要是多一次map查询和多一次hash计算
*
*/
/*
* 注意使用[]rune的问题(此问题已通过使用固定位数hash解决)
* []rune中文使用的是unicode编码若“中”编码为#4E2D而#4E2D对应“N-”;
* 即:"N-"与"中"unicode编码均为#4E2D即会产生hash冲突
*
*/
import (
"fmt"
"strings"
hash "goutil/dfaExUtil/hash64"
)
// hash使用的类型(uint64对应hash64函数uint32对应hash32函数)
type hashType = uint64
type DFAEx struct {
// 忽略大小写true-忽略大小写false-大小写敏感
ignoreCase bool
// hash冲突个数
hashCollisions int
// 树根
// 字符/hash/uint8(b10000000)(最高字符表示是否结束/低7位表示字符位置)
root map[rune]map[hashType]uint8
}
// 新建敏感词对象
// wordList - 敏感词列表
// ignoreCase - [可选默认false] 是否忽略大小写
func NewDFAEx(wordList []string, ignoreCase ...bool) (dfaEx *DFAEx) {
var iCase bool
var mapSize int
if len(ignoreCase) > 0 {
iCase = ignoreCase[0]
}
mapSize = len(wordList) * 10
// 防止过小
if mapSize < 1_000 {
mapSize = 1_000
}
// 通常各语言的单rune非重复数不超过1万
if mapSize > 10_000 {
mapSize = 10_000
}
dfaEx = &DFAEx{
ignoreCase: iCase,
root: make(map[rune]map[hashType]uint8, mapSize),
}
for _, v := range wordList {
word := v
if iCase {
// 忽略大小写;所有字母转大写
word = strings.ToUpper(word)
}
wordRune := []rune(word)
if len(wordRune) > 0 {
dfaEx.InsertWord(wordRune)
}
}
return dfaEx
}
// 添加敏感词
func (dfaEx *DFAEx) InsertWord(word []rune) {
var hs hashType
var lastWord rune
var lastHash hashType
for i, v := range word {
lastWord = v
lastHash = hs
if wdInfo, ok := dfaEx.root[v]; ok {
// "字"已存在
if hsV, ok := wdInfo[hs]; !ok {
// hash不存在添加hash
wdInfo[hs] = uint8(i & 0x7F) // 第i位
} else {
// hash已存在检测是否冲突
if (hsV & 0x7F) != uint8(i) {
// hash冲突
dfaEx.hashCollisions++
// fmt.Printf("hash冲突 %s %016X %d %d\n", string(v), hs, i+1, hsV&0x7F+1)
}
}
} else {
// "字"不存在,添加"字"和hash
wdInfo = make(map[hashType]uint8)
wdInfo[hs] = uint8(i & 0x7F) // 第i位
dfaEx.root[v] = wdInfo
}
hs = hash.FastSumByRune2(v, hs) // hash更新
}
// 敏感词结束标志(uint8最高位置1)
dfaEx.root[lastWord][lastHash] |= 0x80
}
// 字符串查找敏感词
func (dfaEx *DFAEx) IsMatch(str string) bool {
starts, _ := dfaEx.SearchSentence(str, true)
return len(starts) > 0
}
// 指定字符替换敏感词
func (dfaEx *DFAEx) HandleWord(str string, replace rune) string {
starts, ends := dfaEx.SearchSentence(str)
if len(starts) == 0 {
return str
}
strRune := []rune(str)
for i := 0; i < len(starts); i++ {
for idx := starts[i]; idx <= ends[i]; idx++ {
strRune[idx] = replace
}
}
return string(strRune)
}
// 字符串查找敏感词
func (dfaEx *DFAEx) SearchSentence(str string, firstOpt ...bool) (starts, ends []int) {
var first bool // 是否首次匹配就返回
if len(firstOpt) > 0 {
first = firstOpt[0]
}
strBak := str
if dfaEx.ignoreCase {
// 忽略大小写;所有字母转大写
strBak = strings.ToUpper(str)
}
runeStr := []rune(strBak)
for i := 0; i < len(runeStr); {
end := dfaEx.searchByStart(i, runeStr)
if end < 0 {
// 继续下一个进行匹配
i++
} else {
// 记录匹配位置;从匹配到的下一个位置继续
starts = append(starts, i)
ends = append(ends, end)
if first {
// 首次匹配就返回
break
}
i = end + 1
}
}
return
}
// 从指定的开始位置搜索语句
// start - 开始匹配的位置
// str - 待检测字符串
// 返回:匹配到的结束位置,未匹配到返回-1
func (dfaEx *DFAEx) searchByStart(start int, runeStr []rune) (end int) {
var hs hashType
end = -1 // 未匹配到返回值
for i := start; i < len(runeStr); i++ {
wd := runeStr[i]
wdInfo, ok := dfaEx.root[wd]
if !ok {
// "字"不存在
break
}
hsV, ok := wdInfo[hs]
if !ok {
// hash不存在
break
}
// 检测是否句尾
if (hsV & 0x80) != 0 {
// 找到句尾,继续匹配,直到匹配到最长敏感词为止
end = i
}
hs = hash.FastSumByRune2(wd, hs) // hash更新
}
return
}
// 获取hash冲突数
func (dfaEx *DFAEx) GetHashCollisions() int {
return dfaEx.hashCollisions
}
// 调试接口
func (dfaEx *DFAEx) Print() {
fmt.Println(dfaEx)
}
// 调试接口
func (dfaEx *DFAEx) PrintFmt(verbose bool) {
var keys int
var hashs int
for k, v := range dfaEx.root {
keys++
if verbose {
fmt.Println("---------------------------")
fmt.Println(string(k))
}
for kk, vv := range v {
hashs++
if verbose {
fmt.Printf("%016X %02X\n", kk, vv)
}
}
}
fmt.Println("================================")
fmt.Println("keys:", keys, "hashs", hashs, "map count", keys+1, "hashCollisions Count", dfaEx.hashCollisions)
}

View File

@@ -0,0 +1,15 @@
package dfaExUtil
import "testing"
func TestHandleWord(t *testing.T) {
strs := []string{"ABC", "1234", "测试", "测试代码", "测试一下"}
dfaEx1 := NewDFAEx(strs)
str := dfaEx1.HandleWord("abc按了数字12345来测试代码是否正常结果测试出了bug", '*')
t.Log(str)
dfaEx2 := NewDFAEx(strs, true)
str = dfaEx2.HandleWord("abc按了数字12345来测试代码是否正常结果测试出了bug", '*')
t.Log(str)
}

View File

@@ -0,0 +1,98 @@
package hash32
/*
* ***注意***
*
* Sum 使用的是[]byte参数string中文是utf-8编码
* SumByRune 使用的是[]rune参数中文使用的是unicode编码
*
* 两种参数中文编码不同同一个string调用两个接口得到的hash是不同的 这是要特别注意的
*
* 对string进行for range操作会自动被[]rune化一定要注意
*
*/
const shiftBit = 11 // 每个字节移位数(测试经验值11)
const reverseBit = 32 - shiftBit
// 快速Hash计算(*** 注意只取了rune低16位进行hash计算计算结果与SumByRune不一致 ***)
func FastSumByRune2(in rune, hs uint32) (out uint32) {
out = ((hs << shiftBit) | (hs >> reverseBit)) + uint32(byte(in>>8))
out = ((out << shiftBit) | (out >> reverseBit)) + uint32(byte(in))
return
}
// 快速Hash计算(*** 此计算结果与SumByRune一致 ***)
func FastSumByRune4(in rune, hs uint32) (out uint32) {
out = ((hs << shiftBit) | (hs >> reverseBit)) + uint32(byte(in>>24))
out = ((out << shiftBit) | (out >> reverseBit)) + uint32(byte(in>>16))
out = ((out << shiftBit) | (out >> reverseBit)) + uint32(byte(in>>8))
out = ((out << shiftBit) | (out >> reverseBit)) + uint32(byte(in))
return
}
// 原Hash值参数重
func hsArg(hsOpt ...uint32) (out uint32) {
out = uint32(0)
if len(hsOpt) > 0 {
out = hsOpt[0]
}
return
}
// Hash计算
// in - 待hash串
// hsOpt - 原hash值(在此基础上继续hash)
func Sum(in []byte, hsOpt ...uint32) (out uint32) {
out = hsArg(hsOpt...)
for _, v := range in {
out = ((out << shiftBit) | (out >> reverseBit)) + uint32(v)
}
return
}
// Hash计算
func SumByRune(in rune, hsOpt ...uint32) (out uint32) {
// rune转[]byte
inVal := make([]byte, 4)
inVal[0] = byte(in >> 24)
inVal[1] = byte(in >> 16)
inVal[2] = byte(in >> 8)
inVal[3] = byte(in)
// *** 经实际测试:不加以下代码运行效率更高 ***
// 去除前面多余的\x00
// for {
// if len(inVal) <= 1 {
// // 以免全0异常至少要保留1位
// break
// }
// if inVal[0] == 0 {
// inVal = inVal[1:]
// } else {
// break
// }
// }
// 规避hash冲突"N-"与"中"unicode编码均为#4E2D即会产生hash冲突
// 若长度>1(即非常规ASCII)所有字节最高位置1
// if len(inVal) > 1 {
// for i := 0; i < len(inVal); i++ {
// inVal[i] |= 0x80
// }
// }
out = Sum(inVal, hsOpt...)
return
}
// Hash计算
func SumByRunes(in []rune, hsOpt ...uint32) (out uint32) {
out = hsArg(hsOpt...)
for _, v := range in {
out = SumByRune(v, out)
}
return
}

View File

@@ -0,0 +1,98 @@
package hash64
/*
* ***注意***
*
* Sum 使用的是[]byte参数string中文是utf-8编码
* SumByRune 使用的是[]rune参数中文使用的是unicode编码
*
* 两种参数中文编码不同同一个string调用两个接口得到的hash是不同的 这是要特别注意的
*
* 对string进行for range操作会自动被[]rune化一定要注意
*
*/
const shiftBit = 23 // 每个字节移位数(测试经验值23)
const reverseBit = 64 - shiftBit
// 快速Hash计算(*** 注意只取了rune低16位进行hash计算计算结果与SumByRune不一致 ***)
func FastSumByRune2(in rune, hs uint64) (out uint64) {
out = ((hs << shiftBit) | (hs >> reverseBit)) + uint64(byte(in>>8))
out = ((out << shiftBit) | (out >> reverseBit)) + uint64(byte(in))
return
}
// 快速Hash计算(*** 此计算结果与SumByRune一致 ***)
func FastSumByRune4(in rune, hs uint64) (out uint64) {
out = ((hs << shiftBit) | (hs >> reverseBit)) + uint64(byte(in>>24))
out = ((out << shiftBit) | (out >> reverseBit)) + uint64(byte(in>>16))
out = ((out << shiftBit) | (out >> reverseBit)) + uint64(byte(in>>8))
out = ((out << shiftBit) | (out >> reverseBit)) + uint64(byte(in))
return
}
// 原Hash值参数重
func hsArg(hsOpt ...uint64) (out uint64) {
out = uint64(0)
if len(hsOpt) > 0 {
out = hsOpt[0]
}
return
}
// Hash计算
// in - 待hash串
// hsOpt - 原hash值(在此基础上继续hash)
func Sum(in []byte, hsOpt ...uint64) (out uint64) {
out = hsArg(hsOpt...)
for _, v := range in {
out = ((out << shiftBit) | (out >> reverseBit)) + uint64(v)
}
return
}
// Hash计算
func SumByRune(in rune, hsOpt ...uint64) (out uint64) {
// rune转[]byte
inVal := make([]byte, 4)
inVal[0] = byte(in >> 24)
inVal[1] = byte(in >> 16)
inVal[2] = byte(in >> 8)
inVal[3] = byte(in)
// *** 经实际测试:不加以下代码运行效率更高 ***
// 去除前面多余的\x00
// for {
// if len(inVal) <= 1 {
// // 以免全0异常至少要保留1位
// break
// }
// if inVal[0] == 0 {
// inVal = inVal[1:]
// } else {
// break
// }
// }
// 规避hash冲突"N-"与"中"unicode编码均为#4E2D即会产生hash冲突
// 若长度>1(即非常规ASCII)所有字节最高位置1
// if len(inVal) > 1 {
// for i := 0; i < len(inVal); i++ {
// inVal[i] |= 0x80
// }
// }
out = Sum(inVal, hsOpt...)
return
}
// Hash计算
func SumByRunes(in []rune, hsOpt ...uint64) (out uint64) {
out = hsArg(hsOpt...)
for _, v := range in {
out = SumByRune(v, out)
}
return
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB