在 Golang 中统计文件行数可以通过多种方式实现,以下是几种常见方法及适用场景分析:
一、基础实现方案
方案1:逐行读取(适合小文件)
func CountLines(filePath string) (int, error) {
file, err := os.Open(filePath)
if err != nil {
return 0, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
count := 0
for scanner.Scan() {
count++
}
if err := scanner.Err(); err != nil {
return 0, err
}
return count, nil
}
特点:
- 内存友好(逐行处理)
- 自动处理各种换行符(\n/\r\n)
- 适合行数较少(<10万行)的场景
方案2:批量读取(适合大文件)
func CountLinesBulk(filePath string) (int, error) {
data, err := os.ReadFile(filePath)
if err != nil {
return 0, err
}
lines := strings.Split(string(data), "\n")
// 处理末尾可能的空行
if len(lines) > 0 && strings.TrimSpace(lines[len(lines)-1]) == "" {
lines = lines[:len(lines)-1]
}
return len(lines), nil
}
特点: 一次性读取文件,I/O次数少 内存消耗较高(需加载整个文件) 适合大文件(>100MB)场景
二、进阶优化方案
方案3:带缓冲的流式处理
func CountLinesBuffered(filePath string) (int, error) {
file, err := os.Open(filePath)
if err != nil {
return 0, err
}
defer file.Close()
buf := make([]byte, 4096)
count := 0
for {
n, err := file.Read(buf)
if err != nil && err != io.EOF {
return 0, err
}
for i := 0; i < n; {
if buf[i] == '\n' || buf[i] == '\r' {
count++
// 跳过可能的回车+换行组合
if i+1 < n && buf[i] == '\r' && buf[i+1] == '\n' {
i += 2
} else {
i += 1
}
} else {
i++
}
}
}
}
特点: 自定义缓冲区大小(默认4KB) 处理所有换行符变体(\n/\r/\r\n) 内存消耗可控,适合超大文件
四、特殊场景处理
1.超时控制(结合Context):
func CountLinesWithTimeout(filePath string, timeout time.Duration) (int, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
file, err := os.Open(filePath)
if err != nil {
return 0, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
count := 0
for {
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
if scanner.Scan() {
count++
} else {
break
}
}
}
return count, scanner.Err()
}
2.大文件分片处理(适合超过内存限制的场景):
func CountLinesInChunks(filePath string, chunkSize int) (int, error) {
file, err := os.Open(filePath)
if err != nil {
return 0, err
}
defer file.Close()
count := 0
offset := 0
for {
buf := make([]byte, chunkSize)
n, err := file.ReadAt(buf, offset)
if err == io.EOF {
break
}
if err != nil {
return 0, err
}
count += countNewlines(buf)
offset += n
}
return count, nil
}
func countNewlines(buf []byte) int {
count := 0
for i := 0; i < len(buf); i++ {
if buf[i] == '\n' || buf[i] == '\r' {
count++
// 跳过回车+换行组合
if i+1 < len(buf) && buf[i] == '\r' && buf[i+1] == '\n' {
i++
}
}
}
return count
}