经典问题读取一个文件内的字符串,判断子字符串出现在该文件内的次数:
我这里的事例文件不是很大1.6G,仅仅作为示范
github:https://github.com/zhumengyifang/goapp/blob/master/demo/src/main/foo.go
KMP算法进行判断:
func arithmeticKmp(haystack string, needle string) int {
index := -1
if tableValue == nil {
tableValue = getPartialMatchTable(needle)
}
i, j := 0, 0
for ; i < len(haystack) && j < len(needle); {
if haystack[i] == needle[j] {
if j == 0 {
index = i
} //记录第一个匹配字符的索引
j++
i++
} else {
if j == 0 {
i = i + j + 1 - tableValue[j] //移动位数 =已匹配的字符数 - 对应的部分匹配值
} else {
i = index + j - tableValue[j-1] //如果已匹配的字符数不为零,则重新定义i迭代
}
j = 0 //将已匹配迭代置为0
index = -1
}
}
return index
}
var tableValue []int
func getPartialMatchTable(str string) []int {
var left, right []string //前缀、后缀
n := len([]rune(str))
var result = make([]int, n)
for i := 0; i < n; i++ {
left = make([]string, i) //实例化前缀 容器
right = make([]string, i) //实例化后缀容器
//前缀
for j := 0; j < i; j++ {
if j == 0 {
left[j] = string(str[j])
} else {
left[j] = left[j-1] + string(str[j])
}
}
//后缀
for k := i; k > 0; k-- {
if k == i {
right[k-1] = string(str[k])
} else {
right[k-1] = string(str[k]) + right[k]
}
}
//找到前缀和后缀中相同的项,长度即为相等项的长度(相等项应该只有一项)
num := len(left) - 1
for m := 0; m < len(left); m++ {
if right[num] == left[m] {
result[i] = len(left[m])
}
num--
}
}
return result
}
判断[]byte 2048内字符串的子字符串数量:
func GetCount(long string, short string, ch chan int) {
if len(short) > len(long) {
ch <- 0
}
num := 0
for ; len(long) > len(short); {
index := arithmeticKmp(long, short)
if index == -1 {
break
}
num++
if index+len(short) > len(long) {
break
}
long = long[index+len(short):]
}
ch <- num
}
以上代码就可以完成计算子字符串出现在主串中出现的次数。为了追求速度使用goroutine进行多行并行运算,在通过channel将结果汇总。
读取文件1.6G的文件这里占用的时间是比较长的:
func readFile(path *string) *[]string {
fi, err := os.Open(*path)
if err != nil {
panic(err)
}
defer fi.Close()
var strs []string
buf := make([]byte, 2048)
for i := 0; ; i++ {
n, _ := fi.Read(buf)
if n == 0 {
break
}
strs = append(strs, string(buf[:n]))
}
return &strs
}
main方法:
func main() {
path := "/Users/jarvis/desktop/textfile.txt"
longs := readFile(&path)
short:="hello"
fmt.Println("开始计时!")
t1 := time.Now()
ch := make(chan int, 100)
request := make(chan int32)
for _,long:= range *longs {
go GetCount(long, short, ch)
}
go getResult(ch, request)
fmt.Println(<-request)
elapsed := time.Since(t1)
fmt.Println("App elapsed: ", elapsed)
}
结果汇总:
func getResult(ch chan int, request chan int32) {
time.Sleep(time.Second)
var num int32 = 0
for {
select {
case t := <-ch:
atomic.AddInt32(&num, int32(t))
default:
request <- atomic.LoadInt32(&num)
}
}
}
算上读取文件的运算结果 七秒多:
不算读取文件的运行结果 三秒多:
可见读取文件占用的时间是比较长的,而且上述代码的可优化空间还是比较大的