Najłatwiejszym sposobem na podzielenie ciągu znaków na nowe wiersze w .NET?

806

Muszę podzielić ciąg na nowe wiersze w .NET, a jedynym sposobem, w jaki wiem, aby podzielić ciągi jest metoda Split . Jednak to nie pozwoli mi (łatwo) rozdzielić na nowej linii, więc jaki jest najlepszy sposób, aby to zrobić?

RCIX
źródło
2
Dlaczego by nie miał? Po prostu podziel się na System.Environment.NewLine
aviraldg 10.10.2009
16
Ale musisz zawinąć go w ciąg [] i dodać dodatkowy argument i ... to po prostu niezręczne.
RCIX 10.10.2009

Odpowiedzi:

1412

Aby podzielić na ciąg, musisz użyć przeciążenia, które zajmuje tablicę ciągów:

string[] lines = theText.Split(
    new[] { Environment.NewLine },
    StringSplitOptions.None
);

Edycja:
jeśli chcesz obsługiwać różne typy podziałów linii w tekście, możesz użyć możliwości dopasowania więcej niż jednego łańcucha. Spowoduje to prawidłowe podzielenie każdego rodzaju podziału linii i zachowanie pustych linii i odstępów w tekście:

string[] lines = theText.Split(
    new[] { "\r\n", "\r", "\n" },
    StringSplitOptions.None
);
Guffa
źródło
3
@RCIX: Wysłanie poprawnych parametrów do metody jest nieco niewygodne, ponieważ używasz go do czegoś, co jest o wiele prostsze niż jest w stanie. Przynajmniej tam, przed framework 2
musiałeś
4
@Leandro: Environment.NewLineWłaściwość zawiera domyślną nową linię dla systemu. Na przykład w systemie Windows "\r\n".
Guffa
3
@Leandro: Można przypuszczać, że program dzieli się, \npozostawiając \rna końcu każdej linii, a następnie wypisuje linie z \r\nmiędzy nimi.
Guffa
3
@Samuel: \ri \nsekwencje (między innymi) mają szczególne znaczenie dla kompilatora C #. VB nie ma tych sekwencji ucieczki, więc zamiast nich są używane te stałe.
Guffa,
2
Jeśli chcesz akceptować pliki z wielu różnych systemów operacyjnych, możesz również dodać „\ n \ r” na początku i „\ r” na końcu listy ograniczników. Nie jestem jednak pewien, czy warto trafić w wydajność. ( en.wikipedia.org/wiki/Newline )
user420667
121

Co z używaniem StringReader?

using (System.IO.StringReader reader = new System.IO.StringReader(input)) {
    string line = reader.ReadLine();
}
Łaskawy
źródło
13
To jest mój ulubiony. Owinąłem metodę rozszerzenia i zwróciłem
Ronnie Overby
3
Jest to jedyne nieregexowe rozwiązanie, które znalazłem dla .netcf 3.5
Carl
8
Szczególnie dobrze, gdy dane wejściowe są duże, a kopiowanie ich do tablicy staje się powolne / intensywnie zajmuje pamięć.
Alejandro
1
Jak napisano, ta odpowiedź czyta tylko pierwszy wiersz. Zobacz odpowiedź Steve'a Coopera na whilepętlę, którą należy dodać do tej odpowiedzi.
ToolmakerSteve
48

Powinieneś być w stanie dość łatwo rozdzielić łańcuch, na przykład:

aString.Split(Environment.NewLine.ToCharArray());
nikmd23
źródło
46
W systemie innym niż * nix, który będzie dzielił się na osobne znaki w ciągu Newline, tj. Znaki CR i LF. To spowoduje dodatkowy pusty ciąg między każdą linią.
Guffa,
Popraw mnie, jeśli się mylę, ale czy to nie rozdzieli znaków?
RCIX 10.10.2009
7
@RCIX: Nie, kody \ ri \ n reprezentują pojedyncze znaki. Ciąg „\ r \ n” składa się z dwóch znaków, a nie czterech.
Guffa,
10
jeśli dodasz parametr StringSplitOptions.RemoveEmptyEntries, to będzie działać idealnie.
Ruben,
18
@Ruben: Nie, nie będzie. Serge zasugerował już to w swojej odpowiedzi, a ja już wyjaśniłem, że spowoduje to również usunięcie pustych linii w oryginalnym tekście, które należy zachować.
Guffa,
34

Staraj się unikać używania string.Split dla ogólnego rozwiązania, ponieważ będziesz używać więcej pamięci wszędzie, gdzie używasz funkcji - oryginalny string i podzielona kopia, zarówno w pamięci. Zaufaj mi, że może to być jeden wielki problem, gdy zaczynasz skalować - uruchom 32-bitową aplikację do przetwarzania wsadowego przetwarzającą 100 MB dokumentów, a będziesz miał problem z ośmioma równoległymi wątkami. Nie to, że byłem tam wcześniej ...

