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的对应位置:
- 先修正
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, } }
- 修改
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
相关产品推荐
相关产品推荐

