2012-04-26 31 views
6

Me gustaría escribir una función simple que itere sobre las líneas de un archivo de texto. Creo en 2.8 se podría hacer:Iteración sobre las líneas de un archivo

def lines(filename: String) : Iterator[String] = { 
    scala.io.Source.fromFile(filename).getLines 
} 

y eso fue todo, pero en 2.9 lo anterior no funciona y en lugar debo hacer:

def lines(filename: String) : Iterator[String] = { 
    scala.io.Source.fromFile(new File(filename)).getLines() 
} 

Ahora, el problema es que yo quiero para componer los iteradores anteriores en una comprensión for:

for (l1 <- lines("file1.txt"); l2 <- lines("file2.txt")){ 
    do_stuff(l1, l2) 
} 

Esta vez, se utiliza para funcionar bien con 2.8 pero causa un "demasiado ma ny abrir archivos " excepción para obtener lanzado en 2.9. Esto es comprensible: el segundo lines en la comprensión termina abriendo (y no cerrando) un archivo para cada línea en el primero.

En mi caso, sé que el "file1.txt" es grande y no quiero chupar en
memoria, pero el segundo archivo es pequeño, por lo que puedo escribir una diferente linesEager así:

def linesEager(filename: String): Iterator[String] = 
    val buf = scala.io.Source.fromFile(new File(filename)) 
    val zs = buf.getLines().toList.toIterator 
    buf.close() 
    zs 

y luego girar a mi para-comprensión en:

for (l1 <- lines("file1.txt"); l2 <- linesEager("file2.txt")){ 
    do_stuff(l1, l2) 
} 

Esto funciona, pero es claramente fea. ¿Alguien puede sugerir un uniforme & limpiar manera de lograr lo anterior. Parece que necesita una forma para el iterador devuelto por lines a close el archivo cuando llega al final, y esto debe haber estado sucediendo en 2.8 ¿por qué funcionó allí?

Gracias!

BTW - aquí es una versión mínima del programa completo que muestra el problema:

import java.io.PrintWriter 
import java.io.File 

object Fail { 

    def lines(filename: String) : Iterator[String] = { 
    val f = new File(filename) 
    scala.io.Source.fromFile(f).getLines() 
    } 

    def main(args: Array[String]) = { 
    val smallFile = args(0) 
    val bigFile = args(1) 

    println("helloworld") 

    for (w1 <- lines(bigFile) 
     ; w2 <- lines(smallFile) 
     ) 
    { 
     if (w2 == w1){ 
     val msg = "%s=%s\n".format(w1, w2) 
     println("found" + msg) 
     } 
    } 

    println("goodbye") 
    } 

} 

En 2.9.0 puedo compilar con scalac WordsFail.scala y luego me sale esto:

[email protected]:$ scalac WordsFail.scala 
[email protected]:$ scala Fail passwd words 
helloworld 
java.io.FileNotFoundException: passwd (Too many open files) 
    at java.io.FileInputStream.open(Native Method) 
    at java.io.FileInputStream.<init>(FileInputStream.java:120) 
    at scala.io.Source$.fromFile(Source.scala:91) 
    at scala.io.Source$.fromFile(Source.scala:76) 
    at Fail$.lines(WordsFail.scala:8) 
    at Fail$$anonfun$main$1.apply(WordsFail.scala:18) 
    at Fail$$anonfun$main$1.apply(WordsFail.scala:17) 
    at scala.collection.Iterator$class.foreach(Iterator.scala:652) 
    at scala.io.BufferedSource$BufferedLineIterator.foreach(BufferedSource.scala:30) 
    at Fail$.main(WordsFail.scala:17) 
    at Fail.main(WordsFail.scala) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
    at java.lang.reflect.Method.invoke(Method.java:597) 
    at scala.tools.nsc.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClassLoader.scala:78) 
    at scala.tools.nsc.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:24) 
    at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:88) 
    at scala.tools.nsc.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:78) 
    at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:101) 
    at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:33) 
    at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:40) 
    at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:56) 
    at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:80) 
    at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:89) 
    at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala) 
+3

El código uno funciona para mí en el REPL (Scala 2.9). –

+0

No fue el ';' desafortunadamente. –

+0

@userunknown Funciona pero no escala. (Imagine archivos grandes/muchas líneas.) – Debilski

Respuesta

13

scala-arm proporciona un excelente mecanismo para cerrar automáticamente los recursos cuando haya terminado con ellos.

import resource._ 
import scala.io.Source 

for (file1 <- managed(Source.fromFile("file1.txt")); 
    l1 <- file1.getLines(); 
    file2 <- managed(Source.fromFile("file2.txt")); 
    l2 <- file2.getLines()) { 
    do_stuff(l1, l2) 
} 

Pero a menos que usted está contando en el contenido de file2.txt cambiar mientras se está recorriendo file1.txt, sería mejor leer que en una List antes de bucle. No es necesario convertirlo en Iterator.

+0

¿No convertirlo en una lista termina sosteniendo todo el archivo en la memoria? Esperaba evitar eso ... –

+0

Pero 'file2.txt' es pequeño, por lo que debería ser aceptable. Además, eso es lo que hace su 'linesEager' (' .toList'), excepto que lo está creando en la memoria y tirándolo para cada línea en 'archivo1.txt'. – leedm777

+0

Hola Dave, sí, tienes razón. Estaba bajo el error de que las diferentes acciones en la comprensión tenían que tener el mismo tipo, por lo que el '' toList.toIterator'' extraño con 'toList' sería suficiente ... ¡Gracias! –

2

tal vez debería Eche un vistazo a scala-arm (https://github.com/jsuereth/scala-arm) y permita que el cierre de los archivos (flujos de entrada de archivos) suceda automáticamente en segundo plano.

Cuestiones relacionadas