Ganze Datei in Scala lesen?

312

Was ist eine einfache und kanonische Methode, um eine ganze Datei in Scala in den Speicher zu lesen? (Idealerweise mit Kontrolle über die Zeichenkodierung.)

Das Beste, was ich mir einfallen lassen kann, ist:

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

oder soll ich eine von Javas gottesfürchtigen Redewendungen verwenden , von denen die beste (ohne Verwendung einer externen Bibliothek) zu sein scheint:

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

Durch das Lesen von Mailinglistendiskussionen ist mir nicht klar, dass scala.io.Source sogar die kanonische E / A-Bibliothek sein soll. Ich verstehe nicht genau, was der beabsichtigte Zweck ist.

... Ich hätte gerne etwas ganz einfaches und leicht zu merkendes. Zum Beispiel ist es in diesen Sprachen sehr schwer, die Redewendung zu vergessen ...

Ruby    open("file.txt").read
Ruby    File.read("file.txt")
Python  open("file.txt").read()
Brendan OConnor
quelle
12
Java ist nicht so schlecht, wenn Sie die richtigen Tools kennen. import org.apache.commons.io.FileUtils; FileUtils.readFileToString (neue Datei ("file.txt", "UTF-8")
smartnut007
25
Dieser Kommentar verfehlt den Punkt der Sprachgestaltung. Jede Sprache, die eine einfache Bibliotheksfunktion für genau die Operation zur Verfügung hat, die Sie ausführen möchten, ist daher so gut wie ihre Funktionsaufrufsyntax. Bei einer unendlichen und zu 100% gespeicherten Bibliothek würden alle Programme mit einem einzigen Funktionsaufruf implementiert. Eine Programmiersprache ist gut, wenn weniger vorgefertigte Komponenten vorhanden sein müssen, um ein bestimmtes Ergebnis zu erzielen.
Chris Mountford

Antworten:

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

Übrigens ist " scala." nicht wirklich notwendig, da es sowieso immer im Geltungsbereich liegt, und Sie können natürlich den Inhalt von io ganz oder teilweise importieren und vermeiden, "io" voranstellen zu müssen. auch.

Das Obige lässt die Datei jedoch offen. Um Probleme zu vermeiden, sollten Sie es folgendermaßen schließen:

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

Ein weiteres Problem mit dem obigen Code ist, dass er aufgrund seiner Implementierung schrecklich langsam ist. Für größere Dateien sollte man verwenden:

source.getLines mkString "\n"
Daniel C. Sobral
quelle
48
Ich bin zu spät zur Party, aber ich würde es hassen, wenn die Leute nicht wissen, dass sie "io.File (" / etc / passwd "). Slurp" im Kofferraum machen können.
PSP
28
@extempore Wenn du wirklich denkst, dass ich undankbar bin, tut es mir wirklich leid. Ich bin sehr dankbar für Ihre Unterstützung der Scala-Sprache und jedes Mal, wenn Sie sich persönlich mit einem von mir angesprochenen Problem befasst, eine Lösung für ein Problem vorgeschlagen oder mir etwas erklärt haben. Ich werde die Gelegenheit nutzen, um Ihnen zu danken, dass Sie scala.io in etwas Anständiges und Würdiges verwandelt haben. Ich werde mich von nun an lautstark bedanken, aber ich hasse den Namen immer noch, sorry.
Daniel C. Sobral
49
"slurp" ist seit vielen Jahren der Name für das gleichzeitige Lesen einer ganzen Datei in Perl. Perl hat eine viszeralere und informellere Benennungstradition als die C-Sprachfamilie, die manche vielleicht als unangenehm empfinden, aber in diesem Fall denke ich, dass es passt: Es ist ein hässliches Wort für eine hässliche Praxis. Wenn Sie schlürfen (), wissen Sie, dass Sie etwas Unartiges tun, weil Sie das einfach eingeben mussten.
Marcus Downing
15
File.read () wäre ein schönerer Name und würde außerdem mit Ruby und Python übereinstimmen.
Brendan OConnor
26
@extempore: Sie können nicht verhindern, dass Menschen angewidert sind. Es ist einfach so wie es ist. Es sollte Sie nicht stören, dass einige Leute nicht jede Entscheidung mögen, die Sie getroffen haben. Das ist nur das Leben, du kannst nicht allen gefallen :)
Alex Baranosky
58

