文章目录
- 前言
- 读取方式
- 1. fmt.Scan
- 方法描述
- 代码解释
- 2. bufio.NewScanner(os.Stdin)
- 方法描述
- 代码解释
- 踩坑及解决方案
- 1. fmt.Scan 读取速度慢
- 问题描述
- 解决方案
- 2. bufio.NewScanner 的容量限制
- 问题描述
- 解决方案
- 代码解释
- 3. bufio.NewScanner 处理多数字输入繁琐
- 问题描述
- 解决方案
- 代码解释
- 踩坑
- 总结建议
- 1. 优先选择 bufio.NewScanner(os.Stdin)
- 2. 按需调整缓冲区大小
- 3. 灵活使用分割方式
- 最后
前言
你好,我是醉墨居士。在最近使用 Golang 刷牛客网算法题的过程中,处理标准输入时遇到了不少棘手的问题。这些问题一度让我的解题之路变得磕磕绊绊,但通过不断地探索和尝试,我成功找到了有效的解决方案。现在,我迫不及待地想把这些经验分享给大家,希望能帮助各位在使用 Golang 刷题时更加顺畅,少走一些弯路
读取方式
我们主要读取标准输入的方式可以分为以下两种:
1. fmt.Scan
方法描述
fmt.Scan 是 Go 语言标准库 fmt 包提供的一个输入函数,它以空白字符(如空格、制表符、换行符等)作为输入值的分隔符。以下是一个简单的示例,该示例从标准输入读取两个整数,并计算它们的和:
package mainimport ("fmt""io"
)func main() {var a, b int// 循环读取输入for {// 尝试读取两个整数到变量 a 和 b_, err := fmt.Scan(&a, &b)// 如果遇到文件结束符(EOF),则跳出循环if err == io.EOF {break}// 打印两个整数的和fmt.Println(a + b)}
}
代码解释
- 首先定义了两个整数变量 a 和 b,用于存储从标准输入读取的整数
- 使用 for 循环不断尝试读取输入,fmt.Scan(&a, &b) 会尝试从标准输入读取两个整数,并将它们分别赋值给 a 和 b
- 如果读取过程中遇到文件结束符(EOF),则 err 会等于 io.EOF,此时跳出循环
- 若读取成功,则打印 a 和 b 的和
2. bufio.NewScanner(os.Stdin)
方法描述
bufio.NewScanner(os.Stdin) 用于创建一个从标准输入读取数据的扫描器,默认情况下它按行读取输入。以下是一个示例,同样实现读取两个整数并计算它们的和:
package mainimport ("bufio""fmt""os""strconv""strings"
)func main() {var a, b int// 创建一个从标准输入读取数据的扫描器input := bufio.NewScanner(os.Stdin)// 循环读取每一行输入for input.Scan() {// 将当前行按空格分割成字符串切片parts := strings.Split(input.Text(), " ")// 将第一个字符串转换为整数并赋值给 aa, _ = strconv.Atoi(parts[0])// 将第二个字符串转换为整数并赋值给 bb, _ = strconv.Atoi(parts[1])// 打印两个整数的和fmt.Println(a + b)}
}
代码解释
- 首先定义了两个整数变量 a 和 b
- 使用 bufio.NewScanner(os.Stdin) 创建一个扫描器,用于从标准输入读取数据
- 通过 for input.Scan() 循环逐行读取输入,input.Text() 可以获取当前行的文本内容
- 使用 strings.Split(input.Text(), " ") 将当前行按空格分割成字符串切片
- 使用 strconv.Atoi() 函数将分割后的字符串转换为整数,并分别赋值给 a 和 b
- 最后打印 a 和 b 的和
踩坑及解决方案
在使用上述两种读取方式时,可能会遇到一些问题,下面将详细介绍这些问题及相应的解决方案
1. fmt.Scan 读取速度慢
问题描述
在一些算法题中,当算法的时间复杂度已经优化到极致时,使用 fmt.Scan() 读取输入数据仍然可能会导致超时。这是因为 fmt.Scan 在读取输入时会进行较多的格式化和解析操作,效率相对较低
解决方案
建议使用 bufio.NewScanner() 来替代 fmt.Scan。bufio.NewScanner() 采用了缓冲区机制,能够更高效地读取输入数据,从而避免因读取输入而导致的超时问题
2. bufio.NewScanner 的容量限制
问题描述
默认情况下,bufio.NewScanner 存在容量限制,其最大读取长度为 bufio.MaxScanTokenSize,即 64 * 1024 = 65536 个字节。如果读取的一行字符串的字节长度超过 65536,就会报错 bufio.Scanner: token too long,并且无法读取到完整的一行数据。
解决方案
可以使用 bufio.Scanner.Buffer() 方法来增加缓冲区的最大读取大小。以下是一个示例:
package mainimport ("bufio""fmt""os"
)func main() {input := bufio.NewScanner(os.Stdin)// 增加缓冲区大小为 1024 * 1024 字节buffer := make([]byte, 1024*1024)input.Buffer(buffer, 1024*1024)for input.Scan() {fmt.Println(input.Text())}
}
代码解释
- 首先创建一个 bufio.Scanner 对象
- 然后使用 make([]byte, 1024*1024) 创建一个大小为 1024 * 1024 字节的缓冲区
- 调用 input.Buffer(buffer, 1024*1024) 方法将缓冲区设置为 buffer,并将最大读取大小设置为 1024 * 1024 字节
- 最后通过循环逐行读取输入并打印
3. bufio.NewScanner 处理多数字输入繁琐
问题描述
对于某些题目,如果输入的某一行包含多个数字,且这些数字以空格分隔,使用 bufio.NewScanner 时需要先读取一行,然后使用 strings.Split 方法进行分割,再逐个将字符串转换为数字,操作相对繁琐
解决方案
可以使用 bufio.Scanner.Split(bufio.ScanWords) 方法让输入以空白字符(空格、制表符、换行符等)作为分隔符,这样可以简化输入数据的处理。以下是一个示例:
package mainimport ("bufio""fmt""os""strconv"
)func main() {input := bufio.NewScanner(os.Stdin)// 设置分割方式为按单词分割input.Split(bufio.ScanWords)for input.Scan() {num, _ := strconv.Atoi(input.Text())fmt.Println(num)}
}
代码解释
- 首先创建一个 bufio.Scanner 对象
- 调用 input.Split(bufio.ScanWords) 方法将分割方式设置为按空白字符进行分割
- 通过循环逐个读取单词,并将其转换为整数后打印
- 需要注意的是,如果题目的输入数据是以 , 或者其他非空白字符进行分隔的,就不能使用这种方式。不过这种情况出现的概率相对较低
踩坑
-
问题:使用fmt.Scan()读输入数据很慢,在某些题目中算法的时间复杂度已经优化到极致了,也会出现超时
解决方案:使用bufio.NewScanner() -
问题:使用默认的bufio.NewScanner()是存在容量限制的,bufio.MaxScanTokenSize = 64*1024 = 65536个,也就是说默认情况下只能读取65536个byte的长度,如果读取的一行字符串的字节长度超过65536,就会报错bufio.Scanner: token too long,并且也无法读取到完整的一行数据
解决方案:使用bufio.Scanner.Buffer() 增加缓冲区的最大读取大小 -
问题:使用bufio.NewScanner对于某些题来说,如果输入的某一行会有多个数字按照空格分隔,我们需要首先读取一行,然后string.Split,然后在逐个索引下标,然后在字符串转数字,操作还是比较繁琐的
解决方案:使用bufio.Scanner.Split(bufio.ScanWords) 让输入以空白字符(空格、制表符、换行符等)做为分隔,但请注意如果题目的输入数据是以 ‘,’ 或者其它非空白字符进行分隔的,就不能使用这种方式,不过这种情况出现概率会很低,至少我到现在还没有遇到过,如果不幸遇到了,那就没办法了,只能每次读取数据进行略微繁琐的预处理
总结建议
1. 优先选择 bufio.NewScanner(os.Stdin)
在处理标准输入时,建议优先使用 bufio.NewScanner(os.Stdin) 来读取数据。因为它比 fmt.Scan 更快,能够有效避免在一些题目中因读取输入而造成的超时问题
2. 按需调整缓冲区大小
根据题目的具体要求,如果一次读取的输入数据长度可能超过 65536 字节,就要使用 bufio.Scanner.Buffer() 方法来增加缓冲区的大小,防止溢出
3. 灵活使用分割方式
对于一些输入数据完全使用空白字符分隔的题目,可以使用 bufio.Scanner.Split(bufio.ScanWords) 方法以空白字符进行分隔,简化输入数据的处理
最后
希望通过以上的分享,能让大家在使用 Golang 刷算法题时更加得心应手。如果在实践过程中遇到任何问题,欢迎随时交流探讨。祝大家刷题顺利,早日攻克各种难题
如果你还有遇到其它类型的问题,欢迎在评论区进行留言