Golang CSV工具 - diffcsv的发布与使用

Golang CSV工具 - diffcsv的发布与使用 添加了一个用于比较两个CSV文件的差异工具。由于需要将两个输入文件都保留在内存中进行比较,该工具受限于内存大小。更多详情请参阅此处

目前命令行工具列表如下:

catcsv
comparecsv
dedupcsv
diffcsv
editcsv
obfuscatecsv
pivotcsv
recursedata
reordercsv
searchcsv
sortcsv
splitcsv
1 回复

更多关于Golang CSV工具 - diffcsv的发布与使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一个很有用的工具发布!diffcsv 作为 csv-utils 工具集的一部分,为 CSV 文件比较提供了专门解决方案。以下是对该工具的技术分析和使用示例:

技术实现分析

diffcsv 的核心逻辑是通过将两个 CSV 文件完全加载到内存中进行行级比较。这种设计确保了比较的准确性,但也确实带来了内存限制的挑战。

典型的实现架构如下:

package main

import (
    "encoding/csv"
    "fmt"
    "io"
    "os"
)

type CSVDiff struct {
    Added   [][]string
    Removed [][]string
    Changed [][]string
}

func CompareCSVFiles(file1, file2 string) (*CSVDiff, error) {
    // 读取第一个CSV文件到内存
    records1, err := readCSV(file1)
    if err != nil {
        return nil, err
    }
    
    // 读取第二个CSV文件到内存
    records2, err := readCSV(file2)
    if err != nil {
        return nil, err
    }
    
    diff := &CSVDiff{}
    
    // 构建记录映射以便快速查找
    map1 := make(map[string]bool)
    for _, record := range records1 {
        key := generateKey(record)
        map1[key] = true
    }
    
    map2 := make(map[string]bool)
    for _, record := range records2 {
        key := generateKey(record)
        map2[key] = true
        
        // 检查是否为新添加的记录
        if !map1[key] {
            diff.Added = append(diff.Added, record)
        }
    }
    
    // 检查被删除的记录
    for _, record := range records1 {
        key := generateKey(record)
        if !map2[key] {
            diff.Removed = append(diff.Removed, record)
        }
    }
    
    return diff, nil
}

func readCSV(filename string) ([][]string, error) {
    file, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    
    reader := csv.NewReader(file)
    var records [][]string
    
    for {
        record, err := reader.Read()
        if err == io.EOF {
            break
        }
        if err != nil {
            return nil, err
        }
        records = append(records, record)
    }
    
    return records, nil
}

func generateKey(record []string) string {
    // 简单的键生成逻辑,实际实现可能更复杂
    key := ""
    for _, field := range record {
        key += field + "|"
    }
    return key
}

使用示例

func main() {
    diff, err := CompareCSVFiles("file1.csv", "file2.csv")
    if err != nil {
        fmt.Printf("比较失败: %v\n", err)
        return
    }
    
    fmt.Printf("新增记录数: %d\n", len(diff.Added))
    fmt.Printf("删除记录数: %d\n", len(diff.Removed))
    
    // 输出具体差异
    if len(diff.Added) > 0 {
        fmt.Println("新增的记录:")
        for _, record := range diff.Added {
            fmt.Println(record)
        }
    }
    
    if len(diff.Removed) > 0 {
        fmt.Println("删除的记录:")
        for _, record := range diff.Removed {
            fmt.Println(record)
        }
    }
}

内存优化考虑

对于大文件场景,可以考虑流式处理方案:

func StreamCompareCSVFiles(file1, file2 string) error {
    f1, err := os.Open(file1)
    if err != nil {
        return err
    }
    defer f1.Close()
    
    f2, err := os.Open(file2)
    if err != nil {
        return err
    }
    defer f2.Close()
    
    reader1 := csv.NewReader(f1)
    reader2 := csv.NewReader(f2)
    
    lineNum := 1
    for {
        record1, err1 := reader1.Read()
        record2, err2 := reader2.Read()
        
        if err1 == io.EOF && err2 == io.EOF {
            break
        }
        
        if !equalRecords(record1, record2) {
            fmt.Printf("第%d行存在差异:\n", lineNum)
            fmt.Printf("文件1: %v\n", record1)
            fmt.Printf("文件2: %v\n", record2)
        }
        
        lineNum++
    }
    
    return nil
}

func equalRecords(r1, r2 []string) bool {
    if len(r1) != len(r2) {
        return false
    }
    for i := range r1 {
        if r1[i] != r2[i] {
            return false
        }
    }
    return true
}

diffcsv 工具在 csv-utils 生态中填补了 CSV 文件差异比较的空白,与其他工具如 comparecsv、sortcsv 等形成了完整的数据处理工具链。对于内存敏感的大文件场景,建议采用流式处理或分块比较的策略。

回到顶部