对象存储路径文件1TB以上文件比对,go语言

目标:有2个的文件,每个有3TB左右,每一行代表一个文件,现在需要对比2个文件中有哪些不一致。源端3TB,目标端3TB,需要找出源端中存在,但是目标端不存在的数据。

红色代表源端,橙色代表目标端,找到箭头所指的虚线部分

go的编译

go build -o 【输出文件名称】 【go语言路径.go】

  1. 对原端和目标端文件进行检测,看看是否有序 源代码
Go 复制代码
package main

import (
        "bufio"
        "fmt"
        "os"
        "strings"
)

// checkFileOrdered 快速检测文件有序性(字典序升序)
// 检测到1条无序记录立即返回,极低内存占用,支持1TB+超大文件
func checkFileOrdered(filePath string) (
        isOrdered bool,
        prevLine string,   // 无序的上一行内容
        currentLine string, // 无序的当前行内容
        prevLineNum int,   // 无序的上一行号
        currentLineNum int, // 无序的当前行号
        err error,
) {
        // 以只读模式打开文件,优化超大文件读取性能
        file, err := os.OpenFile(filePath, os.O_RDONLY, 0444)
        if err != nil {
                return false, "", "", 0, 0, fmt.Errorf("打开文件失败: %w", err)
        }
        defer file.Close() // 延迟关闭文件,避免资源泄露

        // 配置4MB大缓冲区,减少系统调用,提升超大文件读取效率
        bufSize := 4 * 1024 * 1024
        scanner := bufio.NewScanner(file)
        buf := make([]byte, bufSize)
        scanner.Buffer(buf, bufSize)

        var (
                previousLine string
                lineCount    int // 有效行号计数器
                firstValid   bool = true // 是否是第一个有效行
        )

        // 逐行扫描,不加载全文件,内存占用可控
        for scanner.Scan() {
                rawLine := scanner.Text()
                // 去除行首尾空白字符,确保比较准确性(可按需删除此行)
                processedLine := strings.TrimSpace(rawLine)

                // 跳过空行(有效行不包含空行,可按需删除此判断)
                if processedLine == "" {
                        continue
                }

                lineCount++ // 有效行号递增
                // 第一行有效行,仅记录不比较
                if firstValid {
                        previousLine = processedLine
                        firstValid = false
                        continue
                }

                // 字符串字典序比较,检测是否无序(升序规则)
                if strings.Compare(previousLine, processedLine) > 0 {
                        // 检测到无序,立即返回结果,终止后续扫描
                        return false, previousLine, processedLine, lineCount - 1, lineCount, nil
                }

                // 有序则更新上一行内容,继续扫描
                previousLine = processedLine
        }

        // 检查扫描过程中的IO错误
        if err := scanner.Err(); err != nil {
                return false, "", "", 0, 0, fmt.Errorf("读取文件失败: %w", err)
        }

        // 遍历完成未检测到无序,说明文件有序
        return true, "", "", 0, 0, nil
}

func main() {
        // 检查命令行参数数量,确保传入了文件名称
        if len(os.Args) < 2 {
                fmt.Println("使用方法:./check <文件名称>")
                fmt.Println("示例:./check source.txt")
                os.Exit(1) // 参数不足,异常退出
        }

        // 获取命令行传入的文件名称(os.Args[0]是程序名,os.Args[1]是第一个参数即文件名称)
        fileName := os.Args[1]
        fmt.Printf("开始检测文件「%s」的有序性(缓冲区大小:4MB)\n", fileName)

        // 执行有序性检测
        isOrdered, prevLine, currLine, prevNum, currNum, err := checkFileOrdered(fileName)
        if err != nil {
                fmt.Printf("检测失败:%v\n", err)
                os.Exit(1)
        }

        // 输出检测结果
        if isOrdered {
                fmt.Printf("检测完成:文件「%s」所有有效行均按字典序升序排列,无无序记录\n", fileName)
                return
        }

        // 检测到无序,输出详情并立即退出
        fmt.Printf("检测到无序记录,立即退出!\n")
        fmt.Printf("第%d行:「%s」\n", prevNum, prevLine)
        fmt.Printf("第%d行:「%s」\n", currNum, currLine)
        fmt.Printf("问题:第%d行字典序大于第%d行,违反升序排列规则\n", prevNum, currNum)
        os.Exit(1)
}

