2016-01-14 14 views
11

considerare Ho un file come (solo un estratto)Trova modello in file con Java 8

name: 'foobar' 

mi piace recuperare foobar quando scopro la linea con name.

Il mio approccio attuale è

Pattern m = Pattern.compile("name: '(.+)'"); 
try (Stream<String> lines = Files.lines(ruleFile)) { 
    Optional<String> message = lines.filter(m.asPredicate()).findFirst(); 
    if (message.isPresent()) { 
     Matcher matcher = m.matcher(message.get()); 
     matcher.find(); 
     String group = matcher.group(1); 
     System.out.println(group); 
    } 
} 

che sembra non è bello. L'uso eccessivo del pattern e del matcher sembra sbagliato.

C'è un modo più semplice/migliore? Soprattutto se ho più chiavi che mi piace cercare in questo modo?

risposta

21

mi sarei aspettato qualcosa di più simile a questo, per evitare che corrisponde al modello due volte:

Pattern p = Pattern.compile("name: '([^']*)'"); 
lines.map(p::matcher) 
    .filter(Matcher::matches) 
    .findFirst() 
    .ifPresent(matcher -> System.out.println(matcher.group(1))); 

Cioè, per matcher di ogni stringa, ottiene il primo quello che corrisponde, per quello stampare il primo gruppo.

+0

La fase pipeline 'map (p :: matcher)' crea un nuovo oggetto 'Matcher' per ogni riga in lettura. Per file estremamente grandi, questa può essere una fonte di inefficienza. Invece, un oggetto 'Matcher' riutilizzabile può essere creato, e' .map (matcher :: reset) 'può essere usato per mappare dalla linea appena letto nel' matcher' riutilizzabile, come mostrato [qui] (https://stackoverflow.com/a/47877960/3690024). Il riutilizzo di oggetti stateful nelle pipeline dei flussi viola tutti i tipi di regole, quindi lo consiglierei solo se stai leggendo file di grandi dimensioni e ne crei un collo di bottiglia. – AJNeufeld

7

Questo è come la soluzione Java 9 sarà molto probabilmente simile:

Matcher m = Pattern.compile("name: '(.+)'").matcher(""); 
try(Stream<String> lines = Files.lines(ruleFile)) { 
    lines.flatMap(line -> m.reset(line).results().limit(1)) 
     .forEach(mr -> System.out.println(mr.group(1))); 
} 

Si utilizza il metodo Matcher.results() che restituisce un flusso di tutte le partite. La combinazione di un flusso di linee con un flusso di corrispondenze tramite flatMap ci consente di elaborare tutte le corrispondenze di un file. Poiché il codice originale elabora solo la prima corrispondenza di una riga, ho semplicemente aggiunto un valore limit(1) alle corrispondenze di ogni riga per ottenere lo stesso comportamento.

Purtroppo, questa funzione non è presente in Java 8, tuttavia, furtivamente in prossime versioni aiuta a farsi un'idea di come una soluzione provvisoria può apparire come:

Matcher m = Pattern.compile("name: '(.+)'").matcher(""); 
try(Stream<String> lines = Files.lines(ruleFile)) { 
    lines.flatMap(line -> m.reset(line).find()? Stream.of(m.toMatchResult()): null) 
     .forEach(mr -> System.out.println(mr.group(1))); 
} 

per semplificare la creazione sub-stream, questa soluzione utilizza che solo la prima corrispondenza è destinata e crea un singolo flusso di elementi in primo luogo.

ma nota che con il modello della domanda 'name: '(.+)' non importa se limitiamo il numero di corrispondenze .+ sarà avidamente corrispondere tutti i caratteri fino all'ultimo follow-up ' della linea, in modo da un altro match è impossibile. Le cose sono diverse quando si utilizza un quantificatore riluttante come con name: '(.*?)' che consuma fino al prossimo ' piuttosto che l'ultima uno o vieta di saltare passato ' esplicitamente, come con name: '([^']*)'.


Le soluzioni sopra uso condiviso Matcher che funziona bene con l'utilizzo thread singolo (e questo è improbabile beneficiare mai dall'elaborazione parallela). Ma se si vuole essere sul sicuro filo, si può solo condividere un Pattern e creare un Matcher invece di chiamare m.reset(line):

Pattern pattern = Pattern.compile("name: '(.*)'"); 
try(Stream<String> lines = Files.lines(ruleFile)) { 
    lines.flatMap(line -> pattern.matcher(line).results().limit(1)) 
     .forEach(mr -> System.out.println(mr.group(1))); 
} 

resp. con Java 8

try(Stream<String> lines = Files.lines(ruleFile)) { 
    lines.flatMap(line -> {Matcher m=pattern.matcher(line); 
          return m.find()? Stream.of(m.toMatchResult()): null;}) 
     .forEach(mr -> System.out.println(mr.group(1))); 
} 

che non è così conciso a causa dell'introduzione di una variabile locale.Ciò può essere evitato un map operazione precedente, ma quando siamo a questo punto, fintanto che solamente la testa per una singola partita per linea, non abbiamo bisogno un flatMap poi:

try(Stream<String> lines = Files.lines(ruleFile)) { 
    lines.map(pattern::matcher).filter(Matcher::find) 
     .forEach(m -> System.out.println(m.group(1))); 
} 

Poiché ogni Matcher è usato esattamente una volta, in modo non interferente, la sua natura mutevole non fa male qui e una conversione in un immutabile MatchResult diventa inutile.

Tuttavia, queste soluzioni non possono essere scalati per elaborare più corrispondenze per riga, se diventa mai necessario ...

0

La risposta di risultati @khelwood nella creazione di un nuovo oggetto Matcher più e più volte, che può essere una fonte di inefficienza se vengono scansionati file lunghi.

La seguente soluzione crea il modulo di corrispondenza una sola volta e lo riutilizza per ogni riga del file.

Pattern p = Pattern.compile("name: '([^']*)'"); 
Matcher matcher = p.matcher(""); // Create a matcher for the pattern 

Files.lines(ruleFile) 
    .map(matcher::reset)   // Reuse the matcher object 
    .filter(Matcher::matches) 
    .findFirst() 
    .ifPresent(m -> System.out.println(m.group(1))); 

Attenzione - Hack Sospetto Ahead

La fase gasdotto .map(matcher::reset) è dove la magia/trucco accade. Chiama efficacemente matcher.reset(line), che ripristina matcher per eseguire la successiva corrispondenza sulla riga appena letta dal file e restituisce sé stessa, per consentire le chiamate concatenate. L'operatore flusso .map(...) vede questo come mappatura dalla linea a un oggetto Matcher, ma in realtà, teniamo mappatura stesso oggetto matcher di volta in volta, violando tutti i tipi di regole su effetti collaterali, ecc

Naturalmente, questo non è possibile utilizzare per flussi paralleli, ma fortunatamente la lettura da un file è intrinsecamente sequenziale.

Hack o ottimizzazione? Suppongo che i voti su/giù decideranno.