Czytasz cały plik w Scali?

312

Jaki jest prosty i kanoniczny sposób wczytywania całego pliku do pamięci w Scali? (Idealnie, z kontrolą kodowania znaków.)

Najlepsze, co mogę wymyślić, to:

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

czy mam użyć jednego z okropnych idiomów Javy , z których najlepszym (bez użycia zewnętrznej biblioteki) wydaje się być:

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

Z lektury dyskusji na temat list mailingowych nie jest dla mnie jasne, że scala.io.Source ma być nawet kanoniczną biblioteką I / O. Nie rozumiem dokładnie, jaki jest jego zamierzony cel.

... Chciałbym coś prostego i łatwego do zapamiętania. Na przykład w tych językach bardzo trudno zapomnieć o idiomie ...

Ruby    open("file.txt").read
Ruby    File.read("file.txt")
Python  open("file.txt").read()
Brendan OConnor
źródło
12
Java nie jest taka zła, jeśli znasz odpowiednie narzędzia. import org.apache.commons.io.FileUtils; FileUtils.readFileToString (nowy plik („file.txt”, „UTF-8”)
smartnut007
25
Ten komentarz pomija sens projektowania języka. Każdy język, który ma prostą funkcję biblioteczną dla dokładnie tego, co chcesz wykonać, jest zatem tak dobry, jak jego składnia wywołania funkcji. Biorąc pod uwagę nieskończoną i w 100% zapamiętaną bibliotekę, wszystkie programy byłyby realizowane za pomocą jednego wywołania funkcji. Język programowania jest dobry, gdy potrzebuje już mniej gotowych komponentów, aby osiągnąć określony wynik.
Chris Mountford

Odpowiedzi:

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

Nawiasem mówiąc, „ scala.” nie jest tak naprawdę konieczne, ponieważ i tak zawsze ma zasięg, i możesz oczywiście zaimportować zawartość io, całkowicie lub częściowo, i unikać konieczności dodawania „io”. też.

Powyższe pozostawia jednak plik otwarty. Aby uniknąć problemów, zamknij go w następujący sposób:

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

Innym problemem związanym z powyższym kodem jest to, że jest on strasznie wolny ze względu na jego charakter implementacyjny. W przypadku większych plików należy użyć:

source.getLines mkString "\n"
Daniel C. Sobral
źródło
48
Spóźniłem się na przyjęcie, ale nie chciałbym, żeby ludzie nie wiedzieli, że mogą zrobić „io.File („ / etc / passwd ”). Slurp” w bagażniku.
psp
28
@extempore Jeśli naprawdę uważasz, że jestem niewdzięczny, naprawdę mi przykro. Bardzo doceniam twoje wsparcie dla języka Scala i za każdym razem, gdy osobiście zajmowałeś się poruszonym przeze mnie problemem, sugerowałam rozwiązanie mojego problemu lub coś mi wyjaśniał. Wykorzystam więc okazję, aby podziękować za przekształcenie scala.io w coś przyzwoitego i godnego. Od teraz będę bardziej wyrażał moje podziękowania, ale wciąż nie znoszę tego imienia, przepraszam.
Daniel C. Sobral
49
„slurp” to nazwa odczytywania całego pliku na raz w Perlu od wielu lat. Perl ma bardziej nazistowską i nieformalną tradycję nazywania niż rodzina języków C, co niektórzy mogą uznać za niesmaczne, ale w tym przypadku myślę, że pasuje: to brzydkie słowo na brzydką praktykę. Kiedy slurp (), wiesz, że robisz coś niegrzecznego, ponieważ po prostu musiałeś to wpisać.
Marcus Downing
15
File.read () byłby ładniejszy i zgodny z Ruby i Pythonem.
Brendan OConnor,
26
@extempore: nie możesz powstrzymać ludzi od obrzydzenia. Tak po prostu jest. Nie powinno ci przeszkadzać, że niektórym ludziom nie podoba się każdy dokonany wybór. To tylko życie, nie możesz zadowolić wszystkich :)
Alex Baranosky,
58

Aby rozwinąć rozwiązanie Daniela, możesz ogromnie skrócić ten proces, wstawiając następujący import do dowolnego pliku wymagającego manipulacji:

import scala.io.Source._

Dzięki temu możesz teraz:

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

Byłbym ostrożny, czytając cały plik w jeden String. To bardzo zły nawyk, który gryzie cię wcześniej i mocniej niż myślisz. getLinesMetoda zwraca wartość typu Iterator[String]. W rzeczywistości jest to leniwy kursor w pliku, pozwalający na sprawdzenie tylko potrzebnych danych bez ryzyka nadmiaru pamięci.

