Dizeyi satır satır okuma


144

Çok uzun olmayan bir dize verildiğinde, satır satır okumanın en iyi yolu nedir?

Yapabileceğini biliyorum:

BufferedReader reader = new BufferedReader(new StringReader(<string>));
reader.readLine();

Başka bir yol, eol üzerindeki alt dizeyi almak olacaktır:

final String eol = System.getProperty("line.separator");
output = output.substring(output.indexOf(eol + 1));

Bunu yapmanın başka basit yolları var mı? Yukarıdaki yaklaşımlarla ilgili hiçbir sorunum yok, sadece daha basit ve daha verimli görünebilecek bir şey biliyor musunuz?


5
Peki gereksiniminiz "satır satır oku" dedi, bu da bir kerede bellekteki tüm satırlara ihtiyacınız olmadığını ima ediyor, bu yüzden BufferedReader veya Tarayıcı yaklaşımına bağlı kalacağım, hangisi daha rahat hissediyorsanız (bilmiyorum) ki bu daha verimlidir). Bu şekilde bellek gereksinimleriniz daha az olur. Ayrıca, gelecekte bir dosyadan veri okuyarak daha büyük dizeler kullanmak için uygulamayı "ölçeklendirmenize" olanak tanır.
camickr

Yanıtlar:


133

splitString yöntemini de kullanabilirsiniz :

String[] lines = myString.split(System.getProperty("line.separator"));

Bu, tüm satırları kullanışlı bir dizide verir.

Split'in performansını bilmiyorum. Düzenli ifadeler kullanır.


3
Ve umarım çizgi ayırıcının içinde regex karakterleri yoktur. :)
Tom Hawtin - tackline

47
"line.separator" zaten güvenilir değil. Kodun Unix üzerinde çalıştığı için (örneğin), dosyanın Windows stili "\ r \ n" satır ayırıcılarına sahip olması nasıl engellenir? BufferedReader.readLine () ve Scanner.nextLine () her zaman üç ayırıcı stilinin tümünü kontrol eder.
Alan Moore

6
Bu yorumun gerçekten eski olduğunu biliyorum, ama ... Soru dosyalardan hiç bahsetmiyor. Dizenin bir dosyadan okunmadığı varsayılarak, bu yaklaşım muhtemelen güvenlidir.
Jolta

@Jolta Bu, manuel olarak oluşturulmuş Dizeler için bile güvenli değildir, eğer pencerelerdeyseniz ve Dizenizi '\ n' ile oluşturduysanız ve daha sonra line.separator'da bölerseniz hiçbir satır almazsınız.
masterxilo

Ha? Kullanarak linux kutumda bir dize oluşturursam line.separatorve başka biri kullanarak pencereleri okursa line.separator, hala kambur kalır. Bu yetersiz kodlayıcıların aptalca şeyler yapmasını değil, işlerin nasıl çalıştığını (her zaman değil).
Larry

205

Ayrıca var Scanner. Şunlar gibi kullanabilirsiniz BufferedReader:

Scanner scanner = new Scanner(myString);
while (scanner.hasNextLine()) {
  String line = scanner.nextLine();
  // process the line
}
scanner.close();

Bence bu hem önerilenlerin biraz daha temiz bir yaklaşım.


5
Gerçi bunun adil bir karşılaştırma olduğunu düşünmüyorum - String.split, her zaman mümkün olmayan (örneğin büyük dosyalar için) belleğe okunan tüm girdiye dayanır.
Adamski

3
Giriş Dize olduğu için girişin bellekte bulunması gerekir. Bellek ek yükü dizidir. Ayrıca, sonuçta elde edilen Dizeler aynı arka uç karakter dizisini yeniden kullanır.
notnoop

Unicode karakterleri olan bir UTF-8 dosyasını tararsanız ve kodlamayı Tarayıcıda belirtmezseniz, Tarayıcı'nın yanlış sonuçlar verebileceğini unutmayın.Farklı bir karakteri satır sonu olarak yorumlayabilir. Windows'da varsayılan kodlamasını kullanır.
canlı aşk

43

Özellikle verimlilik açısıyla ilgilendiğim için küçük bir test sınıfı oluşturdum (aşağıda). 5.000.000 satırın sonucu:

Comparing line breaking performance of different solutions
Testing 5000000 lines
Split (all): 14665 ms
Split (CR only): 3752 ms
Scanner: 10005
Reader: 2060

Her zamanki gibi, kesin zamanlar değişebilir, ancak oran doğrudur, ancak sıklıkla çalıştırırım.

Sonuç: OP'nin "daha basit" ve "daha verimli" gereksinimleri eşzamanlı olarak karşılanamaz, splitçözüm (her iki enkarnasyonda) daha basittir, ancak Readeruygulama diğerlerinin eline geçer.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * Test class for splitting a string into lines at linebreaks
 */
public class LineBreakTest {
    /** Main method: pass in desired line count as first parameter (default = 10000). */
    public static void main(String[] args) {
        int lineCount = args.length == 0 ? 10000 : Integer.parseInt(args[0]);
        System.out.println("Comparing line breaking performance of different solutions");
        System.out.printf("Testing %d lines%n", lineCount);
        String text = createText(lineCount);
        testSplitAllPlatforms(text);
        testSplitWindowsOnly(text);
        testScanner(text);
        testReader(text);
    }

    private static void testSplitAllPlatforms(String text) {
        long start = System.currentTimeMillis();
        text.split("\n\r|\r");
        System.out.printf("Split (regexp): %d%n", System.currentTimeMillis() - start);
    }

    private static void testSplitWindowsOnly(String text) {
        long start = System.currentTimeMillis();
        text.split("\n");
        System.out.printf("Split (CR only): %d%n", System.currentTimeMillis() - start);
    }

