如何加快从远程URL获取文件大小的速度?
如何加速批量远程文件大小检测(无需下载)
你的思路其实很对——用HEAD请求获取Content-Length而不下载整个文件,这已经是最省带宽的方式了。现在慢的核心问题是串行处理400多个请求,每个请求都要等前一个完成,总耗时是所有请求耗时的总和。下面给你几个具体的优化方案,直接改代码就能大幅提速:
核心优化点
- 并行请求+并发控制:同时发起多个HEAD请求,但要限制并发数,避免触发目标服务器的限流/反爬机制
- 替换
HttpWebRequest为HttpClient:HttpWebRequest是旧API,HttpClient更高效且支持复用,能减少连接建立的开销 - 添加错误处理:应对链接失效、服务器不支持HEAD请求等异常情况
修改后的完整代码
1. 复用的HttpClient实例(关键!)
首先创建一个全局复用的HttpClient,不要每次请求都创建新实例:
// 推荐放在类的静态字段里,整个应用生命周期复用 private static readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler()) { DefaultRequestHeaders = { { "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" } } };
2. 带并发控制的文件大小获取方法
/// <summary> /// 批量获取远程文件大小,带并发控制 /// </summary> /// <param name="urls">待检测的URL列表</param> /// <param name="maxConcurrentRequests">最大并发数,推荐10-20,根据目标服务器调整</param> /// <returns>总文件大小(字节)</returns> public static async Task<long> GetTotalFileSizeAsync(IEnumerable<string> urls, int maxConcurrentRequests = 15) { var semaphore = new SemaphoreSlim(maxConcurrentRequests); var tasks = new List<Task<long>>(); foreach (var urlStr in urls) { // 跳过空行 if (string.IsNullOrWhiteSpace(urlStr)) continue; tasks.Add(Task.Run(async () => { await semaphore.WaitAsync(); try { if (!Uri.TryCreate(urlStr, UriKind.Absolute, out var uri)) { Console.WriteLine($"无效URL:{urlStr}"); return 0; } using var request = new HttpRequestMessage(HttpMethod.Head, uri); using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); // 处理服务器不支持HEAD请求的情况(比如返回405),可以降级为GET但只取 headers if ((int)response.StatusCode == 405) { request.Method = HttpMethod.Get; using var getResponse = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); getResponse.EnsureSuccessStatusCode(); return getResponse.Content.Headers.ContentLength ?? 0; } response.EnsureSuccessStatusCode(); return response.Content.Headers.ContentLength ?? 0; } catch (Exception ex) { Console.WriteLine($"请求失败 {urlStr}:{ex.Message}"); return 0; } finally { semaphore.Release(); } })); } // 等待所有任务完成,然后求和 var sizes = await Task.WhenAll(tasks); return sizes.Sum(); }
3. 保留你的格式化方法(无需修改)
public static string FormatFileSize(long bytes) { var unit = 1024; if (bytes < unit) { return $"{bytes} B"; } var exp = (int)(Math.Log(bytes) / Math.Log(unit)); return $"{bytes / Math.Pow(unit, exp):F2} " + $"{("KMGTPE")[exp - 1]}B"; }
4. 调用示例(异步化,避免阻塞UI)
// 注意:UI线程中要使用await,不要用Task.Run阻塞 private async void btnCheckSizes_Click(object sender, EventArgs e) { Cursor = Cursors.WaitCursor; try { var urls = tbResult.Lines.Where(line => !string.IsNullOrWhiteSpace(line)).ToList(); var totalBytes = await GetTotalFileSizeAsync(urls); var formattedSize = FormatFileSize(totalBytes); Console.WriteLine($"总文件大小:{formattedSize}"); // 也可以显示在UI控件里,比如: // lblTotalSize.Text = $"总大小:{formattedSize}"; } catch (Exception ex) { MessageBox.Show($"检测失败:{ex.Message}"); } finally { Cursor = Cursors.Default; } }
优化效果说明
- 原来的串行处理:假设每个请求耗时1秒,400个请求需要400秒(约6.7分钟)
- 优化后的并行处理:用15个并发,总耗时大概是400/15 ≈ 27秒(实际取决于网络和服务器响应速度),提速非常明显
注意事项
- 调整
maxConcurrentRequests的值:如果目标服务器限制严格,就调小(比如5-10);如果服务器允许,就调大(比如20-30) - 错误处理很重要:有些服务器可能禁止HEAD请求,或者返回的
Content-Length为空,代码里已经做了降级处理和异常捕获 - 不要频繁创建HttpClient:复用实例能减少TCP连接建立的开销,这是性能提升的关键之一
内容的提问来源于stack exchange,提问作者zackmark15
相关产品推荐
相关产品推荐

