先来看官方文档的说明,这是常人无法看懂的地步啊!🤣
最常见的做法是传入一个 nil
到 dst
里,连官网的示例也是如此。
本着追求卓越不断探索的精神,我深入研究了 Hertz 的底层代码,终于找到了问题的关键。现在让我带你一步步理解 dst
的作用。
深入分析 client.doRequestFollowRedirectsBuffer()
让我们直接分析doRequestFollowRedirectsBuffer()方法,Get()
和 Post()
的形参 dst
最终会流经这个方法。
func doRequestFollowRedirectsBuffer(ctx context.Context, req *protocol.Request, dst []byte, url string, c Doer) (statusCode int, body []byte, err error) {
resp := protocol.AcquireResponse()
bodyBuf := resp.BodyBuffer()
oldBody := bodyBuf.B
bodyBuf.B = dst
statusCode, _, err = DoRequestFollowRedirects(ctx, req, resp, url, defaultMaxRedirectsCount, c)
// In HTTP2 scenario, client use stream mode to create a request and its body is in body stream.
// In HTTP1, only client recv body exceed max body size and client is in stream mode can trig it.
body = resp.Body()
bodyBuf.B = oldBody
protocol.ReleaseResponse(resp)
return statusCode, body, err
}
是不是有点儿懵?乍看之下你可能会有以下疑问:
dst
传入后似乎没有被显性使用bodyBuf.B
获得dst
的值传递后,但在哪里使用了bodyBuf
呢?
关键点分析
关键在于 bodyBuf := resp.BodyBuffer()
会返回一个 *bytebufferpool.ByteBuffer
。在这段代码中,dst
被传入并赋值给 bodyBuf.B
。这是为了让响应的数据直接写入到外部供给的 dst 缓冲区,而不是使用默认的缓冲区。这种做法实际上是一种优化内存使用的策略 —— 知晓这一特性就好,通常来说在小型应用中我们也就用个 nil
🤣
赋值完成后,DoRequestFollowRedirects()
函数被调用,它负责处理请求和可能的重定向。尽管代码中没有直接操作 bodyBuf.B
,但所有的响应数据都会被写入 bodyBuf.B
,而此时 bodyBuf.B
引用了传入的 dst
。这意味着 dst
已经在 DoRequestFollowRedirects()
的内部被利用。
最后,代码将 bodyBuf.B
恢复为原来的 oldBody
,这是为了确保函数调用结束后 resp.BodyBuffer()
的状态保持不变,以防后续操作依赖于其初始状态。
总结一下!
📖 dst
和 bodyBuf.B
没有被显式调用,但实际上,它们在 DoRequestFollowRedirects
函数内部起到了关键作用。
📖 通过将dst
赋值给 bodyBuf.B
来让数据写入外部缓冲区。这是一种内存优化策略。
什么情况/场景下有必要传dst 呢?
通常来说,小型程序或者不考虑复用缓冲区的程序 dst
只需要设为 nil
,但在下面这些场景下传递外部缓冲区可以带来明显的优势:
- 缓冲区复用:在循环或批量处理 HTTP 请求的场景中,复用一个缓冲区来存储响应数据可以避免每次请求都创建新的
[]byte
,从而显著降低内存使用和分配时间。 - 高并发和大数据场景:在这些场景中,减少内存分配操作可以提高性能。
- 性能优化需求:在性能要求极高的应用中,开发者可以通过显式管理内存分配和缓冲区来优化性能。通过传入
dst
,可以更精细地控制内存的使用,避免不必要的内存分配和复制。