如何使用Scala Stream类读取大型CSV文件?

时间:2010-11-23 10:23:05

标签: scala csv streaming large-files

如何使用Scala Stream读取大型CSV文件(> 1 Gb)?你有代码示例吗?或者您是否会使用不同的方法来读取大型CSV文件而不先将其加载到内存中?

3 个答案:

答案 0 :(得分:68)

如您所说,只需使用Source.fromFile(...).getLines

返回一个Iterator,它已经是懒惰的(您将使用stream作为一个惰性集合,您希望先前检索的值被记忆,因此您可以再次阅读它们)

如果您遇到内存问题,那么问题将出在 getLines之后您正在做什么。像toList这样强制严格收集的任何操作都会导致问题。

答案 1 :(得分:12)

我希望你不要指Scala的collection.immutable.Stream与Stream。这是你想要的。流是懒惰的,但会记忆。

我不知道你打算做什么,但只是逐行读取文件应该可以很好地工作,而不需要使用大量的内存。

getLines应该懒惰评估并且不应该崩溃(只要你的文件没有超过2 32行,afaik)。如果是,请询问#scala或提交错误提示(或同时执行)。

答案 2 :(得分:3)

如果您希望逐行处理大型文件,同时避免要求将整个文件的内容一次性加载到内存中,那么您可以使用Iterator返回的scala.io.Source 1}}。

我有一个小函数tryProcessSource,(包含两个子函数),我将它用于这些类型的用例。该功能最多需要四个参数,其中只需要第一个参数。其他参数提供了合理的默认值。

这里是功能配置文件(完整功能实现位于底部):

def tryProcessSource(
  file: File,
  parseLine: (Int, String) => Option[List[String]] =
    (index, unparsedLine) => Some(List(unparsedLine)),
  filterLine: (Int, List[String]) => Option[Boolean] =
    (index, parsedValues) => Some(true),
  retainValues: (Int, List[String]) => Option[List[String]] =
    (index, parsedValues) => Some(parsedValues),
): Try[List[List[String]]] = {
  ???
}

第一个参数file: File是必需的。它只是java.io.File的任何有效实例,它指向面向行的文本文件,如CSV。

第二个参数parseLine: (Int, String) => Option[List[String]]是可选的。如果提供,它必须是一个期望接收两个输入参数的函数; index: IntunparsedLine: String。然后返回Option[List[String]]。该函数可能会返回由有效列值组成的Some包裹List[String]。或者它可能返回None,表示整个流媒体流程提前中止。如果未提供此参数,则会提供默认值(index, line) => Some(List(line))。此默认值会导致整行返回为单个String值。

第三个参数filterLine: (Int, List[String]) => Option[Boolean]是可选的。如果提供,它必须是一个期望接收两个输入参数的函数; index: IntparsedValues: List[String]。然后返回Option[Boolean]。该函数可以返回Some包裹Boolean,指示该特定行是否应包含在输出中。或者它可能返回None,表示整个流媒体流程提前中止。如果未提供此参数,则会提供默认值(index, values) => Some(true)。此默认值会导致包含所有行。

第四个也是最后一个参数retainValues: (Int, List[String]) => Option[List[String]]是可选的。如果提供,它必须是一个期望接收两个输入参数的函数; index: IntparsedValues: List[String]。然后返回Option[List[String]]。该函数可以返回由Some包裹的List[String]组成的None包含现有列值的一些子集和/或更改。或者它可能返回(index, values) => Some(values),表示整个流媒体流程提前中止。如果未提供此参数,则会提供默认值parseLine。此默认值导致第二个参数street,street2,city,state,zip 100 Main Str,,Irving,TX,75039 231 Park Ave,,Irving,TX,75039 1400 Beltline Rd,Apt 312,Dallas,Tx,75240 解析的值。

考虑一个包含以下内容的文件(4行):

val tryLinesDefaults =
  tryProcessSource(new File("path/to/file.csv"))

以下通话资料......

tryLinesDefaults

...导致Success( List( List("street,street2,city,state,zip"), List("100 Main Str,,Irving,TX,75039"), List("231 Park Ave,,Irving,TX,75039"), List("1400 Beltline Rd,Apt 312,Dallas,Tx,75240") ) ) 的输出(文件未更改的内容):