使用:

./check_sort 【文件路径】

2.若无序则进行排序(多路归并) 源代码

注意

// 每个临时小文件的最大行数(可调整:内存越大,该值可设越大,效率越高)800w行分一个文件,可以修改

根据自己的改动,800w的文件大小应该小于可用内存值,并且留20%的冗余

Go 复制代码
package main

import (
        "bufio"
        "container/heap"
        "flag"
        "fmt"
        "os"
        "path/filepath"
        "sort"
        "strings"
        "sync"
)

// 配置项(可根据你的机器内存调整)
const (
        // 每个临时小文件的最大行数(可调整:内存越大,该值可设越大,效率越高)800w行分一个文件,可以修改
        maxTempFileLines = 80000000
        // 临时文件前缀(用于后续清理和识别)
        tempFilePrefix = "tmp_file_sort_"
)

// 命令行参数
var (
        inputFileName  string
        outputFileName string
        tempDir        string // 新增:临时文件目录参数
)

// 优先级队列元素:用于多路归并
type HeapElement struct {
        line    string        // 行内容
        fileIdx int           // 对应临时文件的索引
        scanner *bufio.Scanner // 对应临时文件的扫描器
        file    *os.File      // 保存文件句柄,用于后续关闭
        valid   bool          // 该元素是否有效(是否还有未读取的行)
}

// 优先级队列(最小堆):按行内容字典序排序
type PriorityQueue []*HeapElement

func (pq PriorityQueue) Len() int { return len(pq) }

func (pq PriorityQueue) Less(i, j int) bool {
        // 按UTF-8字典序比较,保留特殊字符排序规则
        return pq[i].line < pq[j].line
}

func (pq PriorityQueue) Swap(i, j int) {
        pq[i], pq[j] = pq[j], pq[i]
}

func (pq *PriorityQueue) Push(x interface{}) {
        elem := x.(*HeapElement)
        *pq = append(*pq, elem)
}

func (pq *PriorityQueue) Pop() interface{} {
        old := *pq
        n := len(old)
        elem := old[n-1]
        *pq = old[0 : n-1]
        return elem
}

func init() {
        // 解析命令行参数
        flag.StringVar(&inputFileName, "f", "", "输入文件名称(必填,支持1TB超大文件)")
        flag.StringVar(&outputFileName, "o", "", "输出文件名称(可选,默认:原文件名_sorted.后缀)")
        flag.StringVar(&tempDir, "t", "", "临时文件存储目录(可选,默认当前工作目录)") // 新增:-t 参数指定临时目录
        flag.Parse()

        // 兼容直接传递输入文件名的格式(./程序名 输入文件名)
        if inputFileName == "" && len(flag.Args()) > 0 {
                inputFileName = flag.Args()[0]
        }

        // 参数校验
        if inputFileName == "" {
                fmt.Fprintf(os.Stderr, "使用方法1:%s 输入文件名 [输出文件名]\n", os.Args[0])
                fmt.Fprintf(os.Stderr, "使用方法2:%s -f 输入文件名 -o 输出文件名 -t 临时文件目录\n", os.Args[0])
                fmt.Fprintf(os.Stderr, "参数说明:-t 指定临时文件存放路径,若不指定则使用当前工作目录\n")
                os.Exit(1)
        }

        // 自动生成输出文件名(若未指定)
        if outputFileName == "" {
                lastDotIndex := strings.LastIndex(inputFileName, ".")
                if lastDotIndex == -1 {
                        outputFileName = inputFileName + "_sorted"
                } else {
                        outputFileName = inputFileName[:lastDotIndex] + "_sorted" + inputFileName[lastDotIndex:]
                }
        }

        // 可选:校验指定的临时目录是否存在(若用户指定了临时目录)
        if tempDir != "" {
                info, err := os.Stat(tempDir)
                if err != nil {
                        // 目录不存在,报错并提示
                        fmt.Fprintf(os.Stderr, "指定的临时目录 %s 不存在,请先创建该目录\n", tempDir)
                        os.Exit(1)
                }
                if !info.IsDir() {
                        // 路径存在,但不是目录
                        fmt.Fprintf(os.Stderr, "指定的路径 %s 不是一个有效目录\n", tempDir)
                        os.Exit(1)
                }
        }
}

