如何在 golang 中从较小的 []byte 数组填充较大的 []byte 数组?

问题描述 投票:0回答:2

所以我正在从 URL 下载文件并将其拆分为带有

content-range
标头的块,并希望将下载文件流式传输到进度条中。目前,我正在努力将下载的字节写入块中。我的做法如下:


type Chunk struct {
    response _http.Response
    wg       *sync.WaitGroup
    index    int
    start    int64
    end      int64
    size     int64
    data     []byte
    ctx      context.Context
}

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
    }

    return &Chunk{
        response: res,
        wg:       wg,
        index:    index,
        start:    start,
        end:      end,
        size:     partsize,
        data:     make([]byte, partsize),
        ctx:      ctx,
    }
}

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)

    for {
        n, err := io.ReadFull(res.Body, buffer)
        if err == io.EOF {
            break
        }

        //TODO: combine the downloaded bytes into c.data
        c.data = append(c.data, buffer[:n]...)
        runtime.EventsEmit(c.ctx, "transfered", c.index, n)
    }


    // c.data, err = io.ReadAll(res.Body)
    // if err != nil {
    //  return err
    // }

    // runtime.EventsEmit(c.ctx, "transfered", len(c.data))

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

    return nil
}

_http.Response
是我的自定义结构,用于在下载之前获取 URL 的数据(例如文件总大小、cansplit?、总部分和内容类型)。

但是上面的代码有一个问题,就是

len()
(块数据)的
c.data
变得比实际块大小大。例如,实际视频文件的块大小是 12 MB,但是使用上面的代码,块变成了两倍大,当然当所有块合并到一个视频文件中时,而不是占用 48 MB(例如) ,变成100MB,下载的视频无法播放

在我之前的尝试中,我使用了

io.ReadAll(res.Body)
(我评论的代码)然后将其写入
c.data
。下载完成后可以播放视频文件,但是上面的方法不行。缺点是,我无法流式下载,因为我需要完整下载它

如果我要在下载块之前和之后记录文件的

len()
,那么下载过程之后的
len()
将变得两倍大。

我认为带有append的扩展运算符足以从头到尾填充

c.data
的每个元素。而且我认为,因为它使用的是
append
,所以数据是否超过数组启动时的
len()
并不重要。有人知道这个问题吗?任何帮助表示赞赏。提前谢谢你

arrays go slice
2个回答
0
投票

确保服务器响应部分内容响应。

这里是我假设在每个响应上运行的循环的一些修复:将报告的字节数附加到缓冲区。将读取的任何数据复制到 c.data 后中断。中断任何错误。

for {
    n, err := io.ReadFull(res.Body, buffer)
    c.data = append(c.data, buffer[:n]...)
    if err != nil {
        break
    }

    runtime.EventsEmit(c.ctx, "transfered", c.index, n)
}

无需使用 content-range 报告进度。请求整个资源并报告读取的字节,如上面的循环所示。


0
投票

我找到答案了

事实证明,

append
实际上是附加到
partsize
索引之后的下一个元素。我尝试在下载过程后重新分配
c.data

var _100KB int64 = 1024 * 100
buffer := make([]byte, _100KB)
transfered := 0

for {
    n, err := io.ReadFull(res.Body, buffer)
    if err == io.EOF {
        break
    }
    transfered += n
    //TODO: combine the downloaded bytes into c.data
    c.data = append(c.data, buffer[:n]...)
    runtime.EventsEmit(c.ctx, "transfered", c.index, n)
}

c.data = c.data[transfered:]

现在可以用了。下载的视频文件可以播放

但是,经过进一步研究,我可以只用

cap
制作切片,所以我需要用以下代码启动切片以使其更简单,并且我不需要在下载过程后对数组进行切片完成

func New(ctx context.Context, res _http.Response, index int, wg *sync.WaitGroup) *Chunk {
    //...

    return &Chunk{
        //...
        data:     make([]byte, 0, partsize),
        //...
    }
}

© www.soinside.com 2019 - 2024. All rights reserved.