golang 亿行级文本文件中判断某字符串的出现次数

 经典问题读取一个文件内的字符串,判断子字符串出现在该文件内的次数:

  我这里的事例文件不是很大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)
		}
	}
}

算上读取文件的运算结果 七秒多:

不算读取文件的运行结果 三秒多:

可见读取文件占用的时间是比较长的,而且上述代码的可优化空间还是比较大的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值