Leggi l'intero file in Scala?


312

Qual è un modo semplice e canonico per leggere un intero file in memoria in Scala? (Idealmente, con controllo sulla codifica dei caratteri.)

Il meglio che posso inventare è:

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

o dovrei usare uno dei terribili idiomi di Java , il migliore dei quali (senza usare una libreria esterna) sembra essere:

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

Dalla lettura delle discussioni sulla mailing list, non è chiaro per me che scala.io.Source sia addirittura la libreria di I / O canonica. Non capisco esattamente quale sia lo scopo previsto.

... Vorrei qualcosa di estremamente semplice e facile da ricordare. Ad esempio, in queste lingue è molto difficile dimenticare il linguaggio ...

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

12
Java non è poi così male se conosci gli strumenti giusti. import org.apache.commons.io.FileUtils; FileUtils.readFileToString (nuovo file ("file.txt", "UTF-8")
smartnut007

25
Questo commento manca il punto del design del linguaggio. Qualsiasi linguaggio che abbia a disposizione una semplice funzione di libreria esattamente per l'operazione che si desidera eseguire è quindi buono quanto la sua sintassi di invocazione della funzione. Data una libreria infinita e memorizzata al 100%, tutti i programmi verrebbero implementati con una singola chiamata di funzione. Un linguaggio di programmazione è valido quando necessita già di meno componenti pre-fab per ottenere un risultato specifico.
Chris Mountford,

Risposte:


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

A proposito, " scala." non è davvero necessario, poiché è sempre nell'ambito di applicazione, e puoi, ovviamente, importare i contenuti di io, in tutto o in parte, ed evitare di dover anteporre "io". pure.

Quanto sopra lascia comunque aperto il file. Per evitare problemi, dovresti chiuderlo in questo modo:

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

Un altro problema con il codice sopra è che è orribile lento a causa della sua natura di implementazione. Per file più grandi si dovrebbe usare:

source.getLines mkString "\n"

48
Sono troppo tardi per la festa, ma odio che le persone non sappiano che possono fare "io.File (" / etc / passwd "). Slurp" nel bagagliaio.
psp,

28
@extempore Se pensi davvero che io sia grato, mi dispiace davvero. Apprezzo profondamente il tuo sostegno al linguaggio Scala e ogni volta che hai esaminato personalmente un problema che ho sollevato, suggerito una soluzione a un problema che ho avuto o mi hai spiegato qualcosa. Ne approfitterò, quindi, per ringraziarti per aver trasformato scala.io in qualcosa di decente e degno. Sarò più vocale nei miei ringraziamenti da ora in poi, ma odio ancora il nome, scusa.
Daniel C. Sobral,

49
"slurp" è stato il nome per la lettura di un intero file contemporaneamente in Perl per molti anni. Perl ha una tradizione di denominazione più viscerale e informale rispetto alla famiglia di lingue C, che alcuni possono trovare sgradevoli, ma in questo caso penso che si adatti: è una brutta parola per una brutta pratica. Quando bevi (), sai che stai facendo qualcosa di cattivo perché devi solo digitarlo.
Marcus Downing,

15
File.read () sarebbe un nome più bello e coerente con Ruby e Python oltre.
Brendan OConnor,

26
@extempore: non puoi impedire alle persone di essere disgustate. È così. Non dovrebbe darti fastidio che ad alcune persone non piaccia ogni scelta che hai fatto. È solo la vita, non puoi piacere a tutti :)
Alex Baranosky,

58

Solo per espandere la soluzione di Daniel, puoi abbreviare enormemente le cose inserendo la seguente importazione in qualsiasi file che richiede la manipolazione dei file:

import scala.io.Source._

Con questo, ora puoi fare:

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

Diffiderei di leggere un intero file in un singolo String. È un'abitudine molto brutta, che ti morderà prima e più forte di quanto pensi. Il getLinesmetodo restituisce un valore di tipo Iterator[String]. È effettivamente un cursore pigro nel file, che consente di esaminare solo i dati necessari senza rischiare l'eccesso di memoria.

