Jeśli wygląda brzydko, po prostu usuń niepotrzebne ToCharArraypołączenie.
Jeśli chcesz podzielić według jednego \nlub \rdrugiego, masz dwie opcje:
Użyj literału tablicowego - ale to da ci puste wiersze dla zakończeń linii w stylu Windows \r\n:
var result = text.Split(new[]{'\r','\n'});
Użyj wyrażenia regularnego, jak wskazał Bart:
var result =Regex.Split(text,"\r\n|\r|\n");
Jeśli chcesz zachować puste wiersze, dlaczego jawnie mówisz C #, aby je wyrzucał? ( StringSplitOptionsparametr) - użyj StringSplitOptions.Nonezamiast tego.
Usunięcie ToCharArray spowoduje, że kod będzie specyficzny dla platformy (NewLine może mieć wartość „\ n”)
Konstantin Spirin
1
@Will: na wypadek, gdybyś odnosił się do mnie zamiast do Konstantina: uważam ( zdecydowanie ), że parsowanie kodu powinno starać się działać na wszystkich platformach (tj. Powinien również czytać pliki tekstowe, które zostały zakodowane na innych platformach niż platforma wykonawcza ). Tak więc, jeśli chodzi o analizowanie, jeśli o mnie Environment.NewLinechodzi, nie ma wyjścia. W rzeczywistości ze wszystkich możliwych rozwiązań preferuję to, w którym używane są wyrażenia regularne, ponieważ tylko ono obsługuje poprawnie wszystkie platformy źródłowe.
Konrad Rudolph,
2
@ Hamish Cóż, spójrz tylko na dokumentację wyliczenia lub spójrz na oryginalne pytanie! To jest StringSplitOptions.RemoveEmptyEntries.
Konrad Rudolph
8
A co z tekstem zawierającym „\ r \ n \ r \ n”. string.Split zwróci 4 puste linie, jednak z '\ r \ n' powinno dać 2. Sytuacja pogarsza się, jeśli '\ r \ n' i '\ r' są zmieszane w jednym pliku.
nazwa użytkownika
1
@SurikovPavel Użyj wyrażenia regularnego. Jest to zdecydowanie preferowany wariant, ponieważ działa poprawnie z dowolną kombinacją zakończeń linii.
Konrad Rudolph
134
using (StringReader sr =newStringReader(text)){string line;while((line = sr.ReadLine())!=null){// do something}}
Ważne jest, aby mieć "\r\n"pierwsze miejsce w tablicy, aby było traktowane jako jeden koniec wiersza. Powyższe daje takie same wyniki, jak każde z poniższych rozwiązań Regex:
Dodaj więcej szczegółów, aby Twoja odpowiedź była bardziej przydatna dla czytelników.
Mohit Jain
Gotowe. Dodano również test porównujący jego wydajność z rozwiązaniem Regex.
orad
Nieco szybszy wzorzec z powodu mniejszego cofania się przy tej samej funkcjonalności, jeśli ktoś używa[\r\n]{1,2}
ΩmegaMan
@OmegaMan To ma inne zachowanie. Będzie pasować \n\rlub \n\njako pojedynczy podział linii, co nie jest poprawne.
orad
3
@OmegaMan Jak wygląda Hello\n\nworld\n\nprzypadek krawędzi? Jest to wyraźnie jeden wiersz z tekstem, po którym następuje pusty wiersz, po którym następuje kolejny wiersz z tekstem, po którym następuje pusty wiersz.
Brandin
36
Możesz użyć Regex.Split:
string[] tokens =Regex.Split(input,@"\r?\n|\r");
Edycja: dodano |\rdo konta dla (starszych) terminatorów linii Mac.
To nie zadziała jednak na plikach tekstowych w stylu OS X, ponieważ są one używane tylko \rjako zakończenie linii.
Konrad Rudolph
2
@Konrad Rudolph: AFAIK, '\ r' był używany na bardzo starych systemach MacOS i prawie nigdy się z nim nie ma. Ale jeśli OP musi to uwzględnić (lub jeśli się mylę), to wyrażenie regularne można oczywiście łatwo rozszerzyć, aby je uwzględnić: \ r? \ N | \ r
Bart Kiers
@Bart: Nie sądzę, że jesteś w błędzie, ale już wielokrotnie spotkałem wszystkich możliwych zakończeń linii w mojej karierze jako programista.
Konrad Rudolph
@Konrad, prawdopodobnie masz rację. Lepiej bezpiecznie niż przepraszam.
Bart Kiers
1
@ ΩmegaMan: Spowoduje to utratę pustych wierszy, np. \ N \ n.
Mike Rosoft
9
Jeśli chcesz zachować puste wiersze, po prostu usuń StringSplitOptions.
var result = input.Split(System.Environment.NewLine.ToCharArray());
Nowa linia może mieć wartość „\ n”, a tekst wejściowy może zawierać „\ n \ r”.
Konstantin Spirin
4
Miałem tę drugą odpowiedź, ale ta, oparta na odpowiedzi Jacka , jest znacznie szybsza, może być preferowana, ponieważ działa asynchronicznie, chociaż nieco wolniej.
publicstaticclassStringExtensionMethods{publicstaticIEnumerable<string>GetLines(thisstring str,bool removeEmptyLines =false){
using (var sr =newStringReader(str)){string line;while((line = sr.ReadLine())!=null){if(removeEmptyLines &&String.IsNullOrWhiteSpace(line)){continue;}yieldreturn line;}}}}
Zastanawiam się, czy dzieje się tak dlatego, że w rzeczywistości nie sprawdzasz wyników modułu wyliczającego i dlatego nie jest on wykonywany. Niestety jestem zbyt leniwy, żeby to sprawdzić.
James Holwell,
Tak, faktycznie jest !! Po dodaniu .ToList () do obu wywołań rozwiązanie StringReader jest w rzeczywistości wolniejsze! Na moim komputerze jest 6,74s w porównaniu do 5,10s
JCH2k
To ma sens. Nadal wolę tę metodę, ponieważ umożliwia mi asynchroniczne pobieranie wierszy.
orad
Może powinieneś usunąć nagłówek „lepsze rozwiązanie” z drugiej odpowiedzi i zmodyfikować ten…
Trudno jest prawidłowo obsługiwać mieszane zakończenia linii. Jak wiemy, znaki terminacji linii może być "Line Feed" (ASCII 10, \n, \x0A, \u000A), "Carriage Return" (ASCII 13, \r, \x0D, \u000D), lub niektóre ich kombinacją. Wracając do DOS, Windows używa dwuznakowej sekwencji CR-LF \u000D\u000A, więc ta kombinacja powinna emitować tylko jedną linię. Unix używa jednego \u000A, a bardzo stare komputery Mac używają jednego \u000Dznaku. Standardowy sposób traktowania dowolnych mieszanin tych znaków w pojedynczym pliku tekstowym jest następujący:
każdy znak CR lub LF powinien przeskakiwać do następnej linii Z WYJĄTKIEM ...
... jeśli bezpośrednio po CR następuje LF ( \u000D\u000A), to te dwie razem pomijają tylko jedną linię.
String.Empty jest jedynym wejściem, które nie zwraca żadnych wierszy (każdy znak zawiera co najmniej jedną linię)
Ostatnia linia musi zostać zwrócona, nawet jeśli nie ma ani CR, ani LF.
Powyższa reguła opisuje zachowanie StringReader.ReadLine i powiązanych funkcji, a funkcja pokazana poniżej daje identyczne wyniki. Jest to wydajna funkcja łamania linii C #, która sumiennie implementuje te wytyczne, aby poprawnie obsługiwać dowolną sekwencję lub kombinację CR / LF. Wyliczone wiersze nie zawierają żadnych znaków CR / LF. Puste wiersze są zachowywane i zwracane jako String.Empty.
/// <summary>/// Enumerates the text lines from the string./// ⁃ Mixed CR-LF scenarios are handled correctly/// ⁃ String.Empty is returned for each empty line/// ⁃ No returned string ever contains CR or LF/// </summary>publicstaticIEnumerable<String>Lines(thisString s){int j =0, c, i;char ch;if((c = s.Length)>0)do{for(i = j;(ch = s[j])!='\r'&& ch !='\n'&&++j < c;);yieldreturn s.Substring(i, j - i);}while(++j < c &&(ch !='\r'|| s[j]!='\n'||++j < c));}
Uwaga: Jeśli nie przeszkadza Ci obciążenie związane z tworzeniem StringReaderwystąpienia przy każdym wywołaniu, możesz zamiast tego użyć następującego kodu C # 7 . Jak zauważono, chociaż powyższy przykład może być nieco bardziej wydajny, obie te funkcje dają dokładnie takie same wyniki.
publicstaticIEnumerable<String>Lines(thisString s){
using (var tr =newStringReader(s))while(tr.ReadLine()isString L)yieldreturn L;}
Odpowiedzi:
Jeśli wygląda brzydko, po prostu usuń niepotrzebne
ToCharArray
połączenie.Jeśli chcesz podzielić według jednego
\n
lub\r
drugiego, masz dwie opcje:Użyj literału tablicowego - ale to da ci puste wiersze dla zakończeń linii w stylu Windows
\r\n
:Użyj wyrażenia regularnego, jak wskazał Bart:
Jeśli chcesz zachować puste wiersze, dlaczego jawnie mówisz C #, aby je wyrzucał? (
StringSplitOptions
parametr) - użyjStringSplitOptions.None
zamiast tego.źródło
Environment.NewLine
chodzi, nie ma wyjścia. W rzeczywistości ze wszystkich możliwych rozwiązań preferuję to, w którym używane są wyrażenia regularne, ponieważ tylko ono obsługuje poprawnie wszystkie platformy źródłowe.StringSplitOptions.RemoveEmptyEntries
.źródło
string.Split
lubRegex.Split
)?Aktualizacja: Zobacz tutaj, aby uzyskać alternatywne / asynchroniczne rozwiązanie.
Działa świetnie i jest szybsze niż Regex:
Ważne jest, aby mieć
"\r\n"
pierwsze miejsce w tablicy, aby było traktowane jako jeden koniec wiersza. Powyższe daje takie same wyniki, jak każde z poniższych rozwiązań Regex:Tyle że Regex okazuje się być około 10 razy wolniejszy. Oto mój test:
Wynik:
00: 00: 03.8527616
00: 00: 31.8017726
00: 00: 32,5557128
a oto metoda rozszerzenia:
Stosowanie:
źródło
[\r\n]{1,2}
\n\r
lub\n\n
jako pojedynczy podział linii, co nie jest poprawne.Hello\n\nworld\n\n
przypadek krawędzi? Jest to wyraźnie jeden wiersz z tekstem, po którym następuje pusty wiersz, po którym następuje kolejny wiersz z tekstem, po którym następuje pusty wiersz.Możesz użyć Regex.Split:
Edycja: dodano
|\r
do konta dla (starszych) terminatorów linii Mac.źródło
\r
jako zakończenie linii.Jeśli chcesz zachować puste wiersze, po prostu usuń StringSplitOptions.
źródło
Miałem tę drugą odpowiedź, ale ta, oparta na odpowiedzi Jacka ,
jest znacznie szybsza,może być preferowana, ponieważ działa asynchronicznie, chociaż nieco wolniej.Stosowanie:
Test:
Wynik:
00: 00: 03.9603894
00: 00: 00.0029996
00: 00: 04.8221971
źródło
źródło
Nieco skręcone, ale blok iteratora, aby to zrobić:
Możesz wtedy zadzwonić:
źródło
źródło
Trudno jest prawidłowo obsługiwać mieszane zakończenia linii. Jak wiemy, znaki terminacji linii może być "Line Feed" (ASCII 10,
\n
,\x0A
,\u000A
), "Carriage Return" (ASCII 13,\r
,\x0D
,\u000D
), lub niektóre ich kombinacją. Wracając do DOS, Windows używa dwuznakowej sekwencji CR-LF\u000D\u000A
, więc ta kombinacja powinna emitować tylko jedną linię. Unix używa jednego\u000A
, a bardzo stare komputery Mac używają jednego\u000D
znaku. Standardowy sposób traktowania dowolnych mieszanin tych znaków w pojedynczym pliku tekstowym jest następujący:\u000D\u000A
), to te dwie razem pomijają tylko jedną linię.String.Empty
jest jedynym wejściem, które nie zwraca żadnych wierszy (każdy znak zawiera co najmniej jedną linię)Powyższa reguła opisuje zachowanie StringReader.ReadLine i powiązanych funkcji, a funkcja pokazana poniżej daje identyczne wyniki. Jest to wydajna funkcja łamania linii C #, która sumiennie implementuje te wytyczne, aby poprawnie obsługiwać dowolną sekwencję lub kombinację CR / LF. Wyliczone wiersze nie zawierają żadnych znaków CR / LF. Puste wiersze są zachowywane i zwracane jako
String.Empty
.Uwaga: Jeśli nie przeszkadza Ci obciążenie związane z tworzeniem
StringReader
wystąpienia przy każdym wywołaniu, możesz zamiast tego użyć następującego kodu C # 7 . Jak zauważono, chociaż powyższy przykład może być nieco bardziej wydajny, obie te funkcje dają dokładnie takie same wyniki.źródło