Zamiast tego użyj takiego iteratora;

    public static IEnumerable<string> SplitToLines(this string input)
    {
        if (input == null)
        {
            yield break;
        }

        using (System.IO.StringReader reader = new System.IO.StringReader(input))
        {
            string line;
            while( (line = reader.ReadLine()) != null)
            {
                yield return line;
            }
        }
    }

Umożliwi to wykonanie bardziej wydajnej pamięci wokół danych;

foreach(var line in document.SplitToLines()) 
{
    // one line at a time...
}

Oczywiście, jeśli chcesz mieć to wszystko w pamięci, możesz to zrobić;

var allTheLines = document.SplitToLines.ToArray();
Steve Cooper
źródło
Byłem tam ... (analizuję duże pliki HTML i brakuje pamięci). Tak, unikaj string.Split. Używanie string.Split może spowodować użycie Dużych Stert Obiektów (LOH) - ale nie jestem tego w 100% pewien.
Peter Mortensen
Jeśli uczyniłeś SplitToLines metodą statyczną (wydaje się, że dd), to jak możesz to zrobić blah.SplitToLines.. np. document.SplitToLines...?
barlop
ah Widzę, że wprowadzasz thisparametry formalne, co czyni go metodą rozszerzenia.
barlop
26

W oparciu o odpowiedź Guffy w klasie rozszerzającej użyj:

public static string[] Lines(this string source) {
    return source.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
}
Erwin Mayer
źródło
9

W przypadku zmiennej łańcuchowej s:

s.Split(new string[]{Environment.NewLine},StringSplitOptions.None)

Wykorzystuje to definicję zakończeń linii w twoim środowisku. W systemie Windows zakończeniami linii są CR-LF (znak powrotu karetki, przejście do wiersza) lub znakami zmiany znaczenia w języku C # \r\n.

Jest to niezawodne rozwiązanie, ponieważ jeśli zrekombinujesz linie String.Join, równa się to oryginalnemu ciągowi:

var lines = s.Split(new string[]{Environment.NewLine},StringSplitOptions.None);
var reconstituted = String.Join(Environment.NewLine,lines);
Debug.Assert(s==reconstituted);

Czego nie robić:

  • Użyj StringSplitOptions.RemoveEmptyEntries, ponieważ spowoduje to uszkodzenie znaczników, takich jak Markdown, gdzie puste linie mają cel składniowy.
  • Podziel na separatorze new char[]{Environment.NewLine}, ponieważ w systemie Windows spowoduje to utworzenie jednego pustego elementu ciągu dla każdej nowej linii.
Pułkownik Panika
źródło
Zasadniczo ta sama odpowiedź tutaj, co najwyżej oceniona, zaakceptowana, ale ma ładny test jednostkowy i zastrzeżenia.
vapcguy
8

Regex jest również opcją:

    private string[] SplitStringByLineFeed(string inpString)
    {
        string[] locResult = Regex.Split(inpString, "[\r\n]+");
        return locResult;
    }
użytkownik1964822
źródło
7
Jeśli chcesz dopasować linie dokładnie, zachowując puste linie, to regex ciąg byłby lepszy: "\r?\n".
Rory O'Kane,
7

Pomyślałem, że dodam moje dwa bity, ponieważ inne rozwiązania tego pytania nie mieszczą się w klasyfikacji kodów wielokrotnego użytku i nie są wygodne.

Poniższy blok kodu rozszerza stringobiekt, dzięki czemu jest on dostępny jako naturalna metoda podczas pracy z łańcuchami.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
using System.Collections.ObjectModel;

namespace System
{
    public static class StringExtensions
    {
        public static string[] Split(this string s, string delimiter, StringSplitOptions options = StringSplitOptions.None)
        {
            return s.Split(new string[] { delimiter }, options);
        }
    }
}

Możesz teraz użyć .Split()funkcji z dowolnego łańcucha w następujący sposób:

string[] result;

// Pass a string, and the delimiter
result = string.Split("My simple string", " ");

// Split an existing string by delimiter only
string foo = "my - string - i - want - split";
result = foo.Split("-");

// You can even pass the split options parameter. When omitted it is
// set to StringSplitOptions.None
result = foo.Split("-", StringSplitOptions.RemoveEmptyEntries);

Aby podzielić znak nowego wiersza, wystarczy przekazać "\n"lub "\r\n"jako parametr separatora.

