Читати весь файл у програмі Scala?


312

Який простий і канонічний спосіб прочитати весь файл в пам'яті в Scala? (Ідеально, з контролем над кодуванням символів.)

Найкраще, що я можу придумати, це:

scala.io.Source.fromPath("file.txt").getLines.reduceLeft(_+_)

чи я повинен використовувати одну з жахливих ідіом Java , найкращою з яких (без використання зовнішньої бібліотеки) є:

import java.util.Scanner
import java.io.File
new Scanner(new File("file.txt")).useDelimiter("\\Z").next()

З читання дискусій зі списку розсилки мені не ясно, що scala.io.Source навіть має бути канонічною бібліотекою вводу-виводу. Я точно не розумію, яке його цільове призначення.

... Я хотів би щось мертве-просте і легке запам'ятати. Наприклад, в цих мовах дуже важко забути ідіому ...

Ruby    open("file.txt").read
Ruby    File.read("file.txt")
Python  open("file.txt").read()

12
Java не погана, якщо ви знаєте правильні інструменти. імпорт org.apache.commons.io.FileUtils; FileUtils.readFileToString (новий файл ("file.txt", "UTF-8")
smartnut007

25
Цей коментар не вистачає мовного дизайну. Будь-яка мова, яка має просту функцію бібліотеки для саме тієї операції, яку ви хочете виконати, є такою ж хорошою, як і синтаксис виклику функції. Враховуючи нескінченну і 100% запам'ятовувану бібліотеку, всі програми будуть реалізовуватися за допомогою одного виклику функції. Мова програмування хороша тоді, коли для досягнення конкретного результату потрібно вже менше компонентів попереднього налаштування.
Кріс Маунтфорд

Відповіді:


429
val lines = scala.io.Source.fromFile("file.txt").mkString

До речі, " scala." насправді не потрібно, оскільки це все одно в області застосування, і, звичайно, ви можете імпортувати вміст io повністю або частково і уникати необхідності додавати "io". теж.

Однак, вищезазначений файл залишає відкритим файл. Щоб уникнути проблем, слід закрити його так:

val source = scala.io.Source.fromFile("file.txt")
val lines = try source.mkString finally source.close()

Ще одна проблема коду, що вказаний вище, полягає в тому, що він жахливо повільний через характер його реалізації. Для великих файлів слід використовувати:

source.getLines mkString "\n"

48
Я занадто пізно на вечірку, але мені б не подобалося, щоб люди не знали, що вони можуть робити "io.File (" / etc / passwd "). Slurp" в багажнику.
psp

28
@extempore Якщо ти справді думаєш, що я невдячний, мені справді шкода. Я глибоко вдячний за вашу підтримку мови Scala і кожен раз, коли ви особисто розглядали проблему, яку я порушив, пропонували вирішити проблему, яку я мав, або щось мені пояснили. Тоді я скористаюсь нагодою, щоб подякувати вам за те, що ви перетворили scala.io на щось пристойне та гідне. Я відтепер буду більш вразливим в подяці, але все одно ненавиджу ім'я, вибачте.
Даніель К. Собрал

49
"slurp" - це назва для читання всього файлу одразу в Perl протягом багатьох років. Perl має більш вісцеральну та неофіційну традицію називання, ніж сімейство мов C, що дехто може здатися неприємним, але в цьому випадку я думаю, що це підходить: це потворне слово для потворної практики. Коли балакаєш (), ти знаєш, що робиш щось неслухняне, тому що ти просто мусив це набрати.
Маркус Даунінг

15
File.read () було б приємнішим іменем, і до того ж відповідало б Ruby та Python.
Брендан Оконнор

26
@extempore: ви не можете запобігти людям огиду. Це просто так. Це не повинно вас турбувати, що деяким людям не подобається кожен ваш вибір. Це просто життя, ви можете не всім догодити :)
Алекс Бараноський,

58

Просто для розширення рішення Даніеля ви можете значно скоротити речі, вставивши наступний імпорт у будь-який файл, який вимагає маніпуляції файлами:

import scala.io.Source._

З цим ви тепер можете зробити:

val lines = fromFile("file.txt").getLines

Я б насторожено читав цілий файл в єдиний String. Це дуже погана звичка - та, яка кусає вас швидше і сильніше, ніж ви думаєте. getLinesМетод повертає значення типу Iterator[String]. Це ефективно ледачий курсор у файлі, що дозволяє вивчити лише потрібні вам дані, не ризикуючи перенаситити пам'ять.

О, і щоб відповісти на ваше загальне питання про Source: так, це канонічна бібліотека вводу-виводу. Більшість кодів закінчується використанням java.ioчерез його нижчий рівень інтерфейсу та кращої сумісності з існуючими рамками, але будь-який код, який має вибір, слід використовувати Source, особливо для простої обробки файлів.


ДОБРЕ. Існує історія мого негативного враження від Джерела: Колись я був у іншій ситуації, ніж зараз, де у мене був дуже великий файл, який би не вписався в пам'ять. Використання Source спричинило збій програми; виявилося, що він намагався прочитати всю справу відразу.
Brendan OConnor

