我如何比较戈兰的两个文件



使用Python,我可以完成下一步:

equals = filecmp.cmp(file_old, file_new)

在go语言中有什么内置函数可以做到这一点吗?我在谷歌上搜索了一下,但没有成功。

我可以在hash/crc32包中使用一些散列函数,但这比上面的Python代码要多得多。

要完成@captncraig的回答,如果您想知道这两个文件是否相同,可以使用操作系统包中的SameFile(fi1,fi2-FileInfo)方法。

SameFile报告fi1和fi2是否描述了同一个文件。例如,在Unix上,这意味着两个底层结构的设备和inode字段是相同的;

否则,如果您想检查文件内容,这里有一个解决方案,可以逐行检查两个文件,避免将整个文件加载到内存中。

第一次尝试:https://play.golang.org/p/NlQZRrW1dT


编辑:按字节读取块,如果文件大小不相同,则快速失败。https://play.golang.org/p/YyYWuCRJXV

const chunkSize = 64000
func deepCompare(file1, file2 string) bool {
    // Check file size ...
    f1, err := os.Open(file1)
    if err != nil {
        log.Fatal(err)
    }
    defer f1.Close()
    f2, err := os.Open(file2)
    if err != nil {
        log.Fatal(err)
    }
    defer f2.Close()
    for {
        b1 := make([]byte, chunkSize)
        _, err1 := f1.Read(b1)
        b2 := make([]byte, chunkSize)
        _, err2 := f2.Read(b2)
        if err1 != nil || err2 != nil {
            if err1 == io.EOF && err2 == io.EOF {
                return true
            } else if err1 == io.EOF || err2 == io.EOF {
                return false
            } else {
                log.Fatal(err1, err2)
            }
        }
        if !bytes.Equal(b1, b2) {
            return false
        }
    }
}

我不确定这个函数能做你认为的事情。从文档来看,

除非给定了shall并且为false,否则具有相同os.stat()签名的文件将被视为相等。

您的呼叫仅比较os.stat的签名,其中仅包括:

  1. 文件模式
  2. 修改的时间
  3. 尺寸

在Go中,您可以从os.Stat函数中学习这三件事。这实际上只表明它们实际上是同一个文件,或者是同一文件的符号链接,或者是该文件的副本。

如果你想更深入,你可以打开这两个文件并进行比较(python版本一次读取8k)。

您可以使用crc或md5对这两个文件进行散列,但如果长文件的开头有差异,则需要提前停止。我建议每次从每个读取器读取一些字节数,并与bytes.Compare进行比较。

使用bytes.Equal怎么样?

package main
import (
"fmt"
"io/ioutil"
"log"
"bytes"
)
func main() {
    // per comment, better to not read an entire file into memory
    // this is simply a trivial example.
    f1, err1 := ioutil.ReadFile("lines1.txt")
    if err1 != nil {
        log.Fatal(err1)
    }
    f2, err2 := ioutil.ReadFile("lines2.txt")
    if err2 != nil {
        log.Fatal(err2)
    }
    fmt.Println(bytes.Equal(f1, f2)) // Per comment, this is significantly more performant.
}

您可以使用类似equalfile 的包

主要API:

func CompareFile(path1, path2 string) (bool, error)

Godoc:https://godoc.org/github.com/udhos/equalfile

示例:

package main
import (
    "fmt"
    "os"
    "github.com/udhos/equalfile"
 )
func main() {
    if len(os.Args) != 3 {
        fmt.Printf("usage: equal file1 file2n")
        os.Exit(2)
    }
    file1 := os.Args[1]
    file2 := os.Args[2]
    equal, err := equalfile.CompareFile(file1, file2)
    if err != nil {
        fmt.Printf("equal: error: %vn", err)
        os.Exit(3)
    }
    if equal {
        fmt.Println("equal: files match")
        os.Exit(0)
    }
    fmt.Println("equal: files differ")
    os.Exit(1)
}

