2011-01-13 6 views
32

Tenemos una aplicación Java que tiene algunos módulos que saben leer archivos de texto. Lo hacen simplemente con un código como el siguiente:Iterar el contenido de un archivo de texto línea por línea. ¿Existe alguna práctica recomendada? (en comparación con AssignmentInOperand de PMD)

BufferedReader br = new BufferedReader(new FileReader(file)); 
String line = null; 
while ((line = br.readLine()) != null) 
{ 
    ... // do stuff to file here 
} 

me corrieron PMD en mi proyecto y consiguió el 'AssignmentInOperand' violación en la línea while (...).

¿Hay una manera más sencilla de hacer esto bucle aparte de la obvia:

String line = br.readLine(); 
while (line != null) 
{ 
    ... // do stuff to file here 
    line = br.readLine(); 
} 

¿Esto se considera una mejor práctica? (¿Aunque "duplicamos" el código line = br.readLine()?)

+0

Nice BufferedReaderIterator. Tuve que reemplazar r.mark (1) con r.mark (2), de lo contrario tendría una "marca no válida" alrededor de 100 líneas en un archivo grande. No entiendo por qué –

+4

¿Qué tal un bucle 'for'? 'for (String line = br.readLine(); line! = null; line = br.readLine()) {...}' –

Respuesta

19

Generalmente prefiero el primero. No hago generalmente como efectos secundarios en una comparación, pero este ejemplo en particular es una expresión que es tan común y tan útil que no me opongo a ella.

