2014-09-26 12 views
7

Sto leggendo un file molto grande (500mb) con Files.lines (...). Si legge una parte del file, ma ad un certo punto si rompe con java.io.UncheckedIOException: java.nio.charset.MalformedInputException: lunghezza ingresso = 1Files.lines per saltare linee spezzate in Java8

penso che il file ha linee con diversi set di caratteri. C'è un modo per saltare queste linee spezzate? So che lo stream restituito è supportato da un Reader e con il lettore so come saltare, ma non so come ottenere il Reader dallo stream per configurarlo come preferisco.

List<String> lines = new ArrayList<>(); 
    try (Stream<String> stream = Files.lines(Paths.get(getClass().getClassLoader().getResource("bigtest.txt").toURI()), Charset.forName("UTF-8"))) { 
     stream 
      .filter(s -> s.substring(0, 2).equalsIgnoreCase("aa")) 
      .forEach(lines::add); 
    } catch (final IOException e) { 
     // catch 
    } 

risposta

9

Non è possibile filtrare linee con caratteri non validi dopo la decodifica quando il decoder preconfigurato ferma già la decodifica con un'eccezione. Devi configurare un CharsetDecoder manualmente per dirgli di ignorare l'input non valido o di sostituire quell'input con un carattere speciale.

CharsetDecoder dec=StandardCharsets.UTF_8.newDecoder() 
        .onMalformedInput(CodingErrorAction.IGNORE); 
Path path=Paths.get(getClass().getClassLoader().getResource("bigtest.txt").toURI()); 
List<String> lines; 
try(Reader r=Channels.newReader(FileChannel.open(path), dec, -1); 
    BufferedReader br=new BufferedReader(r)) { 
     lines=br.lines() 
       .filter(s -> s.regionMatches(true, 0, "aa", 0, 2)) 
       .collect(Collectors.toList()); 
} 

Questo semplicemente ignora gli errori di decodifica del set di caratteri, saltando i caratteri. Per saltare intere linee contenenti errori, è possibile lasciare che il decodificatore inserire un carattere di sostituzione (default '\ufffd') per errori e filtrare le righe che contengono quel carattere:

CharsetDecoder dec=StandardCharsets.UTF_8.newDecoder() 
        .onMalformedInput(CodingErrorAction.REPLACE); 
Path path=Paths.get(getClass().getClassLoader().getResource("bigtest.txt").toURI()); 
List<String> lines; 
try(Reader r=Channels.newReader(FileChannel.open(path), dec, -1); 
    BufferedReader br=new BufferedReader(r)) { 
     lines=br.lines() 
       .filter(s->!s.contains(dec.replacement())) 
       .filter(s -> s.regionMatches(true, 0, "aa", 0, 2)) 
       .collect(Collectors.toList()); 
} 
+0

grazie per la risposta Holger. Mi chiedevo anche se è possibile farlo con lo stream per evitare il codice boilerplate, ma non sembra possibile (il flusso è supportato da un Reader, speravo che fosse possibile ottenere il lettore in qualche modo e aggiungere il decoder) – Francesco

+2

Questo non è supportato da quell'API. Ma anche se lo fosse, non sarebbe più compatto del codice qui. Si noti che il "boilerplate" è solo una riga aggiuntiva. Il risultato sembra più prolisso solo perché ho fatto una formattazione più generosa in quanto non mi piace lo scorrimento orizzontale. Bene, e uso più spazio bianco quando pubblichiamo esempi di codice per il pubblico più ampio di quanto non faccia nel mio vero codice. Ovviamente, è possibile inserire 'dec' e' path' e usare 'import static' per' StandardCharsets.UTF_8', nonché per 'CodingErrorAction. *', 'Channels.newReader',' FileChannel.open', ecc. – Holger

+0

Si, sono d'accordo. Grazie ancora Holger. – Francesco

0

In questa situazione, la soluzione sta per essere complessa e più incline ai bug quando si utilizza l'API di Streams. Suggerisco di usare solo un normale ciclo per leggere da un BufferedReader e quindi acquisire MalformedInputException. Ciò consente inoltre di distinguere altre eccezioni IO:

List<String> lines = new ArrayList<>(); 

try (BufferedReader r = new BufferedReader(path,StandardCharsets.UTF_8)){ 
    try{ 
      String line = null; 
      while((line=r.readLine())!=null){ 
       if(line.substring(0, 2).equalsIgnoreCase("aa")){ 
        lines.add(line); 
       } 
    }catch(MalformedInputException mie){ 
      // ignore or do something 
    } 
} 
+0

grazie per il tuo commento, ho alcune considerazioni: * di solito usando lo stile funzionale rendi il codice più chiaro e più conciso. Al momento ci sono poche righe di codice, ma se cresce penso che sia funzionale la via da percorrere * stai programmando attraverso le eccezioni, questa è una pratica che non mi piace molto * perdi la pigrizia e tutti i benefici di it (facile parallelizzazione, codice stateless, ...) detto questo, la soluzione è sicuramente un'altra soluzione funzionante. – Francesco

+0

@Fra buoni punti. La risposta di Holger è più elaborata. Tuttavia, il mio non ha alcuna perdita di "pigrizia" poiché anch'essa è pigra. Tuttavia, le eccezioni di Stream sono complesse da gestire. Sembra buono fino a quando non è necessario eseguire il debug :) Inoltre, nella soluzione Holgers, è comunque necessario acquisire e gestire una IOException. A meno che non lo ignorerai del tutto e lascialo perdere. Il problema più grande è che usando il paradigma Stream-filter, quel codice è molto lento. Per un grande file di 500 MB, eviterei i filtri. –