Leia o arquivo inteiro no Scala?

312

O que é uma maneira simples e canônica de ler um arquivo inteiro na memória no Scala? (Idealmente, com controle sobre a codificação de caracteres.)

O melhor que posso apresentar é:

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

ou devo usar um dos idiomas terríveis de Java , o melhor dos quais (sem usar uma biblioteca externa) parece ser:

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

Ao ler as discussões da lista de discussão, não está claro para mim que o scala.io.Source deve ser a biblioteca de E / S canônica. Não entendo exatamente qual é o seu objetivo.

... Gostaria de algo absolutamente simples e fácil de lembrar. Por exemplo, nessas línguas é muito difícil esquecer o idioma ...

Ruby    open("file.txt").read
Ruby    File.read("file.txt")
Python  open("file.txt").read()
Brendan OConnor
fonte
12
Java não é tão ruim se você conhece as ferramentas certas. import org.apache.commons.io.FileUtils; FileUtils.readFileToString (novo arquivo ("file.txt", "UTF-8"))
smartnut007 18/06/11
25
Este comentário perde o objetivo do design da linguagem. Qualquer idioma que tenha disponível uma função de biblioteca simples para exatamente a operação que você deseja executar é, portanto, tão bom quanto sua sintaxe de chamada de função. Dada uma biblioteca infinita e 100% memorizada, todos os programas seriam implementados com uma única chamada de função. Uma linguagem de programação é boa quando é necessário que já existam menos componentes pré-fabricados para alcançar um resultado específico.
Chris Mountford

Respostas:

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

A propósito, " scala." não é realmente necessário, pois está sempre no escopo, e você pode, é claro, importar o conteúdo do io, total ou parcialmente, e evitar ter que acrescentar o "io". também.

O item acima deixa o arquivo aberto, no entanto. Para evitar problemas, feche-o assim:

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

Outro problema com o código acima é que ele é horrivelmente lento devido à sua natureza de implementação. Para arquivos maiores, deve-se usar:

source.getLines mkString "\n"
Daniel C. Sobral
fonte
48
Estou muito atrasado para a festa, mas odiaria que as pessoas não soubessem que podem fazer "io.File (" / etc / passwd "). Slurp" no porta-malas.
Psp
28
@extempore Se você realmente acha que sou ingrato, sinto muito. Agradeço profundamente seu apoio ao idioma Scala e a cada vez que você olha pessoalmente para um problema que levantei, sugeri uma solução para um problema que tive ou expliquei algo para mim. Aproveitarei a oportunidade para agradecer por transformar scala.io em algo decente e digno. Eu vou ser mais sincero em meus agradecimentos a partir de agora, mas eu ainda odeio o nome, desculpe.
267 Daniel C. Sobral
49
"slurp" é o nome para a leitura de um arquivo inteiro de uma vez no Perl por muitos anos. Perl tem uma tradição de nomeação mais visceral e informal do que a família de idiomas C, que alguns podem achar desagradável, mas neste caso eu acho que se encaixa: é uma palavra feia para uma prática feia. Quando você slurp (), você sabe que está fazendo algo impertinente porque você só precisa digitar isso.
Marcus Downing
15
File.read () seria um nome melhor e consistente com Ruby e Python.
Brendan OConnor 07/09/09
26
@extempore: você não pode impedir as pessoas de sentir nojo. É do jeito que é. Não deve incomodá-lo que algumas pessoas não gostem de todas as escolhas que você fez. Isso é apenas a vida, você não pode agradar a todos :)
Alex Baranosky
58

Apenas para expandir a solução de Daniel, você pode reduzir enormemente as coisas inserindo a seguinte importação em qualquer arquivo que exija manipulação de arquivos:

import scala.io.Source._

Com isso, agora você pode fazer:

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

Eu seria cauteloso ao ler um arquivo inteiro em um único String. É um hábito muito ruim, que o morderá mais cedo e mais difícil do que você pensa. O getLinesmétodo retorna um valor do tipo Iterator[String]. É efetivamente um cursor lento no arquivo, permitindo que você examine apenas os dados necessários sem arriscar o excesso de memória.

