对象存储路径文件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 【源端文件】 【目标端文件】 【结果输出前缀】

相关推荐
2501_916007473 小时前
iPhone APP 性能测试怎么做,除了Instruments还有什么工具?
android·ios·小程序·https·uni-app·iphone·webview
2501_915106323 小时前
Windows 环境下有哪些可用的 iOS 上架工具, iOS 上架工具的使用方式
android·ios·小程序·https·uni-app·iphone·webview
Student_Zhang5 小时前
一个管理项目中所有弹窗的弹窗管理器(PopupManager)
前端·ios·github
denggun123455 小时前
使用 os_unfair_lock 替代 DispatchQueue?!
ios
巴塞罗那的风5 小时前
golang协程泄漏排查实战
开发语言·后端·golang
DYS_房东的猫6 小时前
学习总结笔记三:让网站“活”起来——处理静态文件、表单验证与用户登录(第3章实战版)
笔记·学习·golang
怪我冷i6 小时前
GORM 的 Migration API
数据库·postgresql·golang·ai编程·ai写作
2501_915106327 小时前
iOS 抓包工具有哪些?不同类型的抓包工具可以做什么
android·ios·小程序·https·uni-app·iphone·webview
迷途的小子7 小时前
go-gin binding 标签详解
java·golang·gin