Lire l'intégralité du fichier dans Scala?


312

Quelle est une manière simple et canonique de lire un fichier entier en mémoire dans Scala? (Idéalement, avec contrôle de l'encodage des caractères.)

Le mieux que je puisse trouver est:

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

ou suis-je censé utiliser l'un des idiomes les plus horribles de Java , dont le meilleur (sans utiliser de bibliothèque externe) semble être:

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

De la lecture des discussions de la liste de diffusion, il n'est pas clair pour moi que scala.io.Source est même censé être la bibliothèque d'E / S canonique. Je ne comprends pas quel est son objectif, exactement.

... Je voudrais quelque chose de simple et facile à retenir. Par exemple, dans ces langues, il est très difficile d'oublier l'idiome ...

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

12
Java n'est pas si mal si vous connaissez les bons outils. import org.apache.commons.io.FileUtils; FileUtils.readFileToString (nouveau fichier ("file.txt", "UTF-8")
smartnut007

25
Ce commentaire passe à côté de la conception du langage. Tout langage qui dispose d'une fonction de bibliothèque simple pour exactement l'opération que vous souhaitez effectuer est donc aussi bon que sa syntaxe d'invocation de fonction. Étant donné une bibliothèque infinie et 100% mémorisée, tous les programmes seraient mis en œuvre avec un seul appel de fonction. Un langage de programmation est bon lorsqu'il a besoin de moins de composants préfabriqués pour exister afin d'obtenir un résultat spécifique.
Chris Mountford

Réponses:


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

Soit dit en passant, " scala." n'est pas vraiment nécessaire, car il est toujours de toute façon, et vous pouvez, bien sûr, importer le contenu d'io, entièrement ou partiellement, et éviter d'avoir à ajouter "io". aussi.

Ce qui précède laisse cependant le fichier ouvert. Pour éviter les problèmes, vous devez le fermer comme ceci:

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

Un autre problème avec le code ci-dessus est qu'il est horriblement lent en raison de sa nature de mise en œuvre. Pour les fichiers plus volumineux, il faut utiliser:

source.getLines mkString "\n"

48
Je suis trop en retard pour la fête, mais je détesterais que les gens ne sachent pas qu'ils peuvent faire "io.File (" / etc / passwd "). Slurp" dans le coffre.
psp

28
@extempore Si vous pensez vraiment que je suis ingrat, je suis vraiment désolé. J'apprécie profondément votre soutien à la langue Scala et chaque fois que vous avez personnellement examiné un problème que j'ai soulevé, suggéré une solution à un problème que j'avais ou expliqué quelque chose à moi. Je profite donc de l'occasion pour vous remercier d'avoir transformé scala.io en quelque chose de décent et de digne. Je serai plus vocal dans mes remerciements à partir de maintenant, mais je déteste toujours le nom, désolé.
Daniel C.Sobral

49
"slurp" est le nom utilisé pour lire un fichier entier à la fois en Perl depuis de nombreuses années. Perl a une tradition de dénomination plus viscérale et informelle que la famille de langues C, que certains peuvent trouver désagréable, mais dans ce cas, je pense que cela convient: c'est un mot laid pour une pratique laide. Lorsque vous slurp (), vous savez que vous faites quelque chose de méchant parce que vous deviez simplement taper cela.
Marcus Downing

15
File.read () serait un nom plus agréable et compatible avec Ruby et Python.
Brendan OConnor

26
@extempore: vous ne pouvez pas empêcher les gens d'être dégoûtés. C'est comme ça. Cela ne devrait pas vous déranger que certaines personnes n'aiment pas tous les choix que vous avez faits. C'est juste la vie, vous ne pouvez pas plaire à tout le monde :)
Alex Baranosky

58

Juste pour développer la solution de Daniel, vous pouvez considérablement raccourcir les choses en insérant l'importation suivante dans n'importe quel fichier qui nécessite une manipulation de fichier:

import scala.io.Source._

Avec cela, vous pouvez maintenant faire:

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

Je me méfierais de lire un fichier entier en un seul String. C'est une très mauvaise habitude, qui vous mordra plus tôt et plus fort que vous ne le pensez. La getLinesméthode renvoie une valeur de type Iterator[String]. Il s'agit en fait d'un curseur paresseux dans le fichier, vous permettant d'examiner uniquement les données dont vous avez besoin sans risquer de saturer la mémoire.

