前言
有時(shí)候我們生成的日志文件很大,如果文件過(guò)大,或者磁盤空間有限,那么我們就需要把日志文件改小一點(diǎn)。
(當(dāng)然實(shí)際應(yīng)用場(chǎng)景中,一般采用log rotate的方式實(shí)現(xiàn)多個(gè)日志文件,定期把舊的日志文件挪走或者刪除)
現(xiàn)在我們考慮如何把日志文件變小的場(chǎng)景。
首先想到的是文件truncate,遺憾的是truncate是把新的內(nèi)容刪掉,而不是把舊的內(nèi)容刪掉;因?yàn)橥ǔN覀兊淖龇ㄊ且雅f的日志內(nèi)容刪除,而保留最新的日志內(nèi)容。
方法1:拷貝文件,然后把前半部分刪除,保留新的后半部分
$ wc -l logfile
$ tail -n {LINENUM}/2 logfile > logfile2
$ mv logfile2 logfile
缺點(diǎn)是需要額外的臨時(shí)磁盤空間(logfile2),保留新拷貝的文件
方法2:把日志文件讀入內(nèi)存,截去前半部分,把后半部分重新寫(xiě)會(huì)文件。
缺點(diǎn)也是要?jiǎng)?chuàng)建一個(gè)臨時(shí)文件,或者把整個(gè)文件內(nèi)容一次性讀入內(nèi)存。
下面是一個(gè)實(shí)現(xiàn)創(chuàng)建臨時(shí)文件的例子:
(如果采用整個(gè)文件讀入內(nèi)存,那么可以重復(fù)覆蓋使用原來(lái)的讀入文件,不用創(chuàng)建臨時(shí)文件):
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()
}
運(yùn)行:
對(duì)于1G大小的日志文件,在我的Linux上執(zhí)行時(shí)間大約是:
$ go build time ./main
real 0m6.972s
user 0m4.668s
sys 0m2.055s
方法3:也是利用文件,把文件mmap映射到內(nèi)存,然后使用copy函數(shù)覆蓋內(nèi)存。
缺點(diǎn)是,mmap操作日志文件不能過(guò)大。
下面是一個(gè)實(shí)現(xiàn)的代碼例子:
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()
}
函數(shù)truncateText的功能就是把文件截取到原來(lái)的一半大小,并保持按行對(duì)齊;分如下幾步:
- 先把文件映射syscall.Mmap 到內(nèi)存。
- 查找到中間行的位置。
- 然后用內(nèi)置函數(shù)copy,把中間行以后的挪動(dòng)到文件開(kāi)始位置。
- 修改文件的大小file.Truncate
- 重置文件的偏移指針offset
- 取消文件映射syscall.Munmap
運(yùn)行:
對(duì)于1G大小的日志文件,在我的Linux上執(zhí)行時(shí)間大約是:
$ go build time ./main
real 0m1.861s
user 0m0.335s
sys 0m0.650s
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
您可能感興趣的文章:- Oracle給用戶授權(quán)truncatetable的實(shí)現(xiàn)方案
- delete from 表名與truncate table 表名區(qū)別
- SQL中Truncate的用法
- tf.truncated_normal與tf.random_normal的詳細(xì)用法
- smarty中改進(jìn)truncate使其支持中文的方法
- SQL Server中TRUNCATE事務(wù)回滾操作方法
- 實(shí)例理解SQL中truncate和delete的區(qū)別
- 詳解SQL中drop、delete和truncate的異同
- Mysql開(kāi)啟慢SQL并分析原因
- Truncate Table的用法講解