7
Джерело не повинен читати весь файл в пам'ять. Якщо ви використовуєте toList після getLines або якийсь інший метод, який створить колекцію, то ви все отримаєте в пам'ять. Тепер Source - це злом , призначений для того, щоб виконати роботу, а не ретельно продумана бібліотека. Він буде вдосконалений у Scala 2.8, але безумовно є можливість для спільноти Scala активізуватися у визначенні хорошого API вводу / виводу.
Даніель К. Собрал

36
// for file with utf-8 encoding
val lines = scala.io.Source.fromFile("file.txt", "utf-8").getLines.mkString

6
Якщо до оригінальної відповіді "getLines" видалити всі нові рядки. Повинен бути "Source.fromFile (" file.txt "," utf-8 "). MkString".
Jo23 23

9
Дивіться також мій коментар у відповіді Даніеля С. Собраля - це використання не закриє екземпляр Source, тому Scala може зберегти блокування у файлі.
djb

26

(EDIT: Це не працює в масштабі 2.9, а може, і не в 2.8)

Використовувати багажник:

scala> io.File("/etc/passwd").slurp
res0: String = 
##
# User Database
# 
... etc

14
" slurp"? Чи справді ми відкинули очевидну, інтуїтивну назву? Проблема slurpполягає в тому, що це може мати сенс для когось із англійською мовою як першої мови, принаймні, але ви ніколи не подумаєте про це з початку!
Даніель К. Собрал

5
Просто натрапили на це питання / відповідь. Fileбільше не в 2.8.0, чи не так?
huynhjl

