2010-01-04 65 views
1

我正在開發一個日誌解析器,並且正在讀取大於150MB的字符串文件.-這是我的方法,有什麼方法可以優化While語句中的內容嗎?問題是,在消耗了大量的memory.-我也有一個StringBuilder試圖面臨着同樣的內存comsuption.-如何優化此算法中的內存使用情況?

private void ReadLogInThread() 
     { 
      string lineOfLog = string.Empty; 

      try 
      { 
       StreamReader logFile = new StreamReader(myLog.logFileLocation); 
       InformationUnit infoUnit = new InformationUnit(); 

       infoUnit.LogCompleteSize = myLog.logFileSize; 

       while ((lineOfLog = logFile.ReadLine()) != null) 
       { 
        myLog.transformedLog.Add(lineOfLog); //list<string> 
        myLog.logNumberLines++; 

        infoUnit.CurrentNumberOfLine = myLog.logNumberLines; 
        infoUnit.CurrentLine = lineOfLog; 
        infoUnit.CurrentSizeRead += lineOfLog.Length; 


        if (onLineRead != null) 
         onLineRead(infoUnit); 
       } 
      } 
      catch { throw; } 
     } 

提前感謝!

EXTRA: 林節約每一行,因爲讀取日誌後,我需要檢查每存儲line.-語言中的一些信息是C#

+0

什麼是語言? – 2010-01-04 19:47:10

+2

保留每一行的原因是什麼?什麼是內存配置文件顯示爲最昂貴的對象或對象?你想要的內存門檻是多少? – 2010-01-04 19:50:56

+0

你使用多少內存,你認爲合理嗎? – Dolphin 2010-01-04 20:12:45

回答

3

如果您的日誌行實際上可以解析爲數據行表示形式,則可以實現內存經濟。

下面是一個典型的日誌行我能想到的:

事件在:2019年1月5日0:24:32.435,原因:操作,種類:DataStoreOperation,操作狀態:成功

該行在內存中佔用200個字節。 與此同時,以下表示只需要貝洛16個字節:

Enum LogReason { Operation, Error, Warning }; 
Enum EventKind short { DataStoreOperation, DataReadOperation }; 
Enum OperationStatus short { Success, Failed }; 

LogRow 
{ 
    DateTime EventTime; 
    LogReason Reason; 
    EventKind Kind; 
    OperationStatus Status; 
} 

另一種優化的可能性只是解析一行字符串標記的陣列, 這種方式,您可以利用字符串的實習。例如,如果單詞「DataStoreOperation」需要36個字節,並且文件中有1000000個Entiries,則經濟性爲(18 * 2 - 4)* 1000000 = 32 000 000字節。

0

內存使用量不斷上升,因爲你根本將它們添加到列表<字符串>,不斷增長。如果你想使用更少的內存,你可以做的一件事就是將數據寫入磁盤,而不是保持在範圍內。當然,這會大大降低速度。

另一種方法是在字符串數據存儲到列表時壓縮字符串數據,並將其解壓縮出來,但我認爲這不是一個好方法。

側面說明:

您需要添加在你的StreamReader using塊。

using (StreamReader logFile = new StreamReader(myLog.logFileLocation)) 
0

考慮這個實施:(我所說的C/C++,替代C作爲必要#)

Use fseek/ftell to find the size of the file. 

Use malloc to allocate a chunk of memory the size of the file + 1; 
Set that last byte to '\0' to terminate the string. 

Use fread to read the entire file into the memory buffer. 
You now have char * which holds the contents of the file as a 
string. 

Create a vector of const char * to hold pointers to the positions 
in memory where each line can be found. Initialize the first element 
of the vector to the first byte of the memory buffer. 

Find the carriage control characters (probably \r\n) Replace the 
\r by \0 to make the line a string. Increment past the \n. 
This new pointer location is pushed back onto the vector. 

Repeat the above until all of the lines in the file have been NUL 
terminated, and are pointed to by elements in the vector. 

Iterate though the vector as needed to investigate the contents of 
each line, in your business specific way. 

When you are done, close the file, free the memory, and continue 
happily along your way. 
+0

這只是不會在C#環境中工作。 C#與C中的char *沒有任何區別,你說的大多數可以用C#完成,但最後一個字節*(與char *最接近的模擬)仍然必須轉換爲String對象纔有用,這將馬無論如何都要複製一份。 – Dolphin 2010-01-04 20:33:56

+0

很酷。 我在我的環境中多次使用這種技術,並取得了良好的效果。 – EvilTeach 2010-01-04 20:37:54

0

1)壓縮字符串您存儲之前(即見System.IO.Compression和GZipStream)。這可能會殺死你的程序的性能,因爲你必須解壓才能讀取每一行。