func main() {
        // 步骤1:拆分大文件为多个排序后的临时小文件
        tempFilePaths, err := splitAndSortLargeFile(inputFileName)
        if err != nil {
                fmt.Fprintf(os.Stderr, "拆分并排序大文件失败:%v\n", err)
                cleanTempFiles(tempFilePaths) // 清理临时文件
                os.Exit(1)
        }
        defer cleanTempFiles(tempFilePaths) // 程序结束后自动清理临时文件

        // 步骤2:多路归并所有临时小文件,生成最终有序文件
        err = mergeSortedTempFiles(tempFilePaths, outputFileName)
        if err != nil {
                fmt.Fprintf(os.Stderr, "归并临时文件失败:%v\n", err)
                cleanTempFiles(tempFilePaths)
                os.Exit(1)
        }

        fmt.Printf("超大文件排序完成!\n原文件:%s\n输出文件:%s\n临时文件目录:%s\n临时文件已自动清理\n",
                inputFileName, outputFileName, getTempDirDisplay())
}

// getTempDirDisplay 获取临时目录显示字符串(兼容默认目录)
func getTempDirDisplay() string {
        if tempDir != "" {
                return tempDir
        }
        // 获取当前工作目录(用于显示)
        wd, err := os.Getwd()
        if err != nil {
                return "当前工作目录(获取失败)"
        }
        return wd
}

// splitAndSortLargeFile 拆分大文件为多个排序后的临时小文件
func splitAndSortLargeFile(inputFile string) ([]string, error) {
        var tempFilePaths []string
        file, err := os.Open(inputFile)
        if err != nil {
                return nil, err
        }
        defer file.Close()

        scanner := bufio.NewScanner(file)
        var lines []string
        tempFileIdx := 0

        // 逐行读取,积累到指定行数后生成排序后的临时文件
        for scanner.Scan() {
                lines = append(lines, scanner.Text())

                // 达到临时文件最大行数,生成并排序临时文件
                if len(lines) >= maxTempFileLines {
                        tempFilePath, err := createSortedTempFile(lines, tempFileIdx)
                        if err != nil {
                                return tempFilePaths, err
                        }
                        tempFilePaths = append(tempFilePaths, tempFilePath)
                        // 重置切片,继续积累下一批行
                        lines = lines[:0]
                        tempFileIdx++
                }
        }

        // 处理最后一批不足maxTempFileLines的行
        if len(lines) > 0 {
                tempFilePath, err := createSortedTempFile(lines, tempFileIdx)
                if err != nil {
                        return tempFilePaths, err
                }
                tempFilePaths = append(tempFilePaths, tempFilePath)
        }

        // 检查扫描错误
        if err := scanner.Err(); err != nil {
                return tempFilePaths, err
        }

        return tempFilePaths, nil
}