Komentarz: Byłoby miło, gdyby Microsoft wdrożył to przeciążenie.

Kraang Prime
źródło
Environment.NewlineKorzystne jest trudne kodowania albo \nczy \r\n.
Michael Blackburn
3
@MichaelBlackburn - To nieprawidłowe stwierdzenie, ponieważ nie ma kontekstu. Environment.Newlinejest kompatybilny z wieloma platformami, a nie do pracy z plikami używającymi innych zakończeń linii niż obecny system operacyjny. Zobacz tutaj, aby uzyskać więcej informacji , więc tak naprawdę zależy to od tego, z czym współpracuje programista. Użycie polecenia Environment.Newlinegwarantuje, że nie ma spójności w typie powrotu linii między systemami operacyjnymi, gdzie „kodowanie na stałe” daje programistom pełną kontrolę.
Kraang Prime
2
@MichaelBlackburn - Nie musisz być niegrzeczny. Po prostu podawałem informacje. .Newlinenie jest magią, pod maską są tylko ciągi, jak podano powyżej, oparte na przełączniku, czy działa na Uniksie, czy na Windowsie. Najbezpieczniejszym zakładem jest najpierw zamiana łańcucha dla wszystkich „\ r \ n”, a następnie podział na „\ n”. Niepowodzenie używania .Newlineoznacza pracę z plikami zapisanymi przez inne programy korzystające z innej metody podziału linii. Działa dobrze, jeśli wiesz, że za każdym razem, gdy plik jest odczytywany, zawsze używa podziału wiersza w bieżącym systemie operacyjnym.
Kraang Prime
To, co słyszę, jest najbardziej czytelnym sposobem (być może wyższym wykorzystaniem pamięci) foo = foo.Replace("\r\n", "\n"); string[] result = foo.Split('\n');. Czy rozumiem poprawnie, że działa to na wszystkich platformach?
John Doe
4

Obecnie używam tej funkcji (na podstawie innych odpowiedzi) w VB.NET:

Private Shared Function SplitLines(text As String) As String()
    Return text.Split({Environment.NewLine, vbCrLf, vbLf}, StringSplitOptions.None)
End Function

Najpierw próbuje podzielić się na lokalną linię nowej platformy, a następnie wraca do każdej możliwej nowej linii.

Do tej pory potrzebowałem tego tylko w jednej klasie. Jeśli to się zmieni, prawdopodobnie zrobię to Publici przeniosę do klasy użyteczności, a może nawet uczynię to metodą rozszerzenia.

Oto jak ponownie dołączyć do linii, dla pewności:

Private Shared Function JoinLines(lines As IEnumerable(Of String)) As String
    Return String.Join(Environment.NewLine, lines)
End Function
Rory O'Kane
źródło
@Samuel - zwróć uwagę na cytaty. W rzeczywistości mają to znaczenie. "\r"= powrót. "\r\n"= powrót + nowa linia. (proszę przejrzeć ten post i zaakceptowane rozwiązanie tutaj
Kraang Prime
@Kraang Hmm .. Dawno nie pracowałem z .NET. Byłbym zaskoczony, gdyby tylu ludzi głosowało złą odpowiedź. Widzę, że skomentowałem odpowiedź Guffy i otrzymałem tam wyjaśnienie. Usunąłem swój komentarz do tej odpowiedzi. Dzięki za heads-upy.
Samuel
2

Właściwie podział powinien zrobić:

//Constructing string...
StringBuilder sb = new StringBuilder();
sb.AppendLine("first line");
sb.AppendLine("second line");
sb.AppendLine("third line");
string s = sb.ToString();
Console.WriteLine(s);

//Splitting multiline string into separate lines
string[] splitted = s.Split(new string[] {System.Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries);

// Output (separate lines)
for( int i = 0; i < splitted.Count(); i++ )
{
    Console.WriteLine("{0}: {1}", i, splitted[i]);
}
MaciekTalaska
źródło
2
Opcja RemoveEmptyEntries usunie puste wiersze z tekstu. Może to być pożądane w niektórych sytuacjach, ale zwykły podział powinien zachować puste linie.
Guffa,
tak, masz rację, właśnie założyłem, że ... no cóż, puste linie nie są interesujące;)
MaciekTalaska 10.10.2009
1
string[] lines = text.Split(
  Environment.NewLine.ToCharArray(), 
  StringSplitOptions.RemoveEmptyStrings);

Opcja RemoveEmptyStrings sprawi, że nie będziesz mieć pustych wpisów, ponieważ \ n następuje po \ r

(Edytuj, aby odzwierciedlić komentarze :) Pamiętaj, że odrzuci również oryginalne puste wiersze w tekście. Zazwyczaj tego właśnie chcę, ale może to nie być twoje wymaganie.

Serge Wautier
źródło
Opcje RemoveEmptyStrings usuwają również puste linie, więc nie działa poprawnie, jeśli w tekście są puste linie.
Guffa,
Prawdopodobnie chcesz zachować oryginalne puste wiersze: \ r \ n \ r \ n
slim
0

Nie wiedziałem o Environment.Newline, ale myślę, że to bardzo dobre rozwiązanie.

Moja próba byłaby:

        string str = "Test Me\r\nTest Me\nTest Me";
        var splitted = str.Split('\n').Select(s => s.Trim()).ToArray();

Dodatkowe .Trim usuwa wszelkie \ r lub \ n, które mogą być nadal obecne (np. Gdy w systemie Windows, ale dzieląc ciąg znaków ze znakami nowej linii). Prawdopodobnie nie jest to najszybsza metoda.

EDYTOWAĆ:

Jak poprawnie wskazano w komentarzach, usuwa to również wszelkie białe znaki na początku wiersza lub przed nowym wierszem. Jeśli chcesz zachować ten biały znak, użyj jednej z innych opcji.

Max
źródło
Trim usunie również wszelkie białe spacje na początku i na końcu linii, na przykład wcięcia.
Guffa,
„.Trim usuwa wszelkie \ r lub \ n, które mogą być nadal obecne” - ouch. Dlaczego zamiast tego nie napisać solidnego kodu?
bzlm 10.10.2009
Może źle zrozumiałem pytanie, ale nie było / nie wiadomo, że należy zachować białe znaki. Oczywiście masz rację, Trim () usuwa również spacje.
Maks.
1
@Max: Wow, poczekaj, aż powiem mojemu szefowi, że kod może robić wszystko, co nie jest specjalnie wykluczone w specyfikacji ...;)
Guffa,
-2

