.NET System.OutOfMemoryException على String.Split() لملف CSV بحجم 120 ميجابايت

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

  •  03-07-2019
  •  | 
  •  

سؤال

أنا أستخدم C# لقراءة ملف CSV بنص عادي يبلغ حجمه 120 ميجابايت تقريبًا.في البداية قمت بالتحليل من خلال قراءته سطرًا تلو الآخر، لكنني قررت مؤخرًا أن قراءة محتويات الملف بالكامل في الذاكرة أولاً كانت أسرع عدة مرات.التحليل بطيء جدًا بالفعل لأن ملف CSV يحتوي على فواصل مضمنة داخل علامات الاقتباس، مما يعني أنه يتعين علي استخدام تقسيم regex.هذا هو الوحيد الذي وجدته يعمل بشكل موثوق:

string[] fields = Regex.Split(line, 
@",(?!(?<=(?:^|,)\s*\x22(?:[^\x22]|\x22\x22|\\\x22)*,)
(?:[^\x22]|\x22\x22|\\\x22)*\x22\s*(?:,|$))");
// from http://regexlib.com/REDetails.aspx?regexp_id=621

من أجل إجراء التحليل بعد قراءة المحتويات بالكامل في الذاكرة، أقوم بتقسيم سلسلة على حرف السطر الجديد للحصول على مصفوفة تحتوي على كل سطر.ومع ذلك، عندما أفعل ذلك على ملف بحجم 120 ميجابايت، أحصل على ملف System.OutOfMemoryException.لماذا تنفد الذاكرة بهذه السرعة عندما يكون لدى جهاز الكمبيوتر الخاص بي 4 جيجابايت من ذاكرة الوصول العشوائي؟هل هناك طريقة أفضل لتحليل ملف CSV المعقد بسرعة؟

هل كانت مفيدة؟

المحلول

ويمكنك الحصول على OutOfMemoryException عن أساسا أي حجم تخصيص. عند تخصيص قطعة الذاكرة كنت طالبا حقا للحصول على قطعة المستمر للذاكرة من الحجم المطلوب. وإذا كان هذا لا يمكن أن يكون تكريم سترى OutOfMemoryException.

ويجب أيضا أن تكون على علم بأن إلا إذا كنت تقوم بتشغيل ويندوز 64 بت، يتم تقسيم الخاص بك GB RAM 4 إلى 2 GB مساحة النواة والفضاء المستخدم 2 GB، لذلك التطبيق. NET الخاص بك لا يمكن الوصول إلى أكثر من 2 GB لكل الافتراضية.

عند القيام بعمليات سلسلة في. NET هل خطر خلق الكثير من السلاسل المؤقتة يرجع ذلك إلى حقيقة أن سلاسل. NET هي ثابتة. لذلك قد ترى استخدام الذاكرة ترتفع بشكل كبير جدا.

نصائح أخرى

ولا لفة محلل الخاصة بك إلا إذا كان لديك. لقد كان الحظ مع هذا واحد:

A CSV قارئ سريع

إذا أي شيء آخر يمكن أن ننظر تحت غطاء محرك السيارة، ونرى كيف شخص آخر يفعل ذلك.

إذا كان لديك الملف بأكمله مقروءًا في سلسلة، فمن المحتمل أن تستخدم ملف قارئ السلسلة.

StringReader reader = new StringReader(fileContents);
string line;
while ((line = reader.ReadLine()) != null) {
    // Process line
}

يجب أن يكون هذا تقريبًا نفس البث من ملف مع اختلاف المحتويات الموجودة في الذاكرة بالفعل.

تحرير بعد الاختبار

جربت ما ورد أعلاه باستخدام ملف بحجم 140 ميجابايت حيث تتكون المعالجة من زيادة متغير الطول باستخدام line.Length.استغرق هذا حوالي 1.6 ثانية على جهاز الكمبيوتر الخاص بي.بعد هذا حاولت ما يلي:

System.IO.StreamReader reader = new StreamReader("D:\\test.txt");
long length = 0;
string line;
while ((line = reader.ReadLine()) != null)
    length += line.Length;

وكانت النتيجة حوالي 1 ثانية.

بالطبع قد يختلف عدد الأميال المقطوعة، خاصة إذا كنت تقرأ من محرك أقراص الشبكة أو تستغرق عملية المعالجة وقتًا طويلاً بما يكفي للبحث عن القرص الصلب في مكان آخر.ولكن أيضًا إذا كنت تستخدم FileStream لقراءة الملف ولا تقوم بالتخزين المؤقت.يوفر StreamReader التخزين المؤقت الذي يعزز القراءة بشكل كبير.

وأنت قد لا تكون قادرة على تخصيص كائن واحد مع أن الذاكرة القريبة بكثير، ولا يجب أن تتوقع أن تكون قادرة على. تدفق هي الطريقة العادية للقيام بذلك، ولكن كنت على حق أنه قد يكون أبطأ (على الرغم من أنني لا أعتقد أنه يجب أن يكون عادة أبطأ تماما أن كثيرا).

وكحل وسط، قد تتمكن من محاولة قراءة الجزء الأكبر من ملف (ولكن لا يزال كل شيء) في آن واحد، مع وظيفة مثل StreamReader.ReadBlock()، ومعالجة كل جزء على حدة.

وكما يقول ملصقات أخرى، وOutOfMemory لأنه لا يمكن العثور على قطعة متجاورة من الذاكرة من الحجم المطلوب.

ولكن، ويقول لك أن تفعل خط إعراب سطرا كان أسرع بعدة مرات من قراءة كل ذلك في دفعة واحدة ومن ثم القيام معالجة الخاص بك. بهذا المعنى فقط يجعل لو كنت متابعة نهج ساذج للقيام حجب يقرأ، على سبيل المثال (في رمز زائف):

while(! file.eof() )
{
    string line = file.ReadLine();
    ProcessLine(line);
}

ويجب عليك بدلا من استخدام الجري، حيث شغل في تيار الخاصة بك من قبل في كتابة () يدعو من موضوع البديل الذي يقرأ الملف، حتى لا يتم حظر قراءة ملف بأي ProcessLine الخاص بك () لا، والعكس بالعكس. يجب أن تكون على قدم المساواة مع أداء قراءة الملف بأكمله دفعة واحدة ومن ثم القيام معالجة الخاص بك.

وربما يجب عليك محاولة CLR التعريف لتحديد استخدام الذاكرة الفعلي. قد يكون أن هناك حدودا الذاكرة الأخرى من RAM النظام الخاص بك. على سبيل المثال إذا كان هذا هو تطبيق IIS، الذاكرة محدودة بسبب تجمعات التطبيقات.

ومع هذه المعلومات الشخصية قد تجد أنك بحاجة إلى استخدام تقنية أكثر قابلة مثل يتدفقون من ملف CSV التي حاولت أصلا.

أنت ينفد من الذاكرة على المكدس، وليس كومة.

هل يمكن أن تحاول إعادة العوملة التطبيق بحيث كنت معالجة المدخلات في "قطع" أكثر سهولة من البيانات بدلا من معالجة 120MB في وقت واحد.

وأنا أتفق مع معظم الجميع هنا، تحتاج إلى استخدام الدفق.

وأنا لا أعرف إذا كان أي شخص قد قال حتى الآن، ولكن يجب أن ننظر إلى طريقة exstention.

وأنا أعلم، على وجه اليقين، الأيدي، أفضل تقنية CSV تقسيم على صافي / CLR غير هذا واحد

وهذا الأسلوب ولدت لي + 10GB XML الناتج من المدخلات CSV، بما في ذلك المرشحات المدخلات exstensive وقبل كل شيء، أسرع من أي شيء آخر رأيته.

ويجب أن تقرأ جزءا في منطقة عازلة والعمل على ذلك. ثم قرأ قسما آخر وهلم جرا.

وهناك العديد من المكتبات هناك من شأنها أن تفعل هذا بشكل فعال بالنسبة لك. I الحفاظ على واحدة تسمى CsvHelper . وهناك الكثير من الحالات الحافة التي تحتاج إلى التعامل معها، مثل عندما فاصلة أو خط النهاية في وسط الميدان.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top