// createSortedTempFile 对行切片排序并写入临时文件(支持自定义临时目录)
func createSortedTempFile(lines []string, idx int) (string, error) {
        // 排序行(内存排序,此时lines大小在内存承受范围内)
        sort.Strings(lines)

        // 拼接临时文件路径(兼容自定义临时目录)
        var tempFilePath string
        if tempDir != "" {
                // 使用 filepath.Join 自动适配 Windows(\)和 Linux/Mac(/)的路径分隔符
                tempFileName := fmt.Sprintf("%s%d.tmp", tempFilePrefix, idx)
                tempFilePath = filepath.Join(tempDir, tempFileName)
        } else {
                // 未指定临时目录,使用当前工作目录
                tempFilePath = fmt.Sprintf("%s%d.tmp", tempFilePrefix, idx)
        }

        // 创建临时文件
        file, err := os.Create(tempFilePath)
        if err != nil {
                return "", err
        }
        defer file.Close()

        writer := bufio.NewWriter(file)
        defer writer.Flush()

        // 写入排序后的行
        for _, line := range lines {
                _, err := writer.WriteString(line + "\n")
                if err != nil {
                        os.Remove(tempFilePath) // 写入失败,删除临时文件
                        return "", err
                }
        }

        return tempFilePath, nil
}

// mergeSortedTempFiles 多路归并所有排序后的临时文件
func mergeSortedTempFiles(tempFilePaths []string, outputFile string) error {
        if len(tempFilePaths) == 0 {
                return fmt.Errorf("无临时文件可归并")
        }

        // 打开所有临时文件,并初始化扫描器和文件句柄
        var heapElements []*HeapElement
        for idx, path := range tempFilePaths {
                // 1. 打开文件,保存文件句柄
                file, err := os.Open(path)
                if err != nil {
                        return err
                }
                // 2. 初始化扫描器
                scanner := bufio.NewScanner(file)
                // 3. 读取每个临时文件的第一行
                if scanner.Scan() {
                        elem := &HeapElement{
                                line:    scanner.Text(),
                                fileIdx: idx,
                                scanner: scanner,
                                file:    file,
                                valid:   true,
                        }
                        heapElements = append(heapElements, elem)
                } else {
                        file.Close() // 无数据,直接关闭文件
                        if err := scanner.Err(); err != nil {
                                return err
                        }
                }
        }

        // 初始化优先级队列(最小堆)
        pq := &PriorityQueue{}
        heap.Init(pq)
        for _, elem := range heapElements {
                heap.Push(pq, elem)
        }

        // 创建输出文件
        outFile, err := os.Create(outputFile)
        if err != nil {
                return err
        }
        defer outFile.Close()

        writer := bufio.NewWriter(outFile)
        defer writer.Flush()

        // 多路归并核心逻辑:不断弹出堆顶最小元素,写入输出文件,再从对应文件读取下一行入堆
        for pq.Len() > 0 {
                // 弹出堆顶最小元素
                topElem := heap.Pop(pq).(*HeapElement)
                if !topElem.valid {
                        continue
                }

                // 写入输出文件
                _, err := writer.WriteString(topElem.line + "\n")
                if err != nil {
                        return err
                }

                // 从当前临时文件读取下一行
                scanner := topElem.scanner
                if scanner.Scan() {
                        // 更新元素内容,重新入堆
                        topElem.line = scanner.Text()
                        heap.Push(pq, topElem)
                } else {
                        // 该临时文件已读取完毕,关闭文件(使用保存的 file 句柄)
                        if topElem.file != nil {
                                topElem.file.Close()
                        }
                        // 检查扫描错误
                        if err := scanner.Err(); err != nil {
                                return err
                        }
                }
        }

        return nil
}

// cleanTempFiles 清理所有临时文件
func cleanTempFiles(tempFilePaths []string) {
        var wg sync.WaitGroup
        for _, path := range tempFilePaths {
                wg.Add(1)
                go func(p string) {
                        defer wg.Done()
                        if err := os.Remove(p); err != nil {
                                fmt.Fprintf(os.Stderr, "清理临时文件 %s 失败:%v\n", p, err)
                        }
                }(path)
        }
        wg.Wait()
}

使用:

nohup exe_sort_bigdata -f 【待排序文件地址】 -o 【输出文件地址】 -t 【磁盘存储临时文件地址】 &

3.比对程序(双指针)

原数据例子是

aaaaa/asadad/adadasd.txt,1300000

bbb/asdad/asdggbg.txt,1231233

类似这种,前面是文件路径,后面是文件更新的时间戳。

本次会判断文件路径相等 ,且源端时间戳小于目标端时间戳 。作为正确的一行。否则会被输出到2个不同的文件,文件路径不同的文件,和文件路径相同但是时间戳不同的文件

ps:如果源端时间大于目标,那就说明是源端更新了数据,但是目标端用的是旧的数据。

Go 复制代码
package main

import (
	"bufio"
	"fmt"
	"os"
	"runtime"
	"strconv"
	"strings"
	"sync"
)

// FileData 存储文件名和时间戳(完全保留原有结构体)
type FileData struct {
	Name      string
	Timestamp int64
}

// 定义流式读取的行数据通道(用于传递单行文本质,不缓存全量数据)
type LineData struct {
	FileData FileData
	Err      error
	Done     bool // 标记当前文件读取完成
}

// 流式解析已排序的文件(进一步优化内存占用,无全量缓存)
func streamParseSortedFile(filePath string, lineChan chan<- LineData, wg *sync.WaitGroup) {
	defer func() {
		lineChan <- LineData{Done: true} // 发送读取完成标记
		wg.Done()
		// 主动触发垃圾回收,释放当前协程内存
		runtime.GC()
	}()

	f, err := os.Open(filePath)
	if err != nil {
		lineChan <- LineData{Err: fmt.Errorf("打开%s失败: %v", filePath, err)}
		return
	}
	defer f.Close()

	// 缩小读取缓冲区:从1MB改为256KB,进一步降低内存占用
	scanner := bufio.NewScanner(f)
	buf := make([]byte, 256*1024) // 256KB缓冲区,足够高效读取且内存友好
	scanner.Buffer(buf, 256*1024)

	// 复用临时变量:避免循环内重复创建变量导致内存累积
	var (
		line  string
		parts []string
		ts    int64
	)

	for scanner.Scan() {
		line = strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}

		// 分割文件名和时间戳(原有逻辑不变,复用parts切片)
		parts = strings.SplitN(line, ",", 2)
		if len(parts) != 2 {
			fmt.Printf("警告:%s 无效行,跳过 -> %s\n", filePath, line)
			// 重置parts,避免残留数据占用内存
			parts = nil
			continue
		}

		// 转换时间戳(原有逻辑不变,复用ts变量)
		ts, err = strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64)
		if err != nil {
			fmt.Printf("警告:%s 无效时间戳,跳过 -> %s\n", filePath, line)
			// 重置parts和ts,释放临时内存
			parts = nil
			ts = 0
			continue
		}

		// 逐行发送数据,发送后立即重置临时变量
		lineChan <- LineData{
			FileData: FileData{
				Name:      strings.TrimSpace(parts[0]),
				Timestamp: ts,
			},
		}

		// 主动重置临时变量,避免内存泄漏和累积
		parts = nil
		ts = 0
		line = ""
	}

	if err := scanner.Err(); err != nil {
		lineChan <- LineData{Err: err}
	}

	// 再次触发垃圾回收
	runtime.GC()
}