在检查现有答案后,我制作了一个简单的包,用于比较任意(有限)io.Reader和文件,作为一种方便的方法:https://github.com/hlubek/readercomp

示例:

package main
import (
    "fmt"
    "log"
    "os"
    "github.com/hlubek/readercomp"
)
func main() {
    result, err := readercomp.FilesEqual(os.Args[1], os.Args[2])
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(result)
}

这是我制作的io.Reader。如果两个流不共享相同的内容,则可以_, err := io.Copy(ioutil.Discard, newCompareReader(a, b))来获取错误。此实现通过限制不必要的数据复制来优化性能。

package main
import (
    "bytes"
    "errors"
    "fmt"
    "io"
)
type compareReader struct {
    a    io.Reader
    b    io.Reader
    bBuf []byte // need buffer for comparing B's data with one that was read from A
}
func newCompareReader(a, b io.Reader) io.Reader {
    return &compareReader{
        a: a,
        b: b,
    }
}
func (c *compareReader) Read(p []byte) (int, error) {
    if c.bBuf == nil {
        // assuming p's len() stays the same, so we can optimize for both of their buffer
        // sizes to be equal
        c.bBuf = make([]byte, len(p))
    }
    // read only as much data as we can fit in both p and bBuf
    readA, errA := c.a.Read(p[0:min(len(p), len(c.bBuf))])
    if readA > 0 {
        // bBuf is guaranteed to have at least readA space
        if _, errB := io.ReadFull(c.b, c.bBuf[0:readA]); errB != nil { // docs: "EOF only if no bytes were read"
            if errB == io.ErrUnexpectedEOF {
                return readA, errors.New("compareReader: A had more data than B")
            } else {
                return readA, fmt.Errorf("compareReader: read error from B: %w", errB)
            }
        }
        if !bytes.Equal(p[0:readA], c.bBuf[0:readA]) {
            return readA, errors.New("compareReader: bytes not equal")
        }
    }
    if errA == io.EOF {
        // in happy case expecting EOF from B as well. might be extraneous call b/c we might've
        // got it already from the for loop above, but it's easier to check here
        readB, errB := c.b.Read(c.bBuf)
        if readB > 0 {
            return readA, errors.New("compareReader: B had more data than A")
        }
        if errB != io.EOF {
            return readA, fmt.Errorf("compareReader: got EOF from A but not from B: %w", errB)
        }
    }
    return readA, errA
}

标准的方法是统计它们并使用os。SameFile。

--https://groups.google.com/g/golang-nuts/c/G-5D6agvz2Q/m/2jV_6j6LBgAJ

os.SameFile应该做与Python的filecmp.cmp(f1, f2)大致相同的事情(即shallow=true,这意味着它只比较stat获得的文件信息)。

func SameFile(fi1, fi2 FileInfo) bool

SameFile报告fi1和fi2是否描述了同一个文件。例如,在Unix上,这意味着两个底层结构的设备和inode字段是相同的;在其他系统上,决策可以基于路径名称。SameFile仅适用于此包的Stat.返回的结果。在其他情况下,它会返回false。

但是,如果你真的想比较文件的内容,你必须自己做。

这会对两个文件进行逐段比较,一旦知道两个文件不同就退出。它只需要标准的库函数。

这是对mat007和christopher通过使用io.ReadFull()提出的短读问题的改进。它还避免了重新分配缓冲区。