(En C# hay una opción más agradable: un método para devolver un IEnumerable<string> que puede repetir con foreach; eso no es tan bueno en Java porque no hay eliminación automática al final de un bucle mejorado para .. .y también porque no puede arrojar IOException desde el iterador, lo que significa que no puede hacer que uno sea un reemplazo directo para el otro.)

Dicho de otra manera: el problema de la línea duplicada me molesta más que la cuestión de asignación dentro del operando. Estoy acostumbrado a observar este patrón de un vistazo: con la versión de línea duplicada, necesito detenerme y verificar que todo esté en el lugar correcto. Eso es probablemente hábito tanto como cualquier otra cosa, pero no creo que sea un problema.

+0

Tengo curiosidad por lo que piensas sobre la creación de un decorador como un mecanismo de conveniencia que abstraiga el semántica de la iteración para que pueda usar un bucle foreach (consulte mi respuesta con una sugerencia * rough * a continuación) ... –

+0

@Mark E: No es tan limpio como la versión de C#, pero no está mal, salvo por excepciones. Voy a comentar tu respuesta y a editar la mía. –

2

AssignmentInOperand es una regla de controversia en el PMD, la razón de esta regla es: "esto puede hacer que el código sea más complicado y más difícil de leer" (consulte http://pmd.sourceforge.net/rules/controversial.html)

, puede desactivar esa regla si usted realmente quiere hazlo de esa manera. Por mi parte, prefiero el primero.

+1

O ponga un comentario '// NOPMD' –

4

Sobre la base de la respuesta de Jon llegué a pensar que debería ser bastante fácil de crear un decorador para actuar como un iterador de archivo de esta manera puede utilizar un bucle foreach:

public class BufferedReaderIterator implements Iterable<String> { 

    private BufferedReader r; 

    public BufferedReaderIterator(BufferedReader r) { 
     this.r = r; 
    } 

    @Override 
    public Iterator<String> iterator() { 
     return new Iterator<String>() { 

      @Override 
      public boolean hasNext() { 
       try { 
        r.mark(1); 
        if (r.read() < 0) { 
         return false; 
        } 
        r.reset(); 
        return true; 
       } catch (IOException e) { 
        return false; 
       } 
      } 

      @Override 
      public String next() { 
       try { 
        return r.readLine(); 
       } catch (IOException e) { 
        return null; 
       } 
      } 

      @Override 
      public void remove() { 
       throw new UnsupportedOperationException(); 
      } 

     }; 
    } 

} 

Feria de advertencia: Esto suprime IOExceptions que podrían ocurrir durante las lecturas y simplemente detiene el proceso de lectura. No está claro que haya una forma de evitar esto en Java sin lanzar excepciones de tiempo de ejecución ya que la semántica de los métodos del iterador está bien definida y debe conformarse para usar la sintaxis for-each. Además, ejecutar múltiples iteradores aquí tendría un comportamiento extraño; entonces no estoy seguro de que esto sea recomendado.

Aunque lo probé, y funciona.

De todos modos, se obtiene el beneficio de los fines de cada sintaxis usando esto como una especie de decorador:

for(String line : new BufferedReaderIterator(br)){ 
    // do some work 
} 
+0

Sospecho que esto no se compilará, porque' readLine' podría lanzar IOException. La interfaz del iterador no lo permite, por lo que tendría que envolverlo en una excepción sin marcar, en cuyo punto está empezando a verse cada vez menos como el código original :( –

+0

@Jon: tiene razón, y desafortunadamente Estoy bastante seguro de que no hay forma de esconder las excepciones para obtener la semántica. Aunque es conveniente, la recompensa parece triste. –

29

Sé que es una entrada antigua, pero sólo tenía la misma necesidad (casi) y resolverlo usando un LineIterator de FileUtils en Apache Commons. Desde su javadoc:

LineIterator it = FileUtils.lineIterator(file, "UTF-8"); 
try { 
    while (it.hasNext()) { 
    String line = it.nextLine(); 
    // do something with line 
    } 
} finally { 
    it.close(); 
} 

Comprobar la documentación: http://commons.apache.org/proper/commons-io/javadocs/api-release/org/apache/commons/io/LineIterator.html

+0

Gracias, puede ser útil en el futuro. – RonK

+1

En caso de que alguien necesita esta dependencia (FileUtils) de Maven: commons-io commons-io 2,4

+0

Estoy bastante seguro de que puede hacer este mismo tipo de cosas con un escáner ' 'también –

3

de Guava Libraries Google proporcionan una solución alternativa usando el método estático CharStreams.readLines(Readable, LineProcessor<T>) con una implementación de LineProcessor<T> para procesar cada línea.

try (BufferedReader br = new BufferedReader(new FileReader(file))) { 
    CharStreams.readLines(br, new MyLineProcessorImpl()); 
} catch (IOException e) { 
    // handling io error ... 
} 

El cuerpo del bucle while se coloca ahora en la aplicación LineProcessor<T>.

class MyLineProcessorImpl implements LineProcessor<Object> { 

    @Override 
    public boolean processLine(String line) throws IOException { 
     if (// check if processing should continue) { 
      // do sth. with line 
      return true; 
     } else { 
      // stop processing 
      return false; 
     } 
    } 

    @Override 
    public Object getResult() { 
     // return a result based on processed lines if needed 
     return new Object(); 
    } 
} 
13

rutinariamente uso el constructo while((line = br.readLine()) != null) ... pero, recently I came accross this nice alternative:

BufferedReader br = new BufferedReader(new FileReader(file)); 

for (String line = br.readLine(); line != null; line = br.readLine()) { 
    ... // do stuff to file here 
} 

Esto todavía está duplicando el código readLine() llamada, pero la lógica es clara, etc.

El otro el tiempo que uso la construcción while((...) ...) es cuando se lee desde una secuencia en una matriz byte[] ...

byte[] buffer = new byte[size]; 
InputStream is = .....; 
int len = 0; 
while ((len = is.read(buffer)) >= 0) { 
    .... 
} 

Esto también se puede transformar en un bucle for con:

byte[] buffer = new byte[size]; 
InputStream is = .....; 
for (int len = is.read(buffer); len >= 0; len = is.read(buffer)) { 
    .... 
} 

No estoy seguro de que realmente prefiero las alternativas de bucle .... pero, que satisfará cualquier herramienta PMD, y el la lógica sigue siendo clara, etc.

+1

Buen enfoque! También puede ajustar la creación de instancias' BufferedReader' con la declaración try-with-resources si se usa con Java 7, reducirá el alcance de la variable y agregar automáticamente cerrar el lector se manejan todas las líneas. –

11

El apoyo a streams y Lambdas en java-8 y Try-With-Resources de java-7 le permite a achive lo que quiere en la sintaxis más compacta.

Path path = Paths.get("c:/users/aksel/aksel.txt"); 

try (Stream<String> lines = Files.lines(path)) { 
    lines.forEachOrdered(line->System.out.println(line)); 
} catch (IOException e) { 
    //error happened 
} 
+1

También se puede acortar la lambda a una referencia de método: 'lines.forEachOrdered (System.out :: println)' –

3

estoy un poco sorprendido de la siguiente alternativa no fue mencionado:

while(true) { 
    String line = br.readLine(); 
    if (line == null) break; 
    ... // do stuff to file here 
} 

Antes de Java 8 era mi favorito debido a su claridad y que no requieren la repetición. IMO, break es una mejor opción para expresiones con efectos secundarios. Sin embargo, todavía es una cuestión de modismos.

Cuestiones relacionadas