如何使用Scala Stream读取大型CSV文件(> 1 Gb)?你有代码示例吗?或者您是否会使用不同的方法来读取大型CSV文件而不先将其加载到内存中?
答案 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: Int
,unparsedLine: String
。然后返回Option[List[String]]
。该函数可能会返回由有效列值组成的Some
包裹List[String]
。或者它可能返回None
,表示整个流媒体流程提前中止。如果未提供此参数,则会提供默认值(index, line) => Some(List(line))
。此默认值会导致整行返回为单个String
值。
第三个参数filterLine: (Int, List[String]) => Option[Boolean]
是可选的。如果提供,它必须是一个期望接收两个输入参数的函数; index: Int
,parsedValues: List[String]
。然后返回Option[Boolean]
。该函数可以返回Some
包裹Boolean
,指示该特定行是否应包含在输出中。或者它可能返回None
,表示整个流媒体流程提前中止。如果未提供此参数,则会提供默认值(index, values) => Some(true)
。此默认值会导致包含所有行。
第四个也是最后一个参数retainValues: (Int, List[String]) => Option[List[String]]
是可选的。如果提供,它必须是一个期望接收两个输入参数的函数; index: Int
,parsedValues: 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();
}
}
非常感谢任何有关如何完成这项工作的帮助。