golang实战之truncate⽇志⽂件详解
前⾔
有时候我们⽣成的⽇志⽂件很⼤,如果⽂件过⼤,或者磁盘空间有限,那么我们就需要把⽇志⽂件改⼩⼀点。
(当然实际应⽤场景中,⼀般采⽤log rotate的⽅式实现多个⽇志⽂件,定期把旧的⽇志⽂件挪⾛或者删除)
现在我们考虑如何把⽇志⽂件变⼩的场景。
⾸先想到的是⽂件truncate,遗憾的是truncate是把新的内容删掉,⽽不是把旧的内容删掉;因为通常我们的做法是要把旧的⽇志内容删除,⽽保留最新的⽇志内容。
⽅法1:拷贝⽂件,然后把前半部分删除,保留新的后半部分
$ wc -l logfile
$ tail -n {LINENUM}/2 logfile > logfile2
$ mv logfile2 logfile
缺点是需要额外的临时磁盘空间(logfile2),保留新拷贝的⽂件
⽅法2:把⽇志⽂件读⼊内存,截去前半部分,把后半部分重新写会⽂件。
缺点也是要创建⼀个临时⽂件,或者把整个⽂件内容⼀次性读⼊内存。
下⾯是⼀个实现创建临时⽂件的例⼦:
(如果采⽤整个⽂件读⼊内存,那么可以重复覆盖使⽤原来的读⼊⽂件,不⽤创建临时⽂件):
package main
import (
"bufio"
"log"
"io"
"os"
)
func ReadLine(r *bufio.Reader, w *bufio.Writer) error {
for i := 0; ; i++ {
line, err := r.ReadString('\n')
if i >= 12554364/2 { // this number is hard-codes just for example use
if _,err := w.WriteString(line); err != nil {
log.Fatal(err)
}
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
}
return nil
}
func main() {
fin, err := os.Open("data.in")
if err != nil {
log.Fatal(err)
}
defer fin.Close()
fout, err := os.Create("data.dat")
if err != nil {
log.Fatal(err)
}
defer fout.Close()
reader := bufio.NewReader(fin)
writer := bufio.NewWriter(fout)
if err := ReadLine(reader, writer); err != nil {
log.Fatal(err)
}
writer.Flush()
}
运⾏:
对于1G⼤⼩的⽇志⽂件,在我的Linux上执⾏时间⼤约是:
$ go build && time ./main
real 0m6.972s
user 0m4.668s
sys  0m2.055s
⽅法3:也是利⽤⽂件,把⽂件mmap映射到内存,然后使⽤copy函数覆盖内存。
缺点是,mmap操作⽇志⽂件不能过⼤。
下⾯是⼀个实现的代码例⼦:
package main
import (
"fmt"
"log"
"os"
"syscall"
)
// Truncate file to half size, with line aligned.
func truncateText(f *os.File) error {
fi, err := f.Stat()
if err != nil {
return err
}
mem, err := syscall.Mmap(int(f.Fd()), 0, int(fi.Size()), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) if err != nil {
return err
}
size := fi.Size()
if size <= 1 {
// Don't need to truncate file if it's too small
return nil
}
trun := size/2 - 1
for ; trun < fi.Size(); trun ++ {
if mem[trun] == '\n' {
break
}
}
//fmt.Printf("size=%d, trun=%d\n", size, trun)
if trun >= size - 1 {
trun = size/2
} else {
trun = trun + 1
}
/
/ Overwrite file content
copy(mem[0:], mem[trun:])
err = syscall.Munmap(mem)
if err != nil {
return err
}
// truncate file
f.Truncate(fi.Size() - trun)
// reset file offset
f.Seek(trun,0)
return nil
}truncated在存储过程中怎么使用
func main() {
path := "data.dat"
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
fmt.Println("Cannot create file")
log.Fatal(err)
}
err = truncateText(f)
if err != nil {
fmt.Println("Cannot truncateLog file")
log.Fatal(err)
}
f.Write([]byte("ABCD"))
f.Close()
}
函数truncateText的功能就是把⽂件截取到原来的⼀半⼤⼩,并保持按⾏对齐;分如下⼏步:
先把⽂件映射syscall.Mmap 到内存。
查到中间⾏的位置。
然后⽤内置函数copy,把中间⾏以后的挪动到⽂件开始位置。
修改⽂件的⼤⼩file.Truncate
重置⽂件的偏移指针offset
取消⽂件映射syscall.Munmap
运⾏:
对于1G⼤⼩的⽇志⽂件,在我的Linux上执⾏时间⼤约是:
$ go build && time ./main
real 0m1.861s
user 0m0.335s
sys  0m0.650s
总结
以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作具有⼀定的参考学习价值,如果有疑问⼤家可以留⾔交流,谢谢⼤家对的⽀持。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。