2)刪除任何額外的空白字符或常用詞,你可以不用。也就是說,如果你能夠理解日誌用「the,a,of ...」來表達的意思,請將其刪除。此外,縮短任何常見詞彙(即將「錯誤」更改爲「錯誤」和「警告」更改爲「wrn」)。這會減緩這一過程的步驟,但不應該影響其他方面的表現。

1

我不確定它是否適合您的項目,但您可以將結果存儲在StringBuilder而不是字符串列表中。

例如,我的機器上這個過程需要250MB的內存加載後(文件爲50MB):

static void Main(string[] args) 
{ 
    using (StreamReader streamReader = File.OpenText("file.txt")) 
    { 
     var list = new List<string>(); 
     string line; 
     while ((line=streamReader.ReadLine())!=null) 
     { 
      list.Add(line); 
     } 
    } 
} 

在另一方面,該代碼的過程將只需要100MB:

static void Main(string[] args) 
{ 
    var stringBuilder = new StringBuilder(); 
    using (StreamReader streamReader = File.OpenText("file.txt")) 
    { 
     string line; 
     while ((line=streamReader.ReadLine())!=null) 
     { 
      stringBuilder.AppendLine(line); 
     } 
    } 
} 
+0

嘿,這是一個很好的。讓我試試這種方法,我會讓你知道:D謝謝 – MRFerocius 2010-01-04 21:05:31

+0

var text = File.ReadAllText(「file.txt」);用streamreader打開文件只是爲了重新構建一個包含所有行的字符串,並沒有什麼幫助 – StarPacker 2010-01-04 22:21:41

2

嘗試使您的算法順序。

如果您不需要按列表中的索引對行進行隨機訪問,則使用IEnumerable而不是列表可以幫助您在內存中播放內容,同時保持與列表一樣的語義。

IEnumerable<string> ReadLines() 
{ 
    // ... 
    while ((lineOfLog = logFile.ReadLine()) != null) 
    { 
    yield return lineOfLog; 
    } 
} 
//... 
foreach(var line in ReadLines()) 
{ 
    ProcessLine(line); 
} 
+0

這也是一個很好的方法.-我會試試看。 – MRFerocius 2010-01-04 21:30:05

0

什麼編碼是你的原始文件?如果是ascii,那麼只需要單獨的字符串就會佔用文件大小的兩倍,以加載到陣列中。 C#字符是2個字節,C#string除字符外還爲每個字符串添加了額外的20個字節。

就你而言,由於它是一個日誌文件,因此你可以利用這個消息中有很多重複的事實。您很可能可以將傳入的行解析爲可減少內存開銷的數據結構。例如,如果您在日誌文件中有時間戳,則可以將其轉換爲一個日期時間值,即8 bytes。即使是一個簡短的時間戳1/1/10也會將12個字節添加到字符串的大小,並且帶時間信息的時間戳會更長。您的日誌流中的其他標記可能能夠以類似的方式變成代碼或枚舉。

即使您將值作爲字符串保留下來,如果您可以將其分解爲大量使用的碎片,或者移除根本不需要的樣板,也可以減少內存使用量。如果有很多常見的字符串,你可以使用Intern這些字符串,無論你擁有多少字符串,只需支付1字符串。

0

如果您必須存儲原始數據,並且假設您的日誌主要是ASCII,那麼您可以通過在內部存儲UTF8字節來保存一些內存。字符串在內部是UTF16,所以你要爲每個字符存儲一個額外的字節。所以通過切換到UTF8,你可以減少一半的內存使用量(不包括班級開銷,這仍然很重要)。然後,您可以根據需要將其轉換回普通字符串。

static void Main(string[] args) 
{ 
    List<Byte[]> strings = new List<byte[]>(); 

    using (TextReader tr = new StreamReader(@"C:\test.log")) 
    { 
     string s = tr.ReadLine(); 
     while (s != null) 
     { 
      strings.Add(Encoding.Convert(Encoding.Unicode, Encoding.UTF8, Encoding.Unicode.GetBytes(s))); 
      s = tr.ReadLine(); 
     } 
    } 

    // Get strings back 
    foreach(var str in strings) 
    { 
     Console.WriteLine(Encoding.UTF8.GetString(str)); 
    } 
}