Как я могу прочитать большой файл CSV ( > 1 Гб) с помощью Scala Stream? У вас есть пример кода? Или вы могли бы использовать другой способ для чтения большого файла CSV, не загружая его сначала в память?
Как прочитать большой CSV файл с классом Scala Stream?
Ответ 1
Просто используйте Source.fromFile(...).getLines
, как вы уже сказали.
Это возвращает Итератор, который уже ленив (вы использовали бы поток как ленивую коллекцию, в которой вы хотели бы сохранить ранее полученные значения, чтобы их можно было прочитать снова)
Если у вас проблемы с памятью, проблема будет заключаться в том, что вы делаете после getLines. Любая операция типа toList
, которая создает строгий сбор, вызовет проблему.
Ответ 2
Надеюсь, вы не имеете в виду Scala collection.immutable.Stream
с Stream. Это не то, что вы хотите. Поток ленив, но делает memoization.
Я не знаю, что вы планируете делать, но просто чтение файла по строкам должно работать очень хорошо, не используя большие объемы памяти.
getLines
должен оцениваться лениво и не должен вылетать (если в вашем файле не более 2-3 строк, afaik). Если это так, спросите на # scala или напишите билет с ошибкой (или выполните оба варианта).
Ответ 3
Если вы хотите обработать большой файл по очереди, избегая при этом необходимости полностью загружать содержимое всего файла в память, вы можете использовать Iterator
, возвращенный scala.io.Source
.
У меня есть небольшая функция, 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
wrapped List[String]
, состоящий из допустимых значений столбца. Или он может вернуть None
, который указывает, что весь процесс потоковой передачи прерывается раньше. Если этот параметр не указан, предоставляется значение по умолчанию (index, line) => Some(List(line))
. Это значение по умолчанию приводит к тому, что вся строка возвращается как одно значение String
.
Третий параметр filterLine: (Int, List[String]) => Option[Boolean]
не является обязательным. И если это предусмотрено, это должна быть функция, ожидающая получить два входных параметра; index: Int
, parsedValues: List[String]
. А затем верните Option[Boolean]
. Функция может возвращать Some
wrapped Boolean
, указывающий, должна ли эта конкретная строка быть включена в выход. Или он может вернуть None
, который указывает, что весь процесс потоковой передачи прерывается раньше. Если этот параметр не указан, предоставляется значение по умолчанию (index, values) => Some(true)
. Это значение по умолчанию приводит к включению всех строк.
Четвертый и последний параметр retainValues: (Int, List[String]) => Option[List[String]]
не является обязательным. И если это предусмотрено, это должна быть функция, ожидающая получить два входных параметра; index: Int
, parsedValues: List[String]
. А затем верните Option[List[String]]
. Функция может возвращать Some
wrapped List[String]
, состоящий из некоторого подмножества и/или изменения существующих значений столбца. Или он может вернуть None
, который указывает, что весь процесс потоковой передачи прерывается раньше. Если этот параметр не указан, предоставляется значение по умолчанию (index, values) => Some(values)
. Это значение по умолчанию приводит к значениям, проанализированным вторым параметром, parseLine
.
Рассмотрим файл со следующим содержимым (4 строки):
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
Следующий вызывающий профиль...
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
(каждая строка анализируется на отдельные значения столбца, без заголовка и только две строки в Irving, Tx):
Success(
List(
List("100 Main Str","","Irving,TX","75039"),
List("231 Park Ave","","Irving","TX","75039"),
)
)
Здесь вся реализация функции 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
}
)
}
Хотя это решение относительно лаконично, мне потребовалось немало времени и много рефакторингов, прежде чем я смог наконец добраться досюда. Пожалуйста, дайте мне знать, видите ли вы какие-либо способы улучшения.
ОБНОВЛЕНИЕ: я только что задал вопрос ниже fooobar.com/info/35057/.... И теперь имеет ответ, фиксирующий ошибку, указанную ниже.
У меня возникла идея попытаться сделать это еще более общим, изменив параметр retainValues
на transformLine
с новым определением функции generic-ified ниже. Тем не менее, я продолжаю получать ошибку выделения в IntelliJ "Выражение типа Some [List [String]] не соответствует ожидаемому типу Option [A]" и не смогло выяснить, как изменить значение по умолчанию, чтобы ошибка уходит.
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]] = {
???
}
Любая помощь в том, как сделать эту работу, будет с благодарностью.