Aha, i aby odpowiedzieć na twoje dorozumiane pytanie dotyczące Source: tak, jest to kanoniczna biblioteka we / wy. Większość kodu używa java.ioze względu na interfejs niższego poziomu i lepszą kompatybilność z istniejącymi frameworkami, ale każdy kod, który ma wybór, powinien używać Source, szczególnie do prostej manipulacji plikami.

Daniel Spiewak
źródło
OK. Jest taka historia o moim negatywnym wrażeniu na Source: kiedyś byłem w innej sytuacji niż teraz, gdzie miałem bardzo duży plik, który nie mieściłby się w pamięci. Korzystanie ze źródła spowodowało awarię programu; okazało się, że próbował odczytać całość naraz.
Brendan OConnor,
7
Źródło nie powinno wczytywać całego pliku do pamięci. Jeśli użyjesz toList po getLines lub jakiejś innej metodzie, która utworzy kolekcję, to dostaniesz wszystko do pamięci. Teraz Source to hack , mający na celu wykonanie zadania, a nie starannie przemyślana biblioteka. Zostanie on ulepszony w Scali 2.8, ale z pewnością społeczność Scala ma okazję aktywnie definiować dobry interfejs API we / wy.
Daniel C. Sobral,
36
// for file with utf-8 encoding
val lines = scala.io.Source.fromFile("file.txt", "utf-8").getLines.mkString
Walter Chang
źródło
6
Dodanie „getLines” do oryginalnej odpowiedzi spowoduje usunięcie wszystkich nowych linii. Powinien to być „Source.fromFile („ file.txt ”,„ utf-8 ”). MkString”.
Joe23
9
Zobacz także mój komentarz w odpowiedzi Daniela C. Sobrala - to użycie nie zamknie instancji Source, więc Scala może zachować blokadę pliku.
djb
26

(EDYCJA: To nie działa w Scala 2.9 i może nie 2.8)

Użyj pnia:

scala> io.File("/etc/passwd").slurp
res0: String = 
##
# User Database
# 
... etc
psp
źródło
14
slurp”? Czy naprawdę porzuciliśmy oczywistą, intuicyjną nazwę? Problem slurppolega na tym, że może to mieć sens dla kogoś, kto zna angielski jako pierwszy język, ale nigdy nie pomyślałbyś o tym!
Daniel C. Sobral
5
Natknąłem się na to pytanie / odpowiedź. Filenie jest już w 2.8.0, prawda?
huynhjl
4
slurp brzmi świetnie. :) Nie spodziewałbym się tego, ale nie spodziewałem się też, że wyjście na ekranie będzie miało nazwę „drukuj”. slurpjest fantastyczna! :) Był fantastyczny? Nie znajduję tego ; (
użytkownik nieznany
5
w scala-2.10.0 nazwa pakietu to scala.reflect.io.File I pytanie o ten „plik”. extempore, dlaczego ten plik jest oznaczony jako „eksperymentalny”? Czy to jest bezpieczne? Czy zwalnia blokadę systemu plików?
VasiliNovikov
4
slurp ma w tym celu długą historię, wywodzącą się, jak sądzę, z perla
Chris Mountford
18
import java.nio.charset.StandardCharsets._
import java.nio.file.{Files, Paths}

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

Kontrola kodowania znaków i brak zasobów do czyszczenia. Również ewentualnie zoptymalizowany (np. Files.readAllBytesAlokując tablicę bajtów odpowiednią do rozmiaru pliku).

Paul Draper
źródło
7

Powiedziano mi, że Source.fromFile jest problematyczny. Osobiście miałem problemy z otwieraniem dużych plików za pomocą Source.fromFile i musiałem skorzystać z Java InputStreams.

Innym ciekawym rozwiązaniem jest użycie skalaksu. Oto przykład dobrze skomentowanego kodu, który otwiera plik dziennika za pomocą ManagedResource w celu otwarcia pliku za pomocą helperów: http://pastie.org/pastes/420714

Ikai Lan
źródło
6

Użycie getLines () na scala.io.Source odrzuca znaki, które zostały użyte dla terminatorów linii (\ n, \ r, \ r \ n itd.)

Poniższe powinny zachować znak po znaku i nie powodują nadmiernej konkatenacji ciągów (problemy z wydajnością):

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)
}
Muyyatin
źródło
6

Jeszcze jeden: https://github.com/pathikrit/better-files#streams-and-codecs

Różne sposoby usuwania pliku bez ładowania zawartości do pamięci:

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 

Możesz także podać swój własny kodek do wszystkiego, co wykonuje odczyt / zapis (przyjmuje kod scala.io.Codec.default, jeśli go nie podasz):

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")
pathikrit
źródło
5

Podobnie jak w Javie, używając biblioteki CommonsIO:

FileUtils.readFileToString(file, StandardCharsets.UTF_8)