// 双指针对比核心逻辑(完全保留业务逻辑,优化内存占用)
func compareAndExport(sourcePath, targetPath, outputPrefix string) {
	// 严格限制CPU核心数≤12
	runtime.GOMAXPROCS(12)

	// 关键优化:缩小通道缓冲区(从100改为20),大幅降低内存占用
	// 通道缓冲区越小,缓存的行数据越少,内存占用越低
	sourceChan := make(chan LineData, 20)
	targetChan := make(chan LineData, 20)
	var wg sync.WaitGroup

	// 启动协程流式解析文件(无全量内存加载)
	wg.Add(2)
	go streamParseSortedFile(sourcePath, sourceChan, &wg)
	go streamParseSortedFile(targetPath, targetChan, &wg)

	// 拼接输出文件名(原有逻辑不变)
	valueErrFileName := fmt.Sprintf("%s_value_mismatch.txt", outputPrefix)
	timeErrFileName := fmt.Sprintf("%s_time_mismatch.txt", outputPrefix)

	// 创建输出文件(原有逻辑不变)
	valueErrFile, err := os.Create(valueErrFileName)
	if err != nil {
		fmt.Printf("创建值差异文件失败: %v\n", err)
		return
	}
	defer valueErrFile.Close()
	timeErrFile, err := os.Create(timeErrFileName)
	if err != nil {
		fmt.Printf("创建时间差异文件失败: %v\n", err)
		return
	}
	defer timeErrFile.Close()

	// 缩小写入缓冲区:从1MB改为256KB,降低内存占用
	valueWriter := bufio.NewWriterSize(valueErrFile, 256*1024)
	defer valueWriter.Flush()
	timeWriter := bufio.NewWriterSize(timeErrFile, 256*1024)
	defer timeWriter.Flush()

	// 流式数据变量(仅缓存单条数据,无批量缓存)
	var (
		srcData   FileData
		tgtData   FileData
		hasSrc    bool
		hasTgt    bool
		srcDone   bool
		tgtDone   bool
		srcErr    error
		tgtErr    error
	)

	// 预读取第一行数据(原有逻辑不变)
	select {
	case line := <-sourceChan:
		if line.Err != nil {
			srcErr = line.Err
		} else if !line.Done {
			srcData = line.FileData
			hasSrc = true
		} else {
			srcDone = true
		}
	}
	select {
	case line := <-targetChan:
		if line.Err != nil {
			tgtErr = line.Err
		} else if !line.Done {
			tgtData = line.FileData
			hasTgt = true
		} else {
			tgtDone = true
		}
	}

	// 处理解析错误(原有逻辑不变)
	if srcErr != nil {
		fmt.Printf("解析源文件失败: %v\n", srcErr)
		return
	}
	if tgtErr != nil {
		fmt.Printf("解析目标文件失败: %v\n", tgtErr)
		return
	}

	// 双指针流式遍历(完全保留业务逻辑,无内存累积)
	for !srcDone || !tgtDone || hasSrc || hasTgt {
		// 补充未读取的数据
		if !hasSrc && !srcDone {
			select {
			case line := <-sourceChan:
				if line.Err != nil {
					srcErr = line.Err
					srcDone = true
				} else if !line.Done {
					srcData = line.FileData
					hasSrc = true
				} else {
					srcDone = true
				}
			}
		}
		if !hasTgt && !tgtDone {
			select {
			case line := <-targetChan:
				if line.Err != nil {
					tgtErr = line.Err
					tgtDone = true
				} else if !line.Done {
					tgtData = line.FileData
					hasTgt = true
				} else {
					tgtDone = true
				}
			}
		}

		// 处理错误(原有逻辑不变)
		if srcErr != nil {
			fmt.Printf("解析源文件失败: %v\n", srcErr)
			return
		}
		if tgtErr != nil {
			fmt.Printf("解析目标文件失败: %v\n", tgtErr)
			return
		}

		// 双指针对比逻辑(完全和原有一致,无业务修改)
		switch {
		// 1. 两个文件都有数据可对比
		case hasSrc && hasTgt:
			src := srcData
			tgt := tgtData

			switch {
			// 文件名相同:对比时间戳
			case src.Name == tgt.Name:
				if src.Timestamp > tgt.Timestamp {
					line := fmt.Sprintf("%s,%d | target时间戳: %d\n", src.Name, src.Timestamp, tgt.Timestamp)
					_, err := timeWriter.WriteString(line)
					if err != nil {
						fmt.Printf("写入时间差异文件失败: %v\n", err)
						return
					}
					// 写入后立即刷新(可选,避免缓冲区累积过多数据)
					_ = timeWriter.Flush()
				}
				// 释放当前数据,重置标记
				srcData = FileData{} // 主动清空,释放内存
				tgtData = FileData{}
				hasSrc = false
				hasTgt = false

			// source文件名更小:写入值差异文件
			case src.Name < tgt.Name:
				line := fmt.Sprintf("%s,%d\n", src.Name, src.Timestamp)
				_, err := valueWriter.WriteString(line)
				if err != nil {
					fmt.Printf("写入值差异文件失败: %v\n", err)
					return
				}
				// 写入后立即刷新(可选)
				_ = valueWriter.Flush()
				// 释放当前数据,重置标记
				srcData = FileData{}
				hasSrc = false

			// target文件名更小:忽略
			default:
				// 释放当前数据,重置标记
				tgtData = FileData{}
				hasTgt = false
			}

		// 2. 仅source有剩余数据:写入值差异文件
		case hasSrc && (!srcDone || tgtDone):
			line := fmt.Sprintf("%s,%d\n", srcData.Name, srcData.Timestamp)
			_, err := valueWriter.WriteString(line)
			if err != nil {
				fmt.Printf("写入值差异文件失败: %v\n", err)
				return
			}
			_ = valueWriter.Flush()
			// 释放当前数据
			srcData = FileData{}
			hasSrc = false

		// 3. 仅target有剩余数据:忽略
		case hasTgt && (!tgtDone || srcDone):
			// 释放当前数据
			tgtData = FileData{}
			hasTgt = false

		// 4. 无数据可处理,退出循环
		default:
			break
		}

		// 循环内定期触发垃圾回收,防止内存累积
		runtime.GC()
	}

	// 等待协程结束,关闭通道
	wg.Wait()
	close(sourceChan)
	close(targetChan)

	// 最终触发一次垃圾回收
	runtime.GC()

	fmt.Printf("对比完成!\n")
	fmt.Printf("值不符合(source有,target无):%s\n", valueErrFileName)
	fmt.Printf("时间不符合(source时间 > target时间):%s\n", timeErrFileName)
}

