You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言:小[]byte填充大[]byte异常,分块下载文件损坏求助

分块下载时数据体积翻倍的问题修复

你的代码核心问题出在切片初始化和数据写入的逻辑冲突:

  • 在New函数中,你用make([]byte, partsize)初始化c.data,这会创建一个长度等于分块大小、所有元素默认填充0的切片。
  • 后续下载时用append(c.data, buffer[:n]...),append是在切片现有长度的末尾追加数据,相当于把下载的有效字节拼在了初始的一堆0字节后面,最终c.data的长度就变成了分块大小(初始0字节)+ 实际下载字节数,直接导致体积翻倍,且前面的0字节会破坏文件结构。

而之前用io.ReadAll能正常工作,是因为它直接把读取到的完整数据赋值给c.data,覆盖了原来初始化的切片,自然不会有多余的0字节,但这种方式无法中途触发进度更新。


修复方案

方案一:初始化时只指定容量(简单高效)

修改New函数中c.data的初始化逻辑,只预分配容量、不设置初始长度:

// 原代码
data: make([]byte, partsize),
// 修改为
data: make([]byte, 0, partsize), // 长度0,容量partsize,无初始0字节

这样append操作会从切片的起始位置开始添加数据,最终c.data的长度就是实际下载的字节数,不会有多余内容。对于最后一个分块(可能大于预分配容量),append会自动扩容,不影响功能。

方案二:用偏移量直接写入切片(内存更可控)

如果想严格利用预分配的内存、避免append的扩容开销,可以维护一个写入偏移量,直接把数据写入到c.data的对应位置:

  1. 先修正New函数中最后一个分块的大小计算:
func New(ctx context.Context, res _http.Response, index int, wg *sync.WaitGroup) *Chunk {
    totalpart := int64(res.Totalpart)
    partsize := res.Size / totalpart

    start := int64(index * int(partsize))
    end := start + int64(int(partsize)-1)

    // 修正最后一个分块的实际大小
    if index == int(totalpart)-1 {
        end = res.Size
        partsize = end - start // 最后一个分块的真实大小
    }

    return &Chunk{
        response: res,
        wg:       wg,
        index:    index,
        start:    start,
        end:      end,
        size:     partsize,
        data:     make([]byte, partsize), // 按真实大小初始化长度
        ctx:      ctx,
    }
}
  1. 修改download函数的写入逻辑:
func (c *Chunk) download() error {
    defer c.wg.Done()

    http_ := &http.Client{}
    part := fmt.Sprintf("bytes=%d-%d", c.start, c.end)

    if c.size == -1 {
        log.Printf("Downloading chunk %d with size unknown", c.index+1)
    } else {
        log.Printf("Downloading chunk %d from %d to %d (~%d MB)", c.index+1, c.start, c.end, (len(c.data))/(1024*1024))
    }

    req, err := http.NewRequest("GET", c.response.Url, nil)
    if err != nil {
        return err
    }

    start := time.Now()

    req.Header.Add("Range", part)
    res, err := http_.Do(req)
    if err != nil {
        return err
    }
    defer res.Body.Close()

    var _100KB int64 = 1024 * 100
    buffer := make([]byte, _100KB)
    var written int64 // 记录已写入的字节偏移量

    for {
        // 用Read替代ReadFull,避免最后一次读取因数据不足报错
        n, err := res.Body.Read(buffer)
        if err != nil {
            if err == io.EOF {
                break
            }
            return err
        }

        // 确保写入不超出切片范围(应对最后一个分块的边界情况)
        if written+int64(n) > int64(len(c.data)) {
            n = int(int64(len(c.data)) - written)
            if n <= 0 {
                break
            }
        }

        // 直接将buffer内容复制到c.data的对应位置
        copy(c.data[written:], buffer[:n])
        written += int64(n)
        runtime.EventsEmit(c.ctx, "transfered", c.index, n)
    }

    elapsed := time.Since(start)
    log.Printf("Chunk %d downloaded in %v s\n", c.index+1, elapsed.Seconds())

    return nil
}

内容的提问来源于stack exchange,提问作者newtocoding

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 05:23:10