val tryLinesParseOnly =
  tryProcessSource(
      new File("path/to/file.csv")
    , parseLine =
        (index, unparsedLine) => Some(unparsedLine.split(",").toList)
  )

以下通话资料......

tryLinesParseOnly

...导致Success( List( List("street","street2","city","state","zip"), List("100 Main Str","","Irving,TX","75039"), List("231 Park Ave","","Irving","TX","75039"), List("1400 Beltline Rd","Apt 312","Dallas","Tx","75240") ) ) 的此输出(每行解析为单个列值):

val tryLinesIrvingTxNoHeader =
  tryProcessSource(
      new File("C:/Users/Jim/Desktop/test.csv")
    , parseLine =
        (index, unparsedLine) => Some(unparsedLine.split(",").toList)
    , filterLine =
        (index, parsedValues) =>
          Some(
            (index != 0) && //skip header line
            (parsedValues(2).toLowerCase == "Irving".toLowerCase) && //only Irving
            (parsedValues(3).toLowerCase == "Tx".toLowerCase)
          )
  )

以下通话资料......

tryLinesIrvingTxNoHeader

...导致Success( List( List("100 Main Str","","Irving,TX","75039"), List("231 Park Ave","","Irving","TX","75039"), ) ) 的此输出(每行解析为单独的列值,没有标题,只有Irving,Tx中的两行):

tryProcessSource

这是整个import scala.io.Source import scala.util.Try import java.io.File def tryProcessSource( file: File, parseLine: (Int, String) => Option[List[String]] = (index, unparsedLine) => Some(List(unparsedLine)), filterLine: (Int, List[String]) => Option[Boolean] = (index, parsedValues) => Some(true), retainValues: (Int, List[String]) => Option[List[String]] = (index, parsedValues) => Some(parsedValues) ): Try[List[List[String]]] = { def usingSource[S <: Source, R](source: S)(transfer: S => R): Try[R] = try {Try(transfer(source))} finally {source.close()} def recursive( remaining: Iterator[(String, Int)], accumulator: List[List[String]], isEarlyAbort: Boolean = false ): List[List[String]] = { if (isEarlyAbort || !remaining.hasNext) accumulator else { val (line, index) = remaining.next parseLine(index, line) match { case Some(values) => filterLine(index, values) match { case Some(keep) => if (keep) retainValues(index, values) match { case Some(valuesNew) => recursive(remaining, valuesNew :: accumulator) //capture values case None => recursive(remaining, accumulator, isEarlyAbort = true) //early abort } else recursive(remaining, accumulator) //discard row case None => recursive(remaining, accumulator, isEarlyAbort = true) //early abort } case None => recursive(remaining, accumulator, isEarlyAbort = true) //early abort } } } Try(Source.fromFile(file)).flatMap( bufferedSource => usingSource(bufferedSource) { source => recursive(source.getLines().buffered.zipWithIndex, Nil).reverse } ) } 函数实现:

retainValues

虽然这个解决方案相对简洁,但在我终于能够到达这里之前,我花了相当多的时间和许多重构过程。如果您发现可以改进的方法,请告诉我。

更新:我刚才将问题问为it's own StackOverflow question。现在has an answer fixing the error提到了它。

我有一个想法,尝试使用下面的新generics-ified函数定义将transformLine参数更改为def tryProcessSource2[A <: AnyRef]( file: File, parseLine: (Int, String) => Option[List[String]] = (index, unparsedLine) => Some(List(unparsedLine)), filterLine: (Int, List[String]) => Option[Boolean] = (index, parsedValues) => Some(true), transformLine: (Int, List[String]) => Option[A] = (index, parsedValues) => Some(parsedValues) ): Try[List[A]] = { ??? } 更加通用。但是,我继续在IntelliJ&#34中获得突出显示错误;某些[List [String]]类型的表达式不符合预期类型选项[A]&#34;并且无法弄清楚如何更改默认值以便错误消失。

public class MainActivity extends ActionBarActivity {

    TextView tv;
    private Socket socket;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);


        try {
            socket = IO.socket("http://"+IP_ADDRESS+":"+PORT_NO);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        socket.connect();
        socket.emit("socketTesting","hiiii");


        setContentView(R.layout.test_xml);


    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        socket.disconnect();
    }
}

非常感谢任何有关如何完成这项工作的帮助。