package util
import (
    "bytes"
    "io"
    "os"
)
// Decide if two files have the same contents or not.
// chunkSize is the size of the blocks to scan by; pass 0 to get a sensible default.
// *Follows* symlinks.
//
// May return an error if something else goes wrong; in this case, you should ignore the value of 'same'.
//
// derived from https://stackoverflow.com/a/30038571
// under CC-BY-SA-4.0 by several contributors
func FileCmp(file1, file2 string, chunkSize int) (same bool, err error) {
    if chunkSize == 0 {
        chunkSize = 4 * 1024
    }
    // shortcuts: check file metadata
    stat1, err := os.Stat(file1)
    if err != nil {
        return false, err
    }
    stat2, err := os.Stat(file2)
    if err != nil {
        return false, err
    }
    // are inputs are literally the same file?
    if os.SameFile(stat1, stat2) {
        return true, nil
    }
    // do inputs at least have the same size?
    if stat1.Size() != stat2.Size() {
        return false, nil
    }
    // long way: compare contents
    f1, err := os.Open(file1)
    if err != nil {
        return false, err
    }
    defer f1.Close()
    f2, err := os.Open(file2)
    if err != nil {
        return false, err
    }
    defer f2.Close()
    b1 := make([]byte, chunkSize)
    b2 := make([]byte, chunkSize)
    for {
        n1, err1 := io.ReadFull(f1, b1)
        n2, err2 := io.ReadFull(f2, b2)
        // https://pkg.go.dev/io#Reader
        // > Callers should always process the n > 0 bytes returned
        // > before considering the error err. Doing so correctly
        // > handles I/O errors that happen after reading some bytes
        // > and also both of the allowed EOF behaviors.
        if !bytes.Equal(b1[:n1], b2[:n2]) {
            return false, nil
        }
        if (err1 == io.EOF && err2 == io.EOF) || (err1 == io.ErrUnexpectedEOF && err2 == io.ErrUnexpectedEOF) {
            return true, nil
        }
        // some other error, like a dropped network connection or a bad transfer
        if err1 != nil {
            return false, err1
        }
        if err2 != nil {
            return false, err2
        }
    }
}

让我惊讶的是,这不是标准库中的任何地方。

这样的东西应该能起到作用,并且与其他答案相比应该具有内存效率。我看了github.com/udhos/equalfile,它对我来说似乎有点过头了。在这里调用compare()之前,你应该先调用两个os.Stat(),并比较早期输出快速路径的文件大小。

与其他答案相比,使用此实现的原因是,如果不必要的话,你不想将两个文件的全部保存在内存中。你可以从A和B中读取一个数量,进行比较,然后继续读取下一个数量——每次从每个文件加载一个缓冲区,直到完成为止。你只需要小心,因为你可能从A读取50个字节,然后从B读取60个字节,因为你的读取可能由于某种原因被阻止了。

此实现假设Read()调用不会返回N>0(读取了一些字节)同时出现错误!=无这就是操作系统。文件的行为,但不是Read的其他实现(如net)的行为。TCPConn。

import (
  "os"
  "bytes"
  "errors"
)
var errNotSame = errors.New("File contents are different")
func compare(p1, p2 string) error {
    var (
        buf1 [8192]byte
        buf2 [8192]byte
    )
    fh1, err := os.Open(p1)
    if err != nil {
        return err
    }
    defer fh1.Close()
    fh2, err := os.Open(p2)
    if err != nil {
        return err
    }
    defer fh2.Close()
    for {
        n1, err1 := fh1.Read(buf1[:])
        n2, err2 := fh2.Read(buf2[:])
        if err1 == io.EOF && err2 == io.EOF {
            // files are the same!
            return nil
        }
        if err1 == io.EOF || err2 == io.EOF {
            return errNotSame
        }
        if err1 != nil {
            return err1
        }
        if err2 != nil {
            return err2
        }
        // short read on n1
        for n1 < n2 {
            more, err := fh1.Read(buf1[n1:n2])
            if err == io.EOF {
                return errNotSame
            }
            if err != nil {
                return err
            }
            n1 += more
        }
        // short read on n2
        for n2 < n1 {
            more, err := fh2.Read(buf2[n2:n1])
            if err == io.EOF {
                return errNotSame
            }
            if err != nil {
                return err
            }
            n2 += more
        }
        if n1 != n2 {
            // should never happen
            return fmt.Errorf("file compare reads out of sync: %d != %d", n1, n2)
        }
        if bytes.Compare(buf1[:n1], buf2[:n2]) != 0 {
            return errNotSame
        }
    }
}

相关内容

  • 没有找到相关文章

最新更新