Oh, et pour répondre à votre question implicite sur Source: oui, c'est la bibliothèque d'E / S canonique. La plupart du code finit par être utilisé en java.ioraison de son interface de bas niveau et de sa meilleure compatibilité avec les frameworks existants, mais tout code qui a le choix devrait être utilisé Source, en particulier pour une simple manipulation de fichiers.


D'ACCORD. Il y a une histoire pour mon impression négative de Source: J'étais autrefois dans une situation différente de celle d'aujourd'hui, où j'avais un très gros fichier qui ne tenait pas en mémoire. L'utilisation de Source a provoqué le plantage du programme; il s'est avéré qu'il essayait de lire le tout à la fois.
Brendan OConnor

7
La source n'est pas censée lire l'intégralité du fichier en mémoire. Si vous utilisez toList après getLines, ou une autre méthode qui produira une collection, alors vous aurez tout en mémoire. Maintenant, Source est un hack , destiné à faire le travail, pas une bibliothèque mûrement réfléchie. Il sera amélioré dans Scala 2.8, mais la communauté Scala a certainement la possibilité de devenir active dans la définition d'une bonne API d'E / S.
Daniel C.Sobral

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

6
L'ajout de "getLines" à la réponse d'origine supprimera toutes les nouvelles lignes. Doit être "Source.fromFile (" file.txt "," utf-8 "). MkString".
Joe23

9
Voir aussi mon commentaire dans la réponse de Daniel C. Sobral - cette utilisation ne fermera pas l'instance Source, donc Scala peut conserver un verrou sur le fichier.
djb

26

(EDIT: Cela ne fonctionne pas dans scala 2.9 et peut-être pas 2.8 non plus)

Utilisez le coffre:

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

14
" slurp"? Avons-nous vraiment abandonné le nom évident et intuitif? Le problème slurpest que cela pourrait avoir du sens après coup, pour quelqu'un avec l'anglais comme première langue, au moins, mais vous n'y penseriez jamais pour commencer!
Daniel C.Sobral

5
Je suis juste tombé sur cette question / réponse. Filen'est plus en 2.8.0, n'est-ce pas?
huynhjl

4
slurp sonne bien. :) Je ne m'y attendrais pas, mais je ne m'attendais pas non plus à ce que la sortie à l'écran soit nommée 'print'. slurpest fantastique! :) C'était fantastique? Je ne le trouve pas. ; (
utilisateur inconnu

5
dans scala-2.10.0 le nom du paquet est scala.reflect.io.File Et une question sur ce "fichier". extempore, pourquoi ce fichier est-il marqué comme "expérimental"? Est-ce sûr? Libère-t-il un verrou sur le système de fichiers?
VasiliNovikov

4
slurp a une longue histoire à cet effet provenant, je pense, de 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)

Contrôle de l'encodage des caractères et aucune ressource à nettoyer. Également, éventuellement optimisé (par exemple en Files.readAllBytesallouant un tableau d'octets approprié à la taille du fichier).


7

On m'a dit que Source.fromFile est problématique. Personnellement, j'ai eu des problèmes pour ouvrir de gros fichiers avec Source.fromFile et j'ai dû recourir à Java InputStreams.

Une autre solution intéressante consiste à utiliser scalax. Voici un exemple de code bien commenté qui ouvre un fichier journal à l'aide de ManagedResource pour ouvrir un fichier avec des assistants scalax: http://pastie.org/pastes/420714


6

L'utilisation de getLines () sur scala.io.Source supprime les caractères utilisés pour les terminateurs de ligne (\ n, \ r, \ r \ n, etc.)

Les éléments suivants doivent le conserver caractère par caractère et ne font pas de concaténation excessive de chaînes (problèmes de performances):

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

Un de plus: https://github.com/pathikrit/better-files#streams-and-codecs

Différentes façons de récupérer un fichier sans charger le contenu en mémoire:

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 