func main() {
	// 检查命令行参数(原有逻辑不变)
	if len(os.Args) != 4 {
		fmt.Println("使用方法:./compare [source.txt] [target.txt] [输出前缀]")
		fmt.Println("示例:./compare source.txt target.txt rs")
		return
	}

	// 强制触发一次初始垃圾回收,释放启动时的冗余内存
	runtime.GC()

	// 获取参数并执行对比
	sourceFile := os.Args[1]
	targetFile := os.Args[2]
	outputPrefix := os.Args[3]
	compareAndExport(sourceFile, targetFile, outputPrefix)
}

使用:

./diff_tos2 【源端文件】 【目标端文件】 【结果输出前缀】

相关推荐
CocoaKier1 天前
苹果谷歌商店:如何监控并维护用户评分评论
ios·google·apple
iOS日常1 天前
iOS设备崩溃日志获取与查看
ios·xcode
wangruofeng1 天前
AI 助力 Flutter 3.27 升级到 3.38 完整指南:两周踩坑与实战复盘
flutter·ios·ai编程
iOS日常2 天前
Xcode 垃圾清理
ios·xcode
开心就好20252 天前
不越狱能抓到 HTTPS 吗?在未越狱 iPhone 上抓取 HTTPS
后端·ios
傅里叶2 天前
iOS相机权限获取
flutter·ios
花酒锄作田3 天前
Gin 框架中的规范响应格式设计与实现
golang·gin
zhangkai3 天前
flutter存储知识点总结
flutter·ios
齐生13 天前
网络知识点 - TCP/IP 四层模型知识大扫盲
笔记·ios
IT技术分享社区3 天前
数码资讯:iPhone 18 Pro,十大升级细节浮出水面
ios·手机·iphone