190 lines
2.9 KiB
Go
190 lines
2.9 KiB
Go
|
package file
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
buffSize = 1 << 20
|
||
|
)
|
||
|
|
||
|
// ReadLineFromEnd --
|
||
|
type ReadLineFromEnd struct {
|
||
|
f *os.File
|
||
|
|
||
|
fileSize int
|
||
|
bwr *bytes.Buffer
|
||
|
lineBuff []byte
|
||
|
swapBuff []byte
|
||
|
|
||
|
isFirst bool
|
||
|
}
|
||
|
|
||
|
// Exists
|
||
|
func IsExists(path string) (os.FileInfo, bool) {
|
||
|
f, err := os.Stat(path)
|
||
|
return f, err == nil || os.IsExist(err)
|
||
|
}
|
||
|
|
||
|
// NewReadLineFromEnd
|
||
|
func NewReadLineFromEnd(filename string) (rd *ReadLineFromEnd, err error) {
|
||
|
f, err := os.Open(filename)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
info, err := f.Stat()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if info.IsDir() {
|
||
|
return nil, fmt.Errorf("not file")
|
||
|
}
|
||
|
|
||
|
fileSize := int(info.Size())
|
||
|
|
||
|
rd = &ReadLineFromEnd{
|
||
|
f: f,
|
||
|
fileSize: fileSize,
|
||
|
bwr: bytes.NewBuffer([]byte{}),
|
||
|
lineBuff: make([]byte, 0),
|
||
|
swapBuff: make([]byte, buffSize),
|
||
|
isFirst: true,
|
||
|
}
|
||
|
return rd, nil
|
||
|
}
|
||
|
|
||
|
// ReadLine 结尾包含'\n'
|
||
|
func (c *ReadLineFromEnd) ReadLine() (line []byte, err error) {
|
||
|
var ok bool
|
||
|
for {
|
||
|
ok, err = c.buff()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if ok {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
line, err = c.bwr.ReadBytes('\n')
|
||
|
if err == io.EOF && c.fileSize > 0 {
|
||
|
err = nil
|
||
|
}
|
||
|
return line, err
|
||
|
}
|
||
|
|
||
|
// Close --
|
||
|
func (c *ReadLineFromEnd) Close() (err error) {
|
||
|
return c.f.Close()
|
||
|
}
|
||
|
|
||
|
func (c *ReadLineFromEnd) buff() (ok bool, err error) {
|
||
|
if c.fileSize == 0 {
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
if c.bwr.Len() >= buffSize {
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
offset := 0
|
||
|
if c.fileSize > buffSize {
|
||
|
offset = c.fileSize - buffSize
|
||
|
}
|
||
|
_, err = c.f.Seek(int64(offset), 0)
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
n, err := c.f.Read(c.swapBuff)
|
||
|
if err != nil && err != io.EOF {
|
||
|
return false, err
|
||
|
}
|
||
|
if c.fileSize < n {
|
||
|
n = c.fileSize
|
||
|
}
|
||
|
if n == 0 {
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
for {
|
||
|
m := bytes.LastIndex(c.swapBuff[:n], []byte{'\n'})
|
||
|
if m == -1 {
|
||
|
break
|
||
|
}
|
||
|
if m < n-1 {
|
||
|
err = c.writeLine(c.swapBuff[m+1 : n])
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
ok = true
|
||
|
} else if m == n-1 && !c.isFirst {
|
||
|
err = c.writeLine(nil)
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
ok = true
|
||
|
}
|
||
|
n = m
|
||
|
if n == 0 {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if n > 0 {
|
||
|
reverseBytes(c.swapBuff[:n])
|
||
|
c.lineBuff = append(c.lineBuff, c.swapBuff[:n]...)
|
||
|
}
|
||
|
if offset == 0 {
|
||
|
err = c.writeLine(nil)
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
ok = true
|
||
|
}
|
||
|
c.fileSize = offset
|
||
|
if c.isFirst {
|
||
|
c.isFirst = false
|
||
|
}
|
||
|
return ok, nil
|
||
|
}
|
||
|
|
||
|
func (c *ReadLineFromEnd) writeLine(b []byte) (err error) {
|
||
|
if len(b) > 0 {
|
||
|
_, err = c.bwr.Write(b)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
if len(c.lineBuff) > 0 {
|
||
|
reverseBytes(c.lineBuff)
|
||
|
_, err = c.bwr.Write(c.lineBuff)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
c.lineBuff = c.lineBuff[:0]
|
||
|
}
|
||
|
_, err = c.bwr.Write([]byte{'\n'})
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func reverseBytes(b []byte) {
|
||
|
n := len(b)
|
||
|
if n <= 1 {
|
||
|
return
|
||
|
}
|
||
|
for i := 0; i < n; i++ {
|
||
|
k := n - 1
|
||
|
if k != i {
|
||
|
b[i], b[k] = b[k], b[i]
|
||
|
}
|
||
|
n--
|
||
|
}
|
||
|
}
|