Głupia odpowiedź: napisz do pliku tymczasowego, abyś mógł użyć czcigodnego File.ReadLines

var s = "Hello\r\nWorld";
var path = Path.GetTempFileName();
using (var writer = new StreamWriter(path))
{
    writer.Write(s);
}
var lines = File.ReadLines(path);
Pułkownik Panika
źródło
1
Unikaj var, ponieważ nie definiuje typu zmiennej, więc możesz nie zrozumieć, jak korzystać z tego obiektu lub co reprezentuje ten obiekt. Dodatkowo pokazuje to pisanie linii i nawet nie określa nazwy pliku, więc wątpię, żeby to zadziałało. Następnie podczas czytania ścieżka do pliku nie jest ponownie określona. Zakładając, że pathto C:\Temp\test.txtnależy wtedy string[] lines = File.ReadLines(path);.
vapcguy
1
@vapcguy co właśnie przeczytałem? - Poleciłbym ponownie przeczytać post lub debugować go w programie konsoli, ponieważ wszystko, co powiedziałeś, jest po prostu złe | ścieżka jest ustawiona na Path.GetTempFileName | var jest powszechną i zalecaną definicją w języku C # - przy okazji definiuje typ zmiennej ...... EDYCJA: Nie twierdzę, że to dobre rozwiązanie
koanbock
@koanbock Ok, więc przejrzałem Path.GetTempFileName msdn.microsoft.com/en-us/library/... i mówi, że tworzy plik zerowy i zwraca „pełną ścieżkę tego pliku”. Mógłbym przysiąc, że próbowałem tego wcześniej i dał wyjątek, ponieważ nie znalazł pliku, ale zamiast tego zwrócił lokalizację folderu. Znam argumenty za użyciem var, ale powiedziałbym, że NIE jest to zalecane, ponieważ nie pokazuje, czym jest obiekt zmiennej. To zaciemnia to.
vapcguy
-3
using System.IO;

string textToSplit;

if (textToSplit != null)
{
    List<string> lines = new List<string>();
    using (StringReader reader = new StringReader(textToSplit))
    {
        for (string line = reader.ReadLine(); line != null; line = reader.ReadLine())
        {
            lines.Add(line);
        }
    }
}
maciej
źródło
-5

Właściwie to bardzo łatwe.

VB.NET:

Private Function SplitOnNewLine(input as String) As String
    Return input.Split(Environment.NewLine)
End Function

DO#:

string splitOnNewLine(string input)
{
    return input.split(environment.newline);
}
Skillaura13
źródło
4
Całkowicie niepoprawny i nie działa. Dodatkowo w języku C # jest Environment.NewLinetak jak w VB.
vapcguy
Zobacz identyfikator końca linii w VB.NET? dla różnych opcji nowej linii.
Peter Mortensen