有时候我们需要从某个位置截断一个文件,仅保留文件的后半部分。Go 标准库中有个 file.Truncate
函数可以帮助我们进行截断,但问题是它是保留前半部分。
要实现保留后半部分,我们可以把后半部分的内容移到前面来,再使用 file.Truncate
截断即可。
如果将文件内容全部读出来,修改,再写回去不失为一个方案,但还有一个更优的方式:使用内存映射 mmap
,可以减少内存拷贝的次数。
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
// TruncateFile truncate file,discard [0,startOffset), and keep [startOffset,)
func TruncateFile(file *os.File, startOffset int64) error {
f, err := file.Stat()
if err != nil {
return err
}
if f.Size() <= startOffset {
// don't need to truncate
return nil
}
mem, err := syscall.Mmap(int(file.Fd()), 0, int(f.Size()), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
return err
}
// mem[startOffset:] override mem[0:]
copy(mem[0:], mem[startOffset:])
err = syscall.Munmap(mem)
if err != nil {
return err
}
err = file.Truncate(f.Size() - startOffset)
if err != nil {
return err
}
_, err = file.Seek(startOffset, 0)
return err
}
我们创建一个文件测试下:
1
$ echo 1234567890 > nums.txt
从第8字节开始截断:
1
2
3
4
5
6
7
8
9
10
11
func main() {
f, err := os.OpenFile("nums.txt", os.O_RDWR, 0)
if err != nil {
panic(err)
}
defer f.Close()
if err := TruncateFile(f, 8); err != nil {
panic(err)
}
fmt.Println("truncate file done")
}
结果:
1
2
$ cat nums.txt
90