Oh, e per rispondere alla tua domanda implicita su Source: sì, è la libreria I / O canonica. La maggior parte del codice finisce per essere utilizzata java.iograzie alla sua interfaccia di livello inferiore e una migliore compatibilità con i framework esistenti, ma qualsiasi codice che ha una scelta dovrebbe essere utilizzato Source, in particolare per la semplice manipolazione dei file.


OK. C'è una storia per la mia impressione negativa di Source: una volta mi trovavo in una situazione diversa rispetto ad ora, dove avevo un file molto grande che non si adattava alla memoria. L'uso di Source ha causato l'arresto anomalo del programma; si è scoperto che stava cercando di leggere tutto in una volta.
Brendan OConnor,

7
L'origine non dovrebbe leggere l'intero file in memoria. Se usi toList dopo getLines o un altro metodo che produrrà una raccolta, otterrai tutto in memoria. Ora, Source è un trucco , destinato a portare a termine il lavoro, non una biblioteca attentamente pensata. Sarà migliorato in Scala 2.8, ma c'è sicuramente l'opportunità per la comunità Scala di diventare attiva nel definire una buona API I / O.
Daniel C. Sobral,

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

6
L'aggiunta di "getLines" alla risposta originale rimuoverà tutte le nuove righe. Dovrebbe essere "Source.fromFile (" file.txt "," utf-8 "). MkString".
Joe23,

9
Vedi anche il mio commento nella risposta di Daniel C. Sobral: questo uso non chiuderà l'istanza Source, quindi Scala potrebbe conservare un blocco sul file.
DJ

26

(EDIT: questo non funziona in scala 2.9 e forse neanche 2.8)

Usa tronco:

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

14
" slurp"? Abbiamo davvero abbandonato il nome ovvio e intuitivo? Il problema slurpè che potrebbe avere senso dopo il fatto, almeno per qualcuno con l'inglese come prima lingua, ma all'inizio non ci penseresti mai!
Daniel C. Sobral,

5
Mi sono appena imbattuto in questa domanda / risposta. Filenon è più in 2.8.0, no?
huynhjl,

4
bere rumorosamente suona alla grande. :) Non me lo sarei aspettato, ma non mi aspettavo che l'output sullo schermo fosse chiamato 'stampa'. slurpè fantastico! :) È stato fantastico? Non lo trovo ; (
utente sconosciuto

5
in scala-2.10.0 il nome del pacchetto è scala.reflect.io.File E una domanda su questo "File". ad esempio, perché questo file è contrassegnato come "sperimentale"? È sicuro? Libera un blocco al file system?
VasiliNovikov,

4
Slurp ha una lunga storia con questo scopo che origina, credo, da Perl
Chris Mountford,

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

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

Controllo sulla codifica dei caratteri e nessuna risorsa da ripulire. Inoltre, possibilmente ottimizzato (ad es. Files.readAllBytesAllocando un array di byte adeguato alla dimensione del file).


7

Mi è stato detto che Source.fromFile è problematico. Personalmente, ho avuto problemi ad aprire file di grandi dimensioni con Source.fromFile e ho dovuto ricorrere a Java InputStreams.

Un'altra soluzione interessante sta usando lo scalax. Ecco un esempio di codice ben commentato che apre un file di registro utilizzando ManagedResource per aprire un file con helper scalax: http://pastie.org/pastes/420714


6

L'uso di getLines () su scala.io.Source elimina i caratteri utilizzati per i terminatori di riga (\ n, \ r, \ r \ n, ecc.)

Quanto segue dovrebbe preservare carattere per carattere e non eseguire eccessiva concatenazione di stringhe (problemi di prestazioni):

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

Ancora uno: https://github.com/pathikrit/better-files#streams-and-codecs

Vari modi per slurpare un file senza caricare il contenuto in memoria:

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 

Puoi fornire anche il tuo codec per tutto ciò che fa una lettura / scrittura (presuppone scala.io.Codec.default se non ne fornisci uno):

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

