如何使用 Encoding.ASCII.GetBytes 处理大量数据? (抛出 OutOfMemoryException)

Posted

技术标签:

【中文标题】如何使用 Encoding.ASCII.GetBytes 处理大量数据? (抛出 OutOfMemoryException)【英文标题】:How to use Encoding.ASCII.GetBytes with large amounts of data? (throws OutOfMemoryException) 【发布时间】:2021-12-16 18:20:18 【问题描述】:

将字符串转换为字节数组时出现内存不足异常。

if (message.Contains("REQZ1S"))

    string strMsg = "REQZID;";
    try
    
        var tmp = LoadCellService.readFromExcel(LoadCellModel.LoadCellRowList.Where(x => x.Stage == 1).ToList(), 1);
        LogHelper.StartTo(nameof(LoadCellSiemensOPCModel), $"tmp count: tmp.Item1.Count");
        if (tmp.Item1 != null)
        
            tmp.Item1.ForEach(
                z => LoadCellModel.LoadCellRowList.Where(x => x.Stage == z.Stage && x.RowIndex == z.RowIndex).First().LoadCellRowColumnList = z.LoadCellRowColumnList
            );

            LoadCellModel.LoadCellRowList.ForEach(
                x => x.LoadCellRowColumnList.ForEach(y =>
                
                    LogHelper.StartTo("temp", y.LoadCellRowColumnKey + ";" + y.LoadCellRowColumnValue);
                    if (y.LoadCellRowColumnKey == "Distance")
                    
                        strMsg += strMsg + ";" + y.LoadCellRowColumnValue;
                    
                )
            );

            LogHelper.KeyValue(nameof(LoadCellSiemensOPCModel), "message2", strMsg);
            byte[] msg = System.Text.Encoding.ASCII.GetBytes(strMsg + "\r");
            Stream.Write(msg, 0, msg.Length);
            LogHelper.KeyValue(nameof(LoadCellSiemensOPCModel), "message", strMsg);
            LogHelper.SiemensOPCTrace(nameof(LoadCellSiemensOPCModel), $"Write <<< strMsg");
        
        else
        
            byte[] msg = System.Text.Encoding.ASCII.GetBytes("REQZ1E;\r");
            Stream.Write(msg, 0, msg.Length);
            LogHelper.SiemensOPCTrace(nameof(LoadCellSiemensOPCModel), $"Write <<< REQZ1E;");
        
        LogHelper.Done(nameof(LoadCellSiemensOPCModel), $"Write <<< REQZ1S;");
    
    catch (Exception EX)
               
        LogHelper.KeyValue(nameof(LoadCellSiemensOPCModel), "message1", strMsg);
        LogHelper.Error("TEMP", EX);
    

我怀疑这部分需要添加一些适当的逻辑:

 byte[] msg = System.Text.Encoding.ASCII.GetBytes(strMsg + "\r");
 Stream.Write(msg, 0, msg.Length); 

【问题讨论】:

你为什么要使用它?为什么不使用StreamWriter 你的问题不在GetBytes。问题是您通过在循环中使用+=非常低效地 连接字符串,并且您可能会将堆碎片化。而是使用StringBuilder - 或者按照@LLama 的建议,重写整个函数以使用StreamWriter 另外,您不应该使用List.ForEach(该方法确实需要从.NET imo 中删除,没有理由使用它)。始终使用原生 foreach 语句。 我刚刚浏览了你的代码并正确地重新缩进了它,我感到很羞愧——你有太多的嵌套循环,运行时复杂性太可怕了——这个程序必须花费几分钟才能运行它应该只需要几毫秒。 感谢您提供的所有解决方案。现在我想在我的代码中添加计数,得到任何解决方案,我仍在使用我的旧代码,只需修改 "strMsg += strMsg + ";" + y.LoadCellRowColumnValue;" TO "strMsg += ";" + y.LoadCellRowColumnValue;"只要。这是因为稍后会得到如下示例数据:[REQZID;3;92.26;;91.79;91.79;;]。 3 是我们拥有的总数据集 【参考方案1】:

问题不是 System.Text.Encoding.ASCII.GetBytes("REQZ1E;\r"); - 问题就在这里:

LoadCellModel.LoadCellRowList.ForEach(
    x => x.LoadCellRowColumnList.ForEach(y => 
    // ...
    strMsg += strMsg + ";" + y.LoadCellRowColumnValue;
    // ...

使用+= 运算符连接字符串会导致整个字符串的完整复制和重新分配

例如,如果您有一个循环迭代 100 次(10*x10*y 总共 100 个),并且在每次迭代中它向字符串添加 50 个字符(因此最终输出长度为 5000 个字符), 但它也会重新复制所有内容,所以一旦你超过了 1000 个字符,计算机现在每次都必须复制一个完整的千字节 - 你通过复制 999、998、997 到达那里, 996 等字符 - 运行时复杂度非常糟糕(大约为 O(n^2))。

您可以这样做:

(为简洁起见,省略了外部iftry/catch 语句和Logging 调用) 我看到您正在写信给Stream,因此请使用带有所需编码集的StreamWriter。 我用foreach 语句替换了您的.ForEach 调用。
var tmp = LoadCellService.readFromExcel( LoadCellModel.LoadCellRowList.Where(x => x.Stage == 1 ).ToList() , 1 );

if (tmp.Item1 != null)

    // This can be further optimized by loading `LoadCellModel.LoadCellRowList` into a dictionary by an appropriate key.
    foreach( var z in tmp.Item1 )
    
        var columnList = LoadCellModel.LoadCellRowList
            .Where( x =>
                x.Stage    == z.Stage &&
                x.RowIndex == z.RowIndex
            )
            .First();

        columnList.LoadCellRowColumnList = z.LoadCellRowColumnList;
    

    using( StreamWriter wtr = new StreamWriter( stream, Encoding.ASCII ) )
    
        foreach( var x in LoadCellModel.LoadCellRowList )
        
            foreach( var y in x.LoadCellRowColumnList.Where( yc => yc.LoadCellRowColumnKey == "Distance" ) )
            
                wtr.Write( ';' );
                wtr.Write( y.LoadCellRowColumnValue );
            
        

        wtr.Flush();
    

else

    byte[] msg = Encoding.ASCII.GetBytes("REQZ1E;\r");
    stream.Write(msg, 0, msg.Length);

【讨论】:

以上是关于如何使用 Encoding.ASCII.GetBytes 处理大量数据? (抛出 OutOfMemoryException)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用本机反应创建登录以及如何验证会话

如何在自动布局中使用约束标识符以及如何使用标识符更改约束? [迅速]

如何使用 AngularJS 的 ng-model 创建一个数组以及如何使用 jquery 提交?

如何使用laravel保存所有行数据每个行名或相等

如何使用 Math.Net 连接矩阵。如何使用 Math.Net 调用特定的行或列?

WSARecv 如何使用 lpOverlapped?如何手动发出事件信号?