string.split () “Eccezione di memoria insufficiente” durante la lettura di file separati da tabulazione

StackOverflow https://stackoverflow.com/questions/1404435

  •  05-07-2019
  •  | 
  •  

Domanda

Sto usando string.split () nel mio codice C # per leggere un file separato da tabulazioni. Sto affrontando " Eccezione OutOfMemory " come indicato di seguito nell'esempio di codice.

Qui vorrei sapere perché il problema sta arrivando per file di dimensioni 16 MB?

Questo è l'approccio giusto o no?

using (StreamReader reader = new StreamReader(_path))
{
  //...........Load the first line of the file................
  string headerLine = reader.ReadLine();

  MeterDataIPValueList objMeterDataList = new MeterDataIPValueList();
  string[] seperator = new string[1];   //used to sepreate lines of file

  seperator[0] = "\r\n";
  //.............Load Records of file into string array and remove all empty lines of file.................
  string[] line = reader.ReadToEnd().Split(seperator, StringSplitOptions.RemoveEmptyEntries);
  int noOfLines = line.Count();
  if (noOfLines == 0)
  {
    mFileValidationErrors.Append(ConstMsgStrings.headerOnly + Environment.NewLine);
  }
  //...............If file contains records also with header line..............
  else
  {
    string[] headers = headerLine.Split('\t');
    int noOfColumns = headers.Count();

    //.........Create table structure.............
    objValidateRecordsTable.Columns.Add("SerialNo");
    objValidateRecordsTable.Columns.Add("SurveyDate");
    objValidateRecordsTable.Columns.Add("Interval");
    objValidateRecordsTable.Columns.Add("Status");
    objValidateRecordsTable.Columns.Add("Consumption");

    //........Fill objValidateRecordsTable table by string array contents ............

    int recordNumber;  // used for log
    #region ..............Fill objValidateRecordsTable.....................
    seperator[0] = "\t";
    for (int lineNo = 0; lineNo < noOfLines; lineNo++)
    {
      recordNumber = lineNo + 1;
      **string[] recordFields = line[lineNo].Split(seperator, StringSplitOptions.RemoveEmptyEntries);** // Showing me error when we  split columns
      if (recordFields.Count() == noOfColumns)
      {
        //Do processing
      }
È stato utile?

Soluzione

Lo split è implementato male e ha seri problemi di prestazioni se applicato su stringhe enormi. Fare riferimento a questo articolo per dettagli sui requisiti di memoria per funzione split :

  

Cosa succede quando si esegue una divisione su una stringa contenente 1355049 stringhe separate da virgola di 16 caratteri ciascuna, con una lunghezza totale di 25745930 caratteri?

     
      
  1. Una matrice di puntatori all'oggetto stringa: spazio di indirizzi virtuale contiguo di 4 (puntatore di indirizzo) * 1355049 = 5420196 (dimensione array) + 16 (per la tenuta di libri) = 5420212.

  2.   
  3. Spazio indirizzi virtuale non contiguo per stringhe 1355049, ognuna di 54 byte. Ciò non significa che tutte queste 1,3 milioni di stringhe sarebbero sparse in tutto l'heap, ma non saranno allocate su LOH. GC li assegnerà a gruppi su heap Gen0.

  4.   
  5. Split.Function creerà un array interno di System.Int32 [] di dimensioni 25745930, consumando (102983736 byte) ~ 98 MB di LOH, che è molto costoso L.

  6.   

Altri suggerimenti

Prova non a leggere prima l'intero file in un array " reader.ReadToEnd () " Leggi il file riga per riga direttamente ..

  using (StreamReader sr = new StreamReader(this._path))
        {
            string line = "";
            while(( line= sr.ReadLine()) != null)
            {
                string[] cells = line.Split(new string[] { "\t" }, StringSplitOptions.None);
                if (cells.Length > 0)
                {

                }
            }
        }

Consiglierei di leggere riga per riga se puoi, ma a volte dividere per nuove righe non è un requisito.

Quindi puoi sempre scrivere la tua suddivisione efficiente della memoria. Questo ha risolto il problema per me.

    private static IEnumerable<string> CustomSplit(string newtext, char splitChar)
    {
        var result = new List<string>();
        var sb = new StringBuilder();
        foreach (var c in newtext)
        {
            if (c == splitChar)
            {
                if (sb.Length > 0)
                {
                    result.Add(sb.ToString());
                    sb.Clear();
                }
                continue;
            }
            sb.Append(c);
        }
        if (sb.Length > 0)
        {
            result.Add(sb.ToString());
        }
        return result;
    }

Uso il mio. È stato testato con 10 unit test ..

public static class StringExtensions
{

    // the string.Split() method from .NET tend to run out of memory on 80 Mb strings. 
    // this has been reported several places online. 
    // This version is fast and memory efficient and return no empty lines. 
    public static List<string> LowMemSplit(this string s, string seperator)
    {
        List<string> list = new List<string>();
        int lastPos = 0;
        int pos = s.IndexOf(seperator);
        while (pos > -1)
        {
            while(pos == lastPos)
            {
                lastPos += seperator.Length;
                pos = s.IndexOf(seperator, lastPos);
                if (pos == -1)
                    return list;
            }

            string tmp = s.Substring(lastPos, pos - lastPos);
            if(tmp.Trim().Length > 0)
                list.Add(tmp);
            lastPos = pos + seperator.Length;
            pos = s.IndexOf(seperator, lastPos);
        }

        if (lastPos < s.Length)
        {
            string tmp = s.Substring(lastPos, s.Length - lastPos);
            if (tmp.Trim().Length > 0)
                list.Add(tmp);
        }

        return list;
    }
}

Prova a leggere il file in modo lineare anziché dividere l'intero contenuto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top