4
сурмуче звучить чудово. :) Я б цього не очікував, але я не очікував, що вихід на екран буде названий "друком". slurpце фантастично! :) Було фантастично? Я не знаходжу його. ; (
користувачеві невідомо

5
у scala-2.10.0 назва пакету - scala.reflect.io.File І питання щодо цього "Файл". extempore, чому цей файл позначений як "експериментальний"? Це безпечно? Це вільне блокування файлової системи?
ВасильНовіков

4
slurp має давню історію з цією метою, що походить, з Perl,
Кріс Маунтфорд,

18
import java.nio.charset.StandardCharsets._
import java.nio.file.{Files, Paths}

new String(Files.readAllBytes(Paths.get("file.txt")), UTF_8)

Контроль за кодуванням символів та відсутність ресурсів для очищення. Також можливо оптимізовано (наприклад, Files.readAllBytesвиділення байтового масиву, відповідного розміру файлу).


7

Мені сказали, що Source.fromFile є проблематичним. Особисто у мене виникли проблеми з відкриттям великих файлів з Source.fromFile, і мені довелося вдатися до Java InputStreams.

Ще одне цікаве рішення - використання шкали. Ось приклад добре коментованого коду, який відкриває файл журналу за допомогою ManagedResource для відкриття файлу з помічниками масштабування: http://pastie.org/pastes/420714


6

Використання getLines () на scala.io.Source відкидає, які символи використовувались для термінальних рядків (\ n, \ r, \ r \ n тощо)

Далі слід зберегти його як символ для символу і не робити надмірне об'єднання рядків (проблеми з продуктивністю):

def fileToString(file: File, encoding: String) = {
  val inStream = new FileInputStream(file)
  val outStream = new ByteArrayOutputStream
  try {
    var reading = true
    while ( reading ) {
      inStream.read() match {
        case -1 => reading = false
        case c => outStream.write(c)
      }
    }
    outStream.flush()
  }
  finally {
    inStream.close()
  }
  new String(outStream.toByteArray(), encoding)
}

6

Ще один: https://github.com/pathikrit/better-files#streams-and-codecs

Різні способи передачі файлу без завантаження вмісту в пам'ять:

val bytes  : Iterator[Byte]            = file.bytes
val chars  : Iterator[Char]            = file.chars
val lines  : Iterator[String]          = file.lines
val source : scala.io.BufferedSource   = file.content 

Ви також можете надати свій власний кодек для будь-якого, що читає / записує (він передбачає scala.io.Codec.default, якщо ви його не надаєте):

val content: String = file.contentAsString  // default codec
// custom codec:
import scala.io.Codec
file.contentAsString(Codec.ISO8859)
//or
import scala.io.Codec.string2codec
file.write("hello world")(codec = "US-ASCII")

5

Як і в Java, використовуючи бібліотеку CommonsIO:

FileUtils.readFileToString(file, StandardCharsets.UTF_8)

Також багато відповідей тут забувають Charset. Краще завжди надавати це явно, інакше це вдарить одного дня.


4

Для емуляції синтаксису Ruby (та передачі семантики) відкриття та читання файлу врахуйте цей неявний клас (Scala 2.10 та верхніх),

import java.io.File

def open(filename: String) = new File(filename)

implicit class RichFile(val file: File) extends AnyVal {
  def read = io.Source.fromFile(file).getLines.mkString("\n")
}

Таким чином,

open("file.txt").read

3

як мало хто згадав scala.io.Source найкраще уникати через витоки підключення.

Ймовірно, масштабування та чисті java libs, як commons-io, - найкращі варіанти, поки новий проект інкубатора (тобто scala-io) не злиється.


3

Ви також можете використовувати Шлях від масштабування для читання та обробки файлів.

import scalax.file.Path

Тепер ви можете отримати шлях до файлу за допомогою цього: -

val filePath = Path("path_of_file_to_b_read", '/')
val lines = file.lines(includeTerminator = true)

Ви також можете включити термінатори, але за замовчуванням встановлено значення false.


3

Для швидшого загального читання / завантаження (великого) файлу, врахуйте збільшення розміру bufferSize( Source.DefaultBufSizeвстановленого 2048), наприклад, наступним чином,

val file = new java.io.File("myFilename")
io.Source.fromFile(file, bufferSize = Source.DefaultBufSize * 2)

Примітка Source.scala . Для подальшого обговорення див. Читання та завантаження у пам'ять швидкого текстового файлу Scala .


3

Вам не потрібно розбирати кожен рядок, а потім з’єднувати їх знову ...

Source.fromFile(path)(Codec.UTF8).mkString

Я вважаю за краще використовувати це:

import scala.io.{BufferedSource, Codec, Source}
import scala.util.Try

def readFileUtf8(path: String): Try[String] = Try {
  val source: BufferedSource = Source.fromFile(path)(Codec.UTF8)
  val content = source.mkString
  source.close()
  content
}

Вам слід закрити потік - якщо сталася помилка вval content = source.mkString
Анджей Йозвік

+1 для Codec. У мене не вдалося випробувати тест, sbt testоскільки не можу його встановити, тоді як команда тесту Intellij проходить усі тести. І ви можете використовувати def usingвід цього
Михайло Іонкін

3

Якщо ви не заперечуєте проти сторонніх залежностей, варто подумати про використання моєї бібліотеки OS-Lib . Це робить читання / запис файлів та роботу з файловою системою дуже зручним:

// Make sure working directory exists and is empty
val wd = os.pwd/"out"/"splash"
os.remove.all(wd)
os.makeDir.all(wd)

// Read/write files
os.write(wd/"file.txt", "hello")
os.read(wd/"file.txt") ==> "hello"

// Perform filesystem operations
os.copy(wd/"file.txt", wd/"copied.txt")
os.list(wd) ==> Seq(wd/"copied.txt", wd/"file.txt")

з однорядними помічниками для читання байтів , читання фрагментів , читання рядків та багатьох інших корисних / звичайних операцій


2

Очевидним є питання "чому ти хочеш читати весь файл?" Це, очевидно, не є масштабним рішенням, якщо ваші файли дуже великі. scala.io.SourceДає спинки Iterator[String]від getLinesметоду, який є дуже корисним і коротким.

Не так багато роботи, щоб створити неявну конверсію, використовуючи основні утиліти Java IO для перетворення a File, a Readerчи a InputStreamв a String. Я думаю, що відсутність масштабованості означає, що вони правильні, щоб не додавати це до стандартного API.


12
Серйозно? Скільки файлів ви насправді регулярно читаєте, у яких виникають реальні проблеми з пам’яттю? Переважна більшість файлів у переважній більшості програм, з якими я коли-небудь стикався, досить малі, щоб вміститись у пам'яті. Відверто кажучи, великі файли даних - це виняток, і ви повинні усвідомити це і відповідно програмувати, якщо ви збираєтесь їх читати / записувати.
Крістофер

8
oxbow_lakes, я не згоден. Існує багато ситуацій, пов’язаних із невеликими файлами, розмір яких у майбутньому не збільшуватиметься.
Брендан ОКоннор

4
Я погоджуюсь, що вони є винятком, але я думаю, що саме тому в пам'яті зчитування всього файлу не є ні в JDK, ні в SDK Scala. Це 3-
рядовий утилітний

1

друкуйте кожен рядок, наприклад, використовуйте Java BufferedReader для читання ervery рядка та роздруковуйте його:

scala.io.Source.fromFile("test.txt" ).foreach{  print  }

еквівалент:

scala.io.Source.fromFile("test.txt" ).foreach( x => print(x))

0
import scala.io.source
object ReadLine{
def main(args:Array[String]){
if (args.length>0){
for (line <- Source.fromLine(args(0)).getLine())
println(line)
}
}

в аргументах ви можете надати шлях до файлу, і він поверне всі рядки


3
Що пропонує ця пропозиція, на яку не відповідає інша відповідь?
jwvh

Я не бачив інших відповідей ... просто думав, що я можу зробити свій внесок тут, опублікований ... сподіваюсь, це нікому не зашкодить :)
Квітень

1
Ви дійсно повинні їх прочитати. Більшість досить інформативні. Навіть ті, кому 8 років, мають відповідну інформацію.
jwvh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.