【问题标题】:C# efficient reading of stream content with a limit on amount readC# 有效读取流内容并限制读取量
【发布时间】:2019-02-12 11:58:00
【问题描述】:

我有一个案例,Web API 调用返回一个非常大的字符串响应。我拨打电话如下:

var multipartContent = new MultipartFormDataContent();
multipartContent.Add(new ByteArrayContent(blobStream.CopyToBytes()), 
                         "upload", Path.GetFileName(fileName));

var response = await _httpClient.PostAsync("api/v1/textResponse", multipartContent);
int responeLength = response.Content.Headers.ContentLength.HasValue ? 
                    (int)response.Content.Headers.ContentLength.Value : -1;

response.EnsureSuccessStatusCode();

我只需要处理响应中的前 1Mb 数据,因此如果响应小于 1Mb,我将读取所有数据,但如果更多,我将硬停止读取 1Mb。

我正在寻找最有效的阅读方式。我试过这段代码:

// section above...

response.EnsureSuccessStatusCode();

string contentText = null;

if (responeLength < maxAllowedLimit) // 1Mb
{
     // less then limit - read all as string.
     contentText = await response.Content.ReadAsStringAsync();
} 
else {
     var contentStream = await response.Content.ReadAsStreamAsync();
     using (var stream = new MemoryStream())
     {
         byte[] buffer = new byte[5120]; // read in chunks of 5KB
         int bytesRead;
         while((bytesRead = contentStream.Read(buffer, 0, buffer.Length)) > 0)
         {
             stream.Write(buffer, 0, bytesRead);
         }
         contentText = stream.ConvertToString();
     }
}

这是最有效的方法吗?如何限制阅读量(其他)。我试过这段代码,它总是返回一个空字符串。还有:

ReadAsStringAsync()
ReadAsByteArrayAsync()
ReadAsStreamAsync()
LoadIntoBufferAsync(int size)

这些方法是否更有效?

提前感谢您的任何指点!

【问题讨论】:

  • 不确定您的ConvertToString 方法是如何实现的,但您可能需要先将MemoryStream 的Position 设置回0
  • 另外,看看Stream.CopyTo - 不需要自己实现。
  • 请注意,您不能只获取 UTF-8 字节流,然后在一定数量的字节后硬停止。您很可能最终处于代码点的中间,这将产生无效的字符串。您可以使用 StreamReader,也可以致电 Encoding.GetDecoder()
  • 我厌倦了这个@canton7 - ``` var contentStream = await response.Content.ReadAsByteArrayAsync(); var text = Encoding.UTF8.GetString(contentStream, 0, _settings.TextSizeUpperLimitBytes);``` 你是这个意思吗?似乎运作良好!这是获取部分流的更有效方式吗?
  • @RobMcCabe 我不建议以任何方式、形状或形式这样做。我说你必须使用StreamReader,或Encoding.GetEncoder()。第一个适用于流。第二个适用于字节数组,但您可以逐位输入字节。

标签: c# stream memorystream


【解决方案1】:

我怀疑最有效(但仍然正确)的方法可能是这样的。由于您对读取的 bytes 数量有限制,而不是 characters 的数量,因此这变得更加复杂,因此我们不能使用StreamReader。请注意,我们必须注意不要在代码点的中间停止读取 - 在很多情况下,单个字符使用多个字节表示,并且在中途停止将是错误的。

const int bufferSize = 1024;
var bytes = new byte[bufferSize];
var chars = new char[Encoding.UTF8.GetMaxCharCount(bufferSize)];
var decoder = Encoding.UTF8.GetDecoder();
// We don't know how long the result will be in chars, but one byte per char is a
// reasonable first approximation. This will expand as necessary.
var result = new StringBuilder(maxAllowedLimit);
int totalReadBytes = 0;
using (var stream = await response.Content.ReadAsStreamAsync())
{
    while (totalReadBytes <= maxAllowedLimit)
    {
        int readBytes = await stream.ReadAsync(
            bytes,
            0,
            Math.Min(maxAllowedLimit - totalReadBytes, bytes.Length));

        // We reached the end of the stream
        if (readBytes == 0)
            break;

        totalReadBytes += readBytes;

        int readChars = decoder.GetChars(bytes, 0, readBytes, chars, 0);
        result.Append(chars, 0, readChars);
    }
}

请注意,您可能想要使用HttpCompletionOption.ResponseHeadersRead,否则HttpClient 无论如何都会去下载整个正文。

如果您乐于限制字符数,那么生活会更轻松:

string result;
using (var reader = new StreamReader(await response.Content.ReadAsStreamAsync()))
{
    char[] chars = new char[maxAllowedLimit];
    int read = reader.ReadBlock(chars, 0, chars.Length);
    result = new string(chars, 0, read);
}

【讨论】:

  • 这使我找到了我的解决方案-希望这是有效的: using (var stream = await response.Content.ReadAsStreamAsync()) { byte[] dataBytes = new byte[responLength]; stream.Read(dataBytes, 0, responseLength); text = Encoding.UTF8.GetString(dataBytes, 0, dataBytes.Length); }
  • @RobMcCabe 正如我多次尝试解释的那样,如果你有任何不是 ascii 的字符,那是不安全的!这是因为任何非 ascii 字符在 utf-8 中都表示为多个字节,如果你在字符的中途停止接收字节,你最终会得到一个无效的字符串!此外,您看不到从流中读取了多少字节,并且如果尚未读取整个主体,您将不会填充字节数组!我的回答有点复杂是有原因的——你不能简单地消除这种复杂性并仍然期望它起作用!
  • @RobMcCabe 请尝试理解这一点。我没有把我的答案放在一起,这样你就可以忽略我试图解释的一切。请阅读我写的内容,并理解为什么我的回答说要做它所做的事情。
  • 好的,我现在明白你的意思 canton7 - 有道理,实际上我的场景可能有其他字符集,例如 ascii!好点!我将在上面使用您的示例!谢谢
猜你喜欢
  • 1970-01-01
  • 2020-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多