Ah, e para responder à sua pergunta implícita sobre Source: sim, é a biblioteca de E / S canônica. A maioria dos códigos acaba sendo usada java.iodevido à sua interface de nível inferior e melhor compatibilidade com as estruturas existentes, mas qualquer código que tenha uma opção deve ser usado Source, principalmente para manipulação simples de arquivos.

Daniel Spiewak
fonte
ESTÁ BEM. Há uma história para minha impressão negativa da Fonte: eu já estava em uma situação diferente da atual, onde tinha um arquivo muito grande que não cabia na memória. Usar o Source causou uma falha no programa; descobriu-se que estava tentando ler a coisa toda de uma só vez.
Brendan OConnor 18/08/09
7
A fonte não deve ler o arquivo inteiro na memória. Se você usar toList após getLines ou algum outro método que produzirá uma coleção, você terá tudo na memória. Agora, o Source é um hack , destinado a fazer o trabalho, não uma biblioteca cuidadosamente pensada. Ele será aprimorado no Scala 2.8, mas definitivamente há uma oportunidade para a comunidade Scala se tornar ativa na definição de uma boa API de E / S.
1827 Daniel C. Sobral
36
// for file with utf-8 encoding
val lines = scala.io.Source.fromFile("file.txt", "utf-8").getLines.mkString
Walter Chang
fonte
6
Adicionar "getLines" à resposta original removerá todas as novas linhas. Deve ser "Source.fromFile (" file.txt "," utf-8 "). MkString".
Joe23
9
Veja também meu comentário na resposta de Daniel C. Sobral - esse uso não fechará a instância Source, portanto, Scala pode reter um bloqueio no arquivo.
djb
26

(EDIT: Isso não funciona no scala 2.9 e talvez também não 2.8)

Use tronco:

scala> io.File("/etc/passwd").slurp
res0: String = 
##
# User Database
# 
... etc
psp
fonte
14
" slurp"? Nós realmente abandonamos o nome óbvio e intuitivo? O problema slurpé que isso pode fazer sentido para alguém com o inglês como primeira língua, pelo menos, mas você nunca pensaria nisso antes!
267 Daniel C. Sobral
5
Apenas tropeçou nesta pergunta / resposta. Filenão está mais no 2.8.0, não está?
precisa saber é o seguinte
4
slurp parece ótimo. :) Eu não esperava isso, mas também não esperava que a saída na tela fosse chamada 'print'. slurpé fantástico! :) Foi fantástico? Eu não acho. ; (
usuário desconhecido
5
no scala-2.10.0, o nome do pacote é scala.reflect.io.File E uma pergunta sobre esse "Arquivo". extempore, por que esse arquivo está marcado como "experimental"? É seguro? Libera um bloqueio para o sistema de arquivos?
VasiliNovikov
4
Slurp tem uma longa história com esta finalidade originária, eu acho, 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)

Controle sobre a codificação de caracteres e nenhum recurso para limpeza. Além disso, possivelmente otimizado (por exemplo, Files.readAllBytesalocando uma matriz de bytes apropriada ao tamanho do arquivo).

Paul Draper
fonte
7

Foi-me dito que o Source.fromFile é problemático. Pessoalmente, tive problemas ao abrir arquivos grandes com o Source.fromFile e tive que recorrer ao Java InputStreams.

Outra solução interessante é usar scalax. Aqui está um exemplo de código bem comentado que abre um arquivo de log usando o ManagedResource para abrir um arquivo com auxiliares scalax: http://pastie.org/pastes/420714

Ikai Lan
fonte
6

O uso de getLines () em scala.io.Source descarta quais caracteres foram usados ​​para terminadores de linha (\ n, \ r, \ r \ n etc.)

O seguinte deve preservá-lo caractere por caractere e não faz concatenação excessiva de cadeias (problemas de desempenho):

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

Mais um: https://github.com/pathikrit/better-files#streams-and-codecs

Várias maneiras de extrair um arquivo sem carregar o conteúdo na memória:

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 

Você também pode fornecer seu próprio codec para qualquer coisa que faça uma leitura / gravação (assume o padrão scala.io.Codec.de se você não fornecer um):

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