Um Daniels Lösung zu erweitern, können Sie die Dinge enorm verkürzen, indem Sie den folgenden Import in jede Datei einfügen, die eine Dateimanipulation erfordert:

import scala.io.Source._

Damit können Sie jetzt Folgendes tun:

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

Ich wäre vorsichtig, wenn ich eine ganze Datei in eine einzige lesen würde String. Es ist eine sehr schlechte Angewohnheit, die dich früher und härter beißen wird, als du denkst. Die getLinesMethode gibt einen Wert vom Typ zurück Iterator[String]. Es ist praktisch ein fauler Cursor in die Datei, mit dem Sie genau die Daten untersuchen können, die Sie benötigen, ohne das Risiko einer Speicherüberlastung einzugehen.

Oh, und um Ihre implizite Frage zu beantworten Source: Ja, es ist die kanonische E / A-Bibliothek. Der meiste Code wird java.ioaufgrund seiner untergeordneten Benutzeroberfläche und der besseren Kompatibilität mit vorhandenen Frameworks verwendet. Jeder Code, der eine Auswahl hat, sollte jedoch verwendet werden Source, insbesondere für die einfache Dateimanipulation.

Daniel Spiewak
quelle
OK. Es gibt eine Geschichte für meinen negativen Eindruck von Source: Ich war einmal in einer anderen Situation als jetzt, wo ich eine sehr große Datei hatte, die nicht in den Speicher passen würde. Die Verwendung von Source führte zum Absturz des Programms. es stellte sich heraus, dass es versuchte, das Ganze auf einmal zu lesen.
Brendan OConnor
7
Die Quelle soll nicht die gesamte Datei in den Speicher lesen. Wenn Sie toList after getLines oder eine andere Methode verwenden, mit der eine Sammlung erstellt wird, wird alles in den Speicher verschoben. Nun, Source ist ein Hack , der die Arbeit erledigen soll, keine sorgfältig durchdachte Bibliothek. Es wird in Scala 2.8 verbessert, aber es gibt definitiv die Möglichkeit für die Scala-Community, aktiv an der Definition einer guten E / A-API mitzuwirken.
Daniel C. Sobral
36
// for file with utf-8 encoding
val lines = scala.io.Source.fromFile("file.txt", "utf-8").getLines.mkString
Walter Chang
quelle
6
Durch Hinzufügen von "getLines" zur ursprünglichen Antwort werden alle Zeilenumbrüche entfernt. Sollte "Source.fromFile (" file.txt "," utf-8 ") sein. MkString".
Joe23
9
Siehe auch meinen Kommentar in der Antwort von Daniel C. Sobral - diese Verwendung schließt die Quellinstanz nicht, sodass Scala die Datei möglicherweise sperrt.
DJB
26

(EDIT: Dies funktioniert nicht in Scala 2.9 und vielleicht auch nicht in 2.8)

Kofferraum verwenden:

scala> io.File("/etc/passwd").slurp
res0: String = 
##
# User Database
# 
... etc
psp
quelle
14
" slurp"? Haben wir den offensichtlichen, intuitiven Namen wirklich fallen lassen? Das Problem dabei slurpist, dass es für jemanden mit Englisch als Muttersprache im Nachhinein vielleicht Sinn macht, aber man würde nie daran denken!
Daniel C. Sobral
5
Bin gerade über diese Frage / Antwort gestolpert. Fileist nicht mehr in 2.8.0, nicht wahr?
Huynhjl
4
schlürfen klingt toll. :) Ich hätte es nicht erwartet, aber ich hätte auch nicht erwartet, dass die Ausgabe auf dem Bildschirm den Namen 'print' trägt. slurpist fantastisch! :) Es war fantastisch? Ich finde es nicht ; (
Benutzer unbekannt
5
In scala-2.10.0 lautet der Paketname scala.reflect.io.File und eine Frage zu dieser "Datei". extempore, warum ist diese Datei als "experimentell" markiert? Ist es sicher? Gibt es eine Sperre für das Dateisystem frei?
VasiliNovikov
4
Slurp hat eine lange Geschichte für diesen Zweck, die, glaube ich, von Perl stammt
Chris Mountford
18
import java.nio.charset.StandardCharsets._
import java.nio.file.{Files, Paths}

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

Kontrolle über die Zeichenkodierung und keine zu bereinigenden Ressourcen. Möglicherweise auch optimiert (z. B. Files.readAllBytesZuweisen eines der Dateigröße entsprechenden Byte-Arrays).

Paul Draper
quelle
7

Mir wurde gesagt, dass Source.fromFile problematisch ist. Persönlich hatte ich Probleme beim Öffnen großer Dateien mit Source.fromFile und musste auf Java InputStreams zurückgreifen.

Eine weitere interessante Lösung ist die Verwendung von Scalax. Hier ist ein Beispiel für einen gut kommentierten Code, der eine Protokolldatei mit ManagedResource öffnet, um eine Datei mit Scalax-Helfern zu öffnen: http://pastie.org/pastes/420714

Ikai Lan
quelle
6

Mit getLines () in scala.io.Source wird verworfen, welche Zeichen für Zeilenabschlusszeichen verwendet wurden (\ n, \ r, \ r \ n usw.).

Das Folgende sollte es Zeichen für Zeichen beibehalten und keine übermäßige Verkettung von Zeichenfolgen (Leistungsprobleme) verursachen:

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
quelle
6

Noch eine: https://github.com/pathikrit/better-files#streams-and-codecs

Verschiedene Möglichkeiten, eine Datei zu schlürfen, ohne den Inhalt in den Speicher zu laden:

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 

Sie können auch Ihren eigenen Codec für alles bereitstellen, was ein Lese- / Schreibvorgang ausführt (dies setzt scala.io.Codec.default voraus, wenn Sie keinen angeben):

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
quelle
5

Genau wie in Java mit der CommonsIO-Bibliothek:

FileUtils.readFileToString(file, StandardCharsets.UTF_8)

Auch viele Antworten hier vergessen Charset. Es ist besser, es immer explizit anzugeben, sonst wird es eines Tages getroffen.

Dzmitry Lazerka
quelle
4

Berücksichtigen Sie diese implizite Klasse (Scala 2.10 und höher), um die Ruby-Syntax (und die Semantik) des Öffnens und Lesens einer Datei zu emulieren.

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

Auf diese Weise,

open("file.txt").read
Ulme
quelle
3

Wie einige Leute bereits erwähnt haben, ist scala.io.Source aufgrund von Verbindungslecks am besten zu vermeiden.

Wahrscheinlich sind Scalax und reine Java-Bibliotheken wie Commons-Io die besten Optionen, bis das neue Inkubator-Projekt (dh Scala-Io) zusammengeführt wird.

poko
quelle
3

Sie können auch Path from scala io verwenden, um Dateien zu lesen und zu verarbeiten.

import scalax.file.Path

Jetzt können Sie den Dateipfad folgendermaßen abrufen: -

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

Sie können auch Terminatoren einschließen, diese sind jedoch standardmäßig auf false gesetzt.

Atiq
quelle
3

Um das Lesen / Hochladen einer (großen) Datei insgesamt zu beschleunigen, sollten Sie die Größe von bufferSize( Source.DefaultBufSizeauf 2048) setzen, z. B. wie folgt:

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

Hinweis Source.scala . Weitere Informationen finden Sie unter Scala Fast-Textdatei lesen und in den Speicher hochladen .

Ulme
quelle
3

Sie müssen nicht jede einzelne Zeile analysieren und dann erneut verketten ...

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

Ich benutze das lieber:

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
quelle
Sie sollten den Stream schließen - wenn ein Fehler auftritt inval content = source.mkString
Andrzej Jozwik
+1 für Codec. Ich habe einen sbt testTestfehler erhalten, weil ich ihn nicht einstellen kann, während der Testbefehl von Intellij alle Tests besteht. Und Sie können verwenden def usingvon dieser
Mikhail Ionkin
3

Wenn Ihnen eine Abhängigkeit von Drittanbietern nichts ausmacht, sollten Sie meine OS-Lib-Bibliothek verwenden . Dies macht das Lesen / Schreiben von Dateien und das Arbeiten mit dem Dateisystem sehr bequem:

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

mit einzeiligen Helfern zum Lesen von Bytes , Lesen von Chunks , Lesen von Zeilen und vielen anderen nützlichen / allgemeinen Operationen

Li Haoyi
quelle
2

Die offensichtliche Frage lautet: "Warum möchten Sie die gesamte Datei einlesen?" Dies ist offensichtlich keine skalierbare Lösung, wenn Ihre Dateien sehr groß werden. Das scala.io.Sourcegibt Ihnen eine Iterator[String]von der getLinesMethode zurück, die sehr nützlich und prägnant ist.

Es ist keine große Aufgabe, eine implizite Konvertierung mit den zugrunde liegenden Java-E / A-Dienstprogrammen zu erstellen, um a File, a Readeroder a InputStreamin a zu konvertieren String. Ich denke, dass die mangelnde Skalierbarkeit bedeutet, dass sie richtig sind, dies nicht zur Standard-API hinzuzufügen.

oxbow_lakes
quelle
12
Ernsthaft? Wie viele Dateien lesen Sie wirklich regelmäßig, bei denen es wirklich Probleme gibt, in den Speicher zu passen? Die überwiegende Mehrheit der Dateien in der überwiegenden Mehrheit der Programme, mit denen ich mich jemals befasst habe, ist leicht klein genug, um in den Speicher zu passen. Ehrlich gesagt sind Big-Data-Dateien die Ausnahme, und Sie sollten dies erkennen und entsprechend programmieren, wenn Sie sie lesen / schreiben möchten.
Christopher
8
oxbow_lakes, ich bin anderer Meinung. Es gibt viele Situationen mit kleinen Dateien, deren Größe in Zukunft nicht mehr zunehmen wird.
Brendan OConnor
4
Ich bin damit einverstanden, dass sie die Ausnahme sind - aber ich denke, deshalb befindet sich das Lesen einer gesamten Datei in den Speicher weder im JDK noch im Scala SDK. Es ist eine dreizeilige Dienstprogrammmethode, mit der Sie sich selbst schreiben können:
Überwinde
1

Drucken Sie jede Zeile aus, wie z. B. Java BufferedReader, lesen Sie jede Zeile und drucken Sie sie aus:

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

Äquivalent:

scala.io.Source.fromFile("test.txt" ).foreach( x => print(x))
gordonpro
quelle
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)
}
}

In Argumenten können Sie einen Dateipfad angeben, der alle Zeilen zurückgibt

Apurw
quelle
3
Was bietet das, was die andere Antwort nicht bietet?
Jwvh
Ich habe keine anderen Antworten gesehen ... dachte nur, ich kann hier einen Beitrag leisten, der so gepostet wurde ... hoffentlich schadet das niemandem :)
Apurw
1
Du solltest sie wirklich lesen. Die meisten sind sehr informativ. Sogar diejenigen, die 8 Jahre alt sind, haben relevante Informationen.
JWVH