Proprio come in Java, usando la libreria CommonsIO:

FileUtils.readFileToString(file, StandardCharsets.UTF_8)

Inoltre, molte risposte qui dimenticano Charset. È meglio fornirlo sempre esplicitamente, altrimenti colpirà un giorno.


4

Per emulare la sintassi di Ruby (e trasmettere la semantica) di apertura e lettura di un file, considera questa classe implicita (Scala 2.10 e superiore),

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

In questo modo,

open("file.txt").read

3

come alcune persone hanno menzionato scala.io.Source è meglio evitare a causa di perdite di connessione.

Probabilmente scalax e libere java libere come commons-io sono le migliori opzioni fino a quando il nuovo progetto dell'incubatore (cioè scala-io) non viene unito.


3

puoi anche usare Path da scala io per leggere ed elaborare i file.

import scalax.file.Path

Ora puoi ottenere il percorso del file usando questo: -

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

Puoi anche includere terminatori ma per impostazione predefinita è impostato su false ..


3

Per una lettura / caricamento generale più veloce di un file (di grandi dimensioni), considera di aumentare le dimensioni di bufferSize( Source.DefaultBufSizeimpostato su 2048), ad esempio come segue,

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

Nota Source.scala . Per ulteriori discussioni vedere il file di testo rapido Scala letto e caricato in memoria .


3

Non è necessario analizzare ogni singola riga e quindi concatenarli di nuovo ...

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

Preferisco usare questo:

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
}

Dovresti chiudere il flusso - se si verifica un erroreval content = source.mkString
Andrzej Jozwik

+1 per Codec. Ho fallito il test sbt testperché non riesco a impostarlo, mentre il comando test di Intellij supera tutti i test. E puoi usarlo def usingda questo
Mikhail Ionkin,

3

Se non ti dispiace una dipendenza di terze parti, dovresti considerare di usare la mia libreria OS-Lib . Ciò rende molto conveniente la lettura / scrittura di file e l'utilizzo del filesystem:

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

con help di una riga per leggere byte , leggere blocchi , leggere righe e molte altre operazioni utili / comuni


2

L'ovvia domanda è "perché vuoi leggere l'intero file?" Questa ovviamente non è una soluzione scalabile se i tuoi file diventano molto grandi. La scala.io.Sourcedà di eseguire una Iterator[String]dal getLinesmetodo, che è molto utile e concisa.

Non è molto difficile trovare una conversione implicita usando le utility java IO sottostanti per convertire a File, a Readero InputStreama in String. Penso che la mancanza di scalabilità significhi che sono corretti non aggiungere questo all'API standard.


12
Sul serio? Quanti file leggi davvero su base regolare con problemi reali di adattamento in memoria? La stragrande maggioranza dei file nella stragrande maggioranza dei programmi che abbia mai affrontato sono abbastanza piccoli da adattarsi alla memoria. Francamente, i file di big data sono l'eccezione, e dovresti rendertene conto e programmare di conseguenza se li stai leggendo / scrivendo.
Christopher,

8
oxbow_lakes, non sono d'accordo. Esistono molte situazioni che coinvolgono piccoli file le cui dimensioni non aumenteranno in futuro.
Brendan OConnor,

4
Concordo sul fatto che sono l'eccezione, ma penso che sia per questo che un intero file letto in memoria non si trova né nel JDK né nell'SDK Scala. È un metodo di utilità a 3 righe per scrivere te stesso:
superalo

1

stampa ogni riga, ad esempio usa Java BufferedReader leggi la riga di recupero, e stampala:

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

equivalente:

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

negli argomenti puoi dare il percorso del file e restituirà tutte le righe


3
Cosa offre ciò che l'altra risposta non fa?
jwvh,

Non ho visto altre risposte ... ho pensato di poter contribuire qui così pubblicato ... speriamo che non danneggi nessuno :)
Apurw,

1
Dovresti davvero leggerli. La maggior parte sono piuttosto istruttivi. Anche quelli di 8 anni hanno informazioni pertinenti.
jwvh
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.