Vous pouvez également fournir votre propre codec pour tout ce qui fait une lecture / écriture (cela suppose scala.io.Codec.default si vous n'en fournissez pas):

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

Tout comme en Java, en utilisant la bibliothèque CommonsIO:

FileUtils.readFileToString(file, StandardCharsets.UTF_8)

De plus, de nombreuses réponses ici oublient Charset. Il est préférable de toujours le fournir explicitement, sinon il arrivera un jour.


4

Pour émuler la syntaxe Ruby (et transmettre la sémantique) d'ouverture et de lecture d'un fichier, considérez cette classe implicite (Scala 2.10 et supérieure),

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

De cette façon,

open("file.txt").read

3

comme quelques personnes l'ont mentionné scala.io.Source est préférable d'éviter en raison de fuites de connexion.

Les bibliothèques scalax et java pur comme commons-io sont probablement les meilleures options jusqu'à ce que le nouveau projet d'incubateur (c'est-à-dire scala-io) soit fusionné.


3

vous pouvez également utiliser Path from scala io pour lire et traiter les fichiers.

import scalax.file.Path

Maintenant, vous pouvez obtenir le chemin du fichier en utilisant ceci: -

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

Vous pouvez également inclure des terminateurs mais par défaut, il est défini sur false.


3

Pour une lecture / téléchargement global plus rapide d'un (gros) fichier, envisagez d'augmenter la taille de bufferSize( Source.DefaultBufSizedéfini sur 2048), par exemple comme suit,

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

Remarque Source.scala . Pour plus de détails, voir le fichier texte rapide Scala lu et téléchargé en mémoire .


3

Vous n'avez pas besoin d'analyser chaque ligne, puis de les concaténer à nouveau ...

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

Je préfère utiliser ceci:

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
}

Vous devez fermer le flux - si une erreur se produit dansval content = source.mkString
Andrzej Jozwik

+1 pour Codec. J'ai obtenu l'échec du test sbt testcar je ne peux pas le définir, tandis que la commande test d'Intellij réussit tous les tests. Et vous pouvez utiliser à def usingpartir de cela
Mikhail Ionkin

3

Si cela ne vous dérange pas une dépendance tierce, vous devriez envisager d'utiliser ma bibliothèque OS-Lib . Cela rend la lecture / écriture de fichiers et l'utilisation du système de fichiers très pratiques:

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

avec des aides d'une ligne pour la lecture d'octets , la lecture de morceaux , la lecture de lignes et de nombreuses autres opérations utiles / courantes


2

La question évidente étant "pourquoi voulez-vous lire tout le fichier?" Ce n'est évidemment pas une solution évolutive si vos fichiers deviennent très volumineux. Le scala.io.Sourcevous donne un retour Iterator[String]de la getLinesméthode, qui est très utile et concis.

Ce n'est pas vraiment un boulot de trouver une conversion implicite en utilisant les utilitaires Java IO sous-jacents pour convertir un File, un Readerou un InputStreamen un String. Je pense que le manque d'évolutivité signifie qu'ils sont corrects de ne pas ajouter cela à l'API standard.


12
Sérieusement? Combien de fichiers lisez-vous régulièrement sur une base régulière qui ont de réels problèmes de mémoire? La grande majorité des fichiers dans la grande majorité des programmes que j'ai jamais traités sont facilement assez petits pour tenir en mémoire. Franchement, les fichiers Big Data sont l'exception, et vous devez le réaliser et les programmer en conséquence si vous allez les lire / écrire.
Christopher

8
oxbow_lakes, je ne suis pas d'accord. Il existe de nombreuses situations impliquant de petits fichiers dont la taille n'augmentera pas à l'avenir.
Brendan OConnor

4
Je suis d'accord pour dire qu'ils sont l'exception - mais je pense que c'est la raison pour laquelle la lecture d'un fichier entier en mémoire ne se trouve ni dans le JDK ni dans le SDK Scala. C'est une méthode utilitaire à 3 lignes pour que vous vous
écriviez

1

imprimez chaque ligne, comme utilisez Java BufferedReader pour lire chaque ligne, et imprimez-la:

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

équivalent:

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

dans les arguments, vous pouvez donner le chemin du fichier et il retournera toutes les lignes


3
Qu'est-ce que cela offre que l'autre réponse ne fait pas?
jwvh

Je n'ai pas vu d'autres réponses ... je pensais juste pouvoir contribuer ici, donc publié ... j'espère que cela ne nuira à personne :)
Apurw

1
Vous devriez vraiment les lire. La plupart sont assez informatifs. Même ceux qui ont 8 ans ont des informations pertinentes.
jwvh
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.