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

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

作者: 卜是 | 来源:发表于2020-10-02 10:56 被阅读0次

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

    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实现

          本文链接:https://www.haomeiwen.com/subject/qgkbuktx.html