分页对齐写入文件——golang实现

卜是 · · 439 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

将内存中的数据写入文件时,往往是有多少,就直接写入多少。当频繁的写入小批量数据,因反复系统调用导致性能较差;此外,有时要求按照一定字节对齐的方式写入文件。为了解决以上两个问题,本文实现了分页写入的方案。

1. 数据结构设计

//以页为单元写入文件,或者通过flush写入
type PageWriter struct {
    w io.Writer
    // pageOffset为当前已写入页的偏移量(相对于buff的起始地址),因有可能非完整的页已写入文件(flush方式),所以新的写入应该先补齐这非完整的部分
    pageOffset int
    // 每页的字节数
    pageBytes int
    // buff中等待写入的字节数,bufferedBytes = len(buf)
    bufferedBytes int
    // buffer
    buf []byte
    // 当需要写入的字节数达到bufWatermarkBytes,则触发写入,该值小于len(buf),保证写入文件中的数据页对齐
    bufWatermarkBytes int
}

2. 对象创建和Flush操作

func NewPageWriter(w io.Writer, pageBytes, pageOffset int) *PageWriter {
    return &PageWriter{
        w:                 w,
        pageOffset:        pageOffset,
        pageBytes:         pageBytes,
        buf:               make([]byte, defaultBufferBytes+pageBytes),//因页偏移,需多申请一页的空间
        bufWatermarkBytes: defaultBufferBytes,
    }
}

func (pw *PageWriter) Flush() error {
    _, err := pw.flush()
    return err
}

func (pw *PageWriter) FlushN() (int, error) {
    return pw.flush()
}

func (pw *PageWriter) flush() (int, error) {
    if pw.bufferedBytes == 0 {
        return 0, nil
    }
    n, err := pw.w.Write(pw.buf[:pw.bufferedBytes])
// 调整pageOffset,考虑到此次flush可能并不是整页的写入,因而要记录已写入的不完整页的字节数
    pw.pageOffset = (pw.pageOffset + pw.bufferedBytes) % pw.pageBytes
    pw.bufferedBytes = 0
    return n, err
}

3. 数据写入

func (pw *PageWriter) Write(p []byte) (n int, err error) {
    if len(p)+pw.bufferedBytes <= pw.bufWatermarkBytes {
        // 未超过,则写入buff
        copy(pw.buf[pw.bufferedBytes:], p)
        pw.bufferedBytes += len(p)
        return len(p), nil
    }
    // 计算出未页对齐的字节数
    slack := pw.pageBytes - ((pw.pageOffset + pw.bufferedBytes) % pw.pageBytes)
    if slack != pw.pageBytes {
        partial := slack > len(p)
        if partial {
            // 没有足够数据来对齐
            slack = len(p)
        }
        // Append数据
        copy(pw.buf[pw.bufferedBytes:], p[:slack])
        pw.bufferedBytes += slack
        n = slack
        p = p[slack:]
        if partial {
            // 若还未达到对齐条件,则直接返回
            return n, nil
        }
    }
    // buffer中已经是页对齐,可以写入文件
    if err = pw.Flush(); err != nil {
        return n, err
    }
    // 若p中字节数大于页大小,则直接写入文件
    if len(p) > pw.pageBytes {
        pages := len(p) / pw.pageBytes
        c, werr := pw.w.Write(p[:pages*pw.pageBytes])
        n += c
        if werr != nil {
            return n, werr
        }
        p = p[pages*pw.pageBytes:] //剩余的部分
    }
    // 将剩余部分写入buffer
    c, werr := pw.Write(p)
    n += c
    return n, werr
}

4. 总结

参考以上代码及代码中的详细说明,可明了其实现过程,关键在于理解pageOffset的含义。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:卜是

查看原文:分页对齐写入文件——golang实现

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

439 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传