Wiele odpowiedzi tutaj zapomina o Charsecie. Lepiej zawsze podawać to wprost, bo inaczej pewnego dnia trafi.

Dzmitry Lazerka
źródło
4

Aby emulować składnię Rubiego (i przekazać semantykę) otwierania i czytania pliku, rozważ tę ukrytą klasę (Scala 2.10 i wyższa),

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")
}

W ten sposób,

open("file.txt").read
wiąz
źródło
3

jak kilka osób wspomniało o scala.io.Source najlepiej unikać ze względu na wycieki z połączenia.

Prawdopodobnie scalax i czyste biblioteki Java, takie jak commons-io, są najlepszymi opcjami do czasu scalenia nowego projektu inkubatora (tj. Scala-io).

poko
źródło
3

możesz także użyć Ścieżki od scala io do odczytu i przetwarzania plików.

import scalax.file.Path

Teraz możesz uzyskać ścieżkę do pliku, używając: -

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

Możesz także dołączyć terminatory, ale domyślnie jest ustawiony na false.

Atiq
źródło
3

Aby przyspieszyć ogólny odczyt / przesyłanie (dużego) pliku, rozważ zwiększenie rozmiaru bufferSize( Source.DefaultBufSizeustaw na 2048), na przykład w następujący sposób:

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

Uwaga Source.scala . Dalsza dyskusja znajduje się w sekcji Szybki odczyt pliku tekstowego Scala i przesłanie do pamięci .

wiąz
źródło
3

Nie musisz analizować każdej linii, a następnie ponownie łączyć je ...

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

Wolę użyć tego:

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
}
comonad
źródło
Powinieneś zamknąć strumień - jeśli wystąpi błądval content = source.mkString
Andrzej Jozwik
+1 dla Codec. Mam błąd testu, sbt testponieważ nie mogę go ustawić, podczas gdy polecenie testowe Intellij zda wszystkie testy. I możesz def usingz tego skorzystać
Michaił Ionkin,
3

Jeśli nie masz nic przeciwko zależności od innych firm, powinieneś rozważyć użycie mojej biblioteki OS-Lib . Dzięki temu odczytywanie / zapisywanie plików i praca z systemem plików jest bardzo wygodna:

// 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")

z pomocnikami jednowierszowymi do odczytu bajtów , odczytu fragmentów , czytania linii i wielu innych przydatnych / typowych operacji

Li Haoyi
źródło
2

Oczywistym pytaniem jest „dlaczego chcesz przeczytać w całym pliku?” To oczywiście nie jest skalowalne rozwiązanie, jeśli twoje pliki stają się bardzo duże. scala.io.SourceDaje grzbiecie Iterator[String]z getLinesmetody, która jest bardzo przydatna i zwięzły.

Wymyślenie niejawnej konwersji przy użyciu podstawowych narzędzi IO Java do konwersji a File, a Readerlub a InputStreamna String. Nie jest wielkim zadaniem . Myślę, że brak skalowalności oznacza, że ​​słusznie nie dodają tego do standardowego interfejsu API.

oxbow_lakes
źródło
12
Poważnie? Ile plików czytasz na bieżąco, które mają prawdziwe problemy z dopasowaniem się do pamięci? Zdecydowana większość plików w zdecydowanej większości programów, z którymi kiedykolwiek miałem do czynienia, jest wystarczająco mała, aby zmieścić się w pamięci. Szczerze mówiąc, pliki big data są wyjątkiem i powinieneś zdać sobie z tego sprawę i odpowiednio je zaprogramować, jeśli zamierzasz je czytać / zapisywać.
Christopher
8
oxbow_lakes, nie zgadzam się. Istnieje wiele sytuacji związanych z małymi plikami, których rozmiar nie wzrośnie w przyszłości.
Brendan OConnor,
4
Zgadzam się, że są one wyjątkiem - ale myślę, że właśnie dlatego odczytu całego pliku do pamięci nie ma ani w JDK, ani w Scala SDK. Jest to 3-liniowa metoda służąca do samodzielnego pisania:
pokonaj
1

wypisz każdą linię, np. użyj Java BufferedReader przeczytaj każdą linię i wydrukuj:

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

odpowiednik:

scala.io.Source.fromFile("test.txt" ).foreach( x => print(x))
Gordonpro
źródło
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)
}
}

w argumentach możesz podać ścieżkę do pliku, a to zwróci wszystkie wiersze

Apurw
źródło
3
Co oferuje ta oferta, której nie ma druga odpowiedź?
jwvh
Nie widziałem innych odpowiedzi ... po prostu pomyślałem, że mogę się tutaj
zgłosić,
1
Naprawdę powinieneś je przeczytać. Większość ma charakter informacyjny. Nawet te, które mają 8 lat, mają odpowiednie informacje.
jwvh