Assim como em Java, usando a biblioteca CommonsIO:

FileUtils.readFileToString(file, StandardCharsets.UTF_8)

Além disso, muitas respostas aqui esquecem o Charset. É melhor sempre fornecê-lo explicitamente, ou será atingido um dia.

Dzmitry Lazerka
fonte
4

Para emular a sintaxe Ruby (e transmitir a semântica) de abrir e ler um arquivo, considere esta classe implícita (Scala 2.10 e superior),

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

Nesse caminho,

open("file.txt").read
olmo
fonte
3

como algumas pessoas mencionaram scala.io.Source, é melhor evitar isso devido a vazamentos de conexão.

Provavelmente scalax e libs java puras como commons-io são as melhores opções até que o novo projeto da incubadora (ou seja, scala-io) seja mesclado.

poko
fonte
3

você também pode usar o Path do scala io para ler e processar arquivos.

import scalax.file.Path

Agora você pode obter o caminho do arquivo usando este: -

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

Você também pode incluir terminadores, mas por padrão está definido como falso.

Atiq
fonte
3

Para uma leitura / upload geral mais rápida de um arquivo (grande), considere aumentar o tamanho de bufferSize( Source.DefaultBufSizedefinido como 2048), por exemplo, da seguinte maneira:

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

Nota Source.scala . Para uma discussão mais aprofundada, consulte o arquivo de texto rápido Scala lido e carregado na memória .

olmo
fonte
3

Você não precisa analisar todas as linhas e concatená-las novamente ...

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

Eu prefiro usar isso:

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
fonte
Você deve fechar o fluxo - se ocorrer um erroval content = source.mkString
Andrzej Jozwik
+1 para Codec. O teste falhou sbt testporque não é possível defini-lo, enquanto o comando de teste do Intellij passa em todos os testes. E você pode usar def usinga partir deste
Mikhail Ionkin
3

Se você não se importa com uma dependência de terceiros, considere usar minha biblioteca OS-Lib . Isso torna a leitura / gravação de arquivos e o trabalho com o sistema de arquivos muito conveniente:

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

com auxiliares de uma linha para ler bytes , ler blocos , ler linhas e muitas outras operações úteis / comuns

Li Haoyi
fonte
2

A pergunta óbvia é "por que você deseja ler o arquivo inteiro?" Obviamente, essa não é uma solução escalável se os arquivos forem muito grandes. O scala.io.Sourcedevolve um Iterator[String]dogetLines método, que é muito útil e concisa.

Não é tarefa fácil criar uma conversão implícita usando os utilitários Java IO subjacentes para converter a File, a Readerou a InputStreampara a String. Eu acho que a falta de escalabilidade significa que eles estão corretos para não adicionar isso à API padrão.

oxbow_lakes
fonte
12
Seriamente? Quantos arquivos você realmente lê regularmente que têm problemas reais na memória? A grande maioria dos arquivos na grande maioria dos programas que já lidei é facilmente pequena o suficiente para caber na memória. Francamente, os arquivos de big data são a exceção, e você deve perceber isso e programar adequadamente, se quiser lê-los / gravá-los.
Christopher
8
oxbow_lakes, eu discordo. Existem muitas situações envolvendo arquivos pequenos cujo tamanho não aumentará no futuro.
Brendan OConnor 18/08/09
4
Concordo que eles são a exceção - mas acho que é por isso que um arquivo inteiro de leitura na memória não está no JDK nem no Scala SDK. É um método utilitário de 3 linhas para você se escrever:
supere
1

imprima todas as linhas, como usar o Java BufferedReader, leia todas as linhas e imprima:

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

equivalente:

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

nos argumentos você pode dar o caminho do arquivo e ele retornará todas as linhas

Apurw
fonte
3
O que isso oferece que a outra resposta não oferece?
jwvh
Não vi outras respostas ... apenas pensei que eu posso contribuir aqui, então postou ... espero que não irá prejudicar ninguém :)
Apurw
1
Você realmente deveria lê-los. A maioria é bastante informativa. Mesmo aqueles com 8 anos de idade têm informações relevantes.
jwvh