    private static void testScanner(String text) {
        long start = System.currentTimeMillis();
        List<String> result = new ArrayList<>();
        try (Scanner scanner = new Scanner(text)) {
            while (scanner.hasNextLine()) {
                result.add(scanner.nextLine());
            }
        }
        System.out.printf("Scanner: %d%n", System.currentTimeMillis() - start);
    }

    private static void testReader(String text) {
        long start = System.currentTimeMillis();
        List<String> result = new ArrayList<>();
        try (BufferedReader reader = new BufferedReader(new StringReader(text))) {
            String line = reader.readLine();
            while (line != null) {
                result.add(line);
                line = reader.readLine();
            }
        } catch (IOException exc) {
            // quit
        }
        System.out.printf("Reader: %d%n", System.currentTimeMillis() - start);
    }

    private static String createText(int lineCount) {
        StringBuilder result = new StringBuilder();
        StringBuilder lineBuilder = new StringBuilder();
        for (int i = 0; i < 20; i++) {
            lineBuilder.append("word ");
        }
        String line = lineBuilder.toString();
        for (int i = 0; i < lineCount; i++) {
            result.append(line);
            result.append("\n");
        }
        return result.toString();
    }
}

4
Java8'den itibaren, BufferedReader satırlardan birini lines()döndüren bir işleve sahiptir Stream<String>; bu, isterseniz bir listeye toplayabilir veya akışı işleyebilir.
Steve K

22

Kullanımı Apache Commons IOUtils güzelce aracılığıyla yapabilirsiniz

List<String> lines = IOUtils.readLines(new StringReader(string));

Akıllıca bir şey yapmıyor, ama güzel ve kompakt. Akarsuları da işleyecek ve isterseniz de alabilirsiniz LineIterator.


2
Bu yaklaşımın bir dezavantajı, IOUtils.readlines(Reader)a IOException. Bu muhtemelen bir StringReader'da asla olmayacak olsa da, onu yakalamanız veya beyan etmeniz gerekir.
sleske

Hafif bir yazım hatası vardır, şöyle olmalıdır: List lines = IOUtils.readLines (new StringReader (string));
tommy chheng

17

Çözüm kullanarak Java 8gibi özellikler Stream APIveMethod references

new BufferedReader(new StringReader(myString))
        .lines().forEach(System.out::println);

veya

public void someMethod(String myLongString) {

    new BufferedReader(new StringReader(myLongString))
            .lines().forEach(this::parseString);
}

private void parseString(String data) {
    //do something
}

11

Java 11'den beri yeni bir yöntem var String.lines:

/**
 * Returns a stream of lines extracted from this string,
 * separated by line terminators.
 * ...
 */
public Stream<String> lines() { ... }

Kullanımı:

"line1\nline2\nlines3"
    .lines()
    .forEach(System.out::println);

7

Java 8'de satırlar () akış çıktısı olan bir BufferedReader'a sarılmış bir api akışını ve bir StringReader'ı kullanabilirsiniz:

import java.util.stream.*;
import java.io.*;
class test {
    public static void main(String... a) {
        String s = "this is a \nmultiline\rstring\r\nusing different newline styles";

        new BufferedReader(new StringReader(s)).lines().forEach(
            (line) -> System.out.println("one line of the string: " + line)
        );
    }
}

verir

one line of the string: this is a
one line of the string: multiline
one line of the string: string
one line of the string: using different newline styles

BufferedReader'ın readLine öğesinde olduğu gibi, yeni satır karakterlerinin kendisi dahil edilmez. Her türlü yeni satır ayırıcı desteklenir (hatta aynı dizede).


Bunu bile bilmiyordum! Çok teşekkürler .
GOXR3PLUS

6

Ayrıca kullanabilirsiniz:

String[] lines = someString.split("\n");

O değilse çalışma deneyin değiştirilmesi \nile \r\n.


3
Newline'ın temsilini zor kodlamak çözümü platforma bağımlı hale getirir.
thSoft

@thSoft Aynı şeyi kodlama konusunda söylenebilir diyebilirim - eğer kodlamıyorsanız , aynı giriş için farklı platformlarda farklı sonuçlar elde edersiniz (yani platforma bağlı satır sonları yerine tam olarak aynı satır sonlarıyla) giriş). Bu gerçekten bir evet / hayır değil ve girdinizin ne olacağını düşünmeniz gerekiyor.
Jiri Tousek

Evet, pratikte yüzlerce kez kullandığım yöntemi kullandım ve gördüm. Tarayıcı sınıfını kullanmaktan ziyade metin parçalarınızı kıran tek bir satıra sahip olmak daha kolaydır. Yani, dizeniz anormal derecede büyük değilse.
Olin Kirkland

5

Veya Tarayıcı ile birleştirilmiş kaynaklar yan tümcesiyle yeni deneme özelliğini kullanın:

   try (Scanner scanner = new Scanner(value)) {
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            // process the line
        }
    }

2

Aşağıdaki normal ifadeyi deneyebilirsiniz:

\r?\n

Kod:

String input = "\nab\n\n    \n\ncd\nef\n\n\n\n\n";
String[] lines = input.split("\\r?\\n", -1);
int n = 1;
for(String line : lines) {
    System.out.printf("\tLine %02d \"%s\"%n", n++, line);
}

Çıktı:

Line 01 ""
Line 02 "ab"
Line 03 ""
Line 04 "    "
Line 05 ""
Line 06 "cd"
Line 07 "ef"
Line 08 ""
Line 09 ""
Line 10 ""
Line 11 ""
Line 12 ""

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.