文件大于 4GB 以下方法一定行不通, 32位操作系统 最大的寻址空间就是 4GB

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
    "crypto/sha1"
    "fmt"
    "io/ioutil"
    "log"
)

func main() {
    bytes, err := ioutil.ReadFile("file.txt")
    if err != nil {
        log.Fatal(err)
    }

    h := sha1.New()
    h.Write(bytes)

    fmt.Printf("% x", h.Sum(nil))
}

以下方法可以算出大于 4GB 文件的 sha1, 但是如果直接表面理解代码, 给人的感觉是无法运行的

io.Copy(h, f) 这里给人的感觉也是一次性读取文件到 h 变量中, “给人一种把 整个文件读取到内存的感觉”

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
    "crypto/sha1"
    "fmt"
    "io"
    "log"
    "os"
)

func main() {
    f, err := os.Open("file.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    h := sha1.New()
    if _, err := io.Copy(h, f); err != nil {
        log.Fatal(err)
    }

    fmt.Printf("% x", h.Sum(nil))
}

详解

跟踪代码 sha1.New() 找到 sha1Write 方法实现

io.Copy(h, f) 会使用这个 Write 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func (d *digest) Write(p []byte) (nn int, err error) {
    nn = len(p)
    d.len += uint64(nn)
    // 这里 d.nx 大于 0 的时候 进入进行处理数据
    if d.nx > 0 {
        n := copy(d.x[d.nx:], p)
        d.nx += n
        if d.nx == chunk {
            // 处理. '具体不知道怎么实现的.. 没研究过'
            block(d, d.x[:])
            // 但是这里处理完毕后会 清空 d.nx
            // 所以这里的 Write 函数其实已经在处理 sha1 了
            // 并没有多少实际的内存占用
            d.nx = 0
        }
        p = p[n:]
    }

    if len(p) >= chunk {
        n := len(p) &^ (chunk - 1)
        block(d, p[:n])
        p = p[n:]
    }
    if len(p) > 0 {
        d.nx = copy(d.x[:], p)
    }
    return
}