前言
有時候我們生成的日志文件很大,如果文件過大,或者磁盤空間有限,那么我們就需要把日志文件改小一點。
(當然實際應用場景中,一般采用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
}
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
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
您可能感興趣的文章:- Oracle給用戶授權truncatetable的實現方案
- delete from 表名與truncate table 表名區別
- SQL中Truncate的用法
- tf.truncated_normal與tf.random_normal的詳細用法
- smarty中改進truncate使其支持中文的方法
- SQL Server中TRUNCATE事務回滾操作方法
- 實例理解SQL中truncate和delete的區別
- 詳解SQL中drop、delete和truncate的異同
- Mysql開啟慢SQL并分析原因
- Truncate Table的用法講解