C #: File.ReadLines () vs File.ReadAllLines () - un kāpēc man tas būtu jāinteresē?

Pirms pāris nedēļām es un divas komandas, ar kurām strādāju, saskārāmies ar diskusiju par efektīviem lielu teksta failu apstrādes veidiem.

Tas izsauca dažas citas iepriekšējās diskusijas, kuras man bija par šo tēmu iepriekš, un jo īpaši par ražas ienesīguma izmantošanu C # (par kuru es, iespējams, runāšu nākamajā emuāra ierakstā). Tātad, es domāju, ka būtu labs izaicinājums parādīt, kā C # var efektīvi mērogot, apstrādājot lielus datu fragmentus.

Izaicinājums

Apspriežamā problēma ir šāda:

  • Pieņemsim, ka ir liels CSV fails, teiksim ~ 500 MB iesācējiem
  • Programmai jāiet cauri visām faila rindām, parsēt to un veikt dažus kartēšanas / samazināšanas aprēķinus

Un jautājums šajā diskusijas brīdī ir:

Kā visefektīvāk uzrakstīt kodu, kas šo mērķi var sasniegt? Vienlaikus ievērojot:
i) samazināt izmantoto atmiņu un
ii) samaziniet programmas koda rindas (protams, saprātīgā apjomā)

Argumenta labad mēs varētu izmantot StreamReader, taču tas novestu pie tā, ka tiktu uzrakstīts vairāk koda, kas nepieciešams, un faktiski C # jau ir izveidojis File.ReadAllLines () un File.ReadLines () ērtības metodes. Tāpēc mums tie būtu jāizmanto!

Parādiet man kodu

Piemēra labad apsvērsim programmu, kas:

  1. Tiek izmantots teksta fails kā ievade, kur katra rinda ir vesels skaitlis
  2. Aprēķina visu failā esošo skaitļu summu

Šī piemēra labad mēs izlaidīsim diezgan daudz apstiprināšanas ziņojumu :-)

C # to var izdarīt ar šādu kodu:

var sumOfLines = File.ReadAllLines (filePath)
    .Izvēlieties (līnija => int.Parse (līnija))
    .Sum ()

Diezgan vienkārši, vai ne?

Kas notiek, kad mēs šo programmu barojam ar lielu failu?

Ja mēs palaižam šo programmu 100 MB faila apstrādei, to iegūstam:

  • Šīs skaitļošanas pabeigšanai 2GB RAM patērēja atmiņu
  • Daudz GC (katrs dzeltenais elements ir GC palaišana)
  • 18 sekundes, lai pabeigtu izpildi
BTW, 500MB faila pievienošana šim kodam izraisīja programmas avāriju ar OutOfMemoryException Fun, vai ne?

Tagad tā vietā izmēģināsim File.ReadLines ()

Nomainīsim kodu, lai File.ReadAllLines () vietā izmantotu File.ReadLines (), un redzēsim, kā tas notiek:

var sumOfLines = File.ReadLines (filePath)
    .Izvēlieties (līnija => int.Parse (līnija))
    .Sum ()

Palaižot to, mēs tagad iegūstam:

  • Patērē 12 MB RAM, nevis 2 GB (!!)
  • Tikai 1 GC skrējiens
  • 18 sekunžu vietā jāpabeidz 10 sekundes

Kāpēc tas notiek?

TL; DR galvenā atšķirība ir tā, ka File.ReadAllLines () veido virkni [], kurā ir katra faila rinda, un tam nepieciešams pietiekami daudz atmiņas, lai ielādētu visu failu; pretēji File.ReadLines (), kas pabaro programmu katrā rindā vienlaikus, vienas rindas ielādēšanai nepieciešama tikai atmiņa.

Nedaudz sīkāk:

File.ReadAllLines () nolasa visu failu uzreiz un atgriež virkni [], kur katrs masīva vienums atbilst faila rindai. Tas nozīmē, ka programmai ir nepieciešams tikpat daudz atmiņas kā faila lielumam, lai no faila ielādētu saturu. Turklāt nepieciešamā atmiņa, lai parsētu VISUS virknes elementus un pēc tam aprēķinātu summu ()

No otras puses, File.ReadLines () izveido failā skaitītāju, nolasot to pa rindām (faktiski izmantojot StreamReader.ReadLine ()). Tas nozīmē, ka katra rinda tiek nolasīta, pārveidota un pievienota daļējai summai režīmā-be-line.

Secinājums

Šī tēma varētu šķist zema ieviešanas detaļa, taču tā patiesībā ir ļoti svarīga, jo tā nosaka programmas mērogu, ja tiek barota ar lielu datu kopu.

Ir svarīgi, lai programmatūras izstrādātāji spētu paredzēt šāda veida situācijas, jo nekad nevar zināt, vai kāds gatavojas sniegt lielu ieguldījumu, kas izstrādes posmā nebija paredzēts.

Turklāt LINQ ir pietiekami elastīgs, lai netraucēti apstrādātu šos divus scenārijus, un nodrošina izcilu efektivitāti, ja to lieto kopā ar kodu, kas nodrošina vērtību “straumēšanu”.

Tas nozīmē, ka ne visam jābūt sarakstam vai T [], kas nozīmē, ka visa datu kopa ir ielādēta atmiņā. Izmantojot IEnumerable , mēs padarām mūsu kodu vispārīgu izmantošanai ar metodēm, kuras visu datu kopu nodrošina atmiņā vai kuras nodrošina vērtības straumēšanas režīmā.