Baca seluruh file di Scala?


312

Apa cara sederhana dan kanonik untuk membaca seluruh file ke dalam memori di Scala? (Idealnya, dengan kontrol atas pengkodean karakter.)

Yang terbaik yang bisa saya dapatkan adalah:

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

atau apakah saya seharusnya menggunakan salah satu idiom mengerikan Jawa , yang terbaik (tanpa menggunakan perpustakaan eksternal) tampaknya adalah:

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

Dari membaca diskusi milis, tidak jelas bagi saya bahwa scala.io.Sumber bahkan seharusnya menjadi perpustakaan I / O kanonik. Saya tidak mengerti apa tujuan yang dimaksudkan, tepatnya.

... Saya ingin sesuatu yang mati sederhana dan mudah diingat. Misalnya, dalam bahasa ini sangat sulit untuk melupakan idiom ...

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

12
Java tidak seburuk itu jika Anda tahu alat yang tepat. import org.apache.commons.io.FileUtils; FileUtils.readFileToString (File baru ("file.txt", "UTF-8")
smartnut007

25
Komentar ini melewatkan titik desain bahasa. Oleh karena itu, bahasa apa pun yang memiliki fungsi pustaka sederhana untuk operasi yang ingin Anda lakukan sama baiknya dengan sintaks pemanggilan fungsinya. Diberikan pustaka tak terbatas dan 100% dihafal, semua program akan diimplementasikan dengan panggilan fungsi tunggal. Bahasa pemrograman baik ketika membutuhkan komponen pre-fab yang lebih sedikit untuk sudah ada untuk mencapai hasil tertentu.
Chris Mountford

Jawaban:


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

Ngomong-ngomong, " scala." tidak benar-benar diperlukan, karena selalu ada dalam ruang lingkup, dan Anda tentu saja dapat mengimpor konten io, sepenuhnya atau sebagian, dan menghindari keharusan untuk menambahkan "io." terlalu.

Namun, file di atas tetap terbuka. Untuk menghindari masalah, Anda harus menutupnya seperti ini:

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

Masalah lain dengan kode di atas adalah sangat lambat karena sifat implementasinya. Untuk file yang lebih besar harus digunakan:

source.getLines mkString "\n"

48
Saya terlambat ke pesta, tapi saya benci orang tidak tahu mereka bisa melakukan "io.File (" / etc / passwd "). Slurp" di trunk.
psp

28
@ Ekstempor Jika Anda benar-benar berpikir saya tidak berterima kasih, saya benar-benar minta maaf. Saya sangat menghargai dukungan Anda terhadap bahasa Scala dan setiap kali Anda secara pribadi melihat ke dalam masalah yang saya kemukakan, menyarankan solusi untuk masalah yang saya miliki, atau menjelaskan sesuatu kepada saya. Saya akan mengambil kesempatan itu, untuk berterima kasih karena telah mengubah scala.io menjadi sesuatu yang layak dan layak. Aku akan lebih vokal dalam terima kasih mulai sekarang, tapi aku masih benci namanya, maaf.
Daniel C. Sobral

49
"slurp" telah menjadi nama untuk membaca seluruh file sekaligus di Perl selama bertahun-tahun. Perl memiliki tradisi penamaan yang lebih mendalam dan tidak resmi daripada keluarga bahasa C, yang mungkin dianggap tidak menyenangkan oleh beberapa orang, tetapi dalam kasus ini saya pikir itu cocok: ini adalah kata yang jelek untuk praktik yang jelek. Ketika Anda menyeruput (), Anda tahu Anda melakukan sesuatu yang nakal karena Anda baru saja mengetiknya.
Marcus Downing

15
File.read () akan menjadi nama yang lebih bagus, dan konsisten dengan Ruby dan Python di samping.
Brendan OConnor

26
@extempore: Anda tidak bisa mencegah orang merasa jijik. Seperti itulah adanya. Seharusnya tidak mengganggu Anda bahwa beberapa orang tidak menyukai setiap pilihan yang Anda buat. Itu hanya hidup, Anda tidak bisa menyenangkan semua orang :)
Alex Baranosky

58

Hanya untuk memperluas solusi Daniel, Anda dapat mempersingkat banyak hal dengan memasukkan impor berikut ke dalam file apa pun yang memerlukan manipulasi file:

import scala.io.Source._

Dengan ini, Anda sekarang dapat melakukan:

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

Saya akan berhati-hati membaca seluruh file menjadi satu String. Ini adalah kebiasaan yang sangat buruk, yang akan menggigit Anda lebih cepat dan lebih keras daripada yang Anda pikirkan. The getLinesmethod mengembalikan nilai tipe Iterator[String]. Secara efektif kursor malas ke dalam file, memungkinkan Anda untuk memeriksa hanya data yang Anda butuhkan tanpa risiko kekenyangan memori.

Oh, dan untuk menjawab pertanyaan tersirat Anda tentang Source: ya, itu adalah perpustakaan I / O kanonik. Sebagian besar kode akhirnya digunakan java.iokarena antarmuka tingkat rendah dan kompatibilitas yang lebih baik dengan kerangka kerja yang ada, tetapi kode apa pun yang memiliki pilihan harus digunakan Source, terutama untuk manipulasi file sederhana.


BAIK. Ada sebuah cerita untuk kesan negatif saya tentang Sumber: Saya pernah berada dalam situasi yang berbeda dari sekarang, di mana saya memiliki file yang sangat besar yang tidak muat ke dalam memori. Menggunakan Sumber menyebabkan program macet; ternyata ia mencoba membaca semuanya sekaligus.
Brendan OConnor

7
Sumber tidak seharusnya membaca seluruh file ke dalam memori. Jika Anda menggunakan toList setelah getLines, atau metode lain yang akan menghasilkan koleksi, maka Anda memasukkan semuanya ke dalam memori. Sekarang, Source adalah hack , dimaksudkan untuk menyelesaikan pekerjaan, bukan perpustakaan yang dipikirkan dengan cermat. Ini akan ditingkatkan di Scala 2.8, tetapi pasti ada peluang bagi komunitas Scala untuk menjadi aktif dalam mendefinisikan API I / O yang baik.
Daniel C. Sobral

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

6
Menambahkan "getLines" ke jawaban asli akan menghapus semua baris baru. Seharusnya "Source.fromFile (" file.txt "," utf-8 "). MkString".
Joe23

9
Lihat juga komentar saya dalam jawaban Daniel C. Sobral - penggunaan ini tidak akan menutup instance Source, jadi Scala dapat menyimpan kunci pada file.
djb

26

(EDIT: Ini tidak berfungsi di scala 2.9 dan mungkin juga tidak 2.8)

Gunakan bagasi:

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

14
" slurp"? Apakah kita benar-benar membuang nama yang jelas dan intuitif? Masalahnya slurpadalah bahwa hal itu mungkin masuk akal setelah-fakta-, untuk seseorang dengan bahasa Inggris sebagai bahasa pertama, setidaknya, tetapi Anda tidak akan pernah berpikir untuk memulainya!
Daniel C. Sobral

5
Hanya tersandung pada pertanyaan / jawaban ini. Filetidak lagi di 2.8.0, bukan?
huynhjl

4
menghirup terdengar hebat. :) Saya tidak akan mengharapkannya, tapi saya juga tidak mengharapkan output ke layar untuk dinamai 'print'. slurpfantastis! :) Fantastis? Saya tidak menemukannya. ; (
pengguna tidak diketahui

5
di scala-2.10.0 nama paketnya adalah scala.reflect.io.File Dan pertanyaan tentang "File" ini. tanpa persiapan, mengapa file ini ditandai sebagai "eksperimental"? Apakah ini aman? Apakah itu membebaskan kunci ke sistem file?
VasiliNovikov

4
slurp memiliki sejarah panjang untuk tujuan ini, saya pikir, dari 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)

Kontrol atas pengkodean karakter, dan tidak ada sumber daya untuk dibersihkan. Juga, mungkin dioptimalkan (misalnya Files.readAllBytesmengalokasikan array byte yang sesuai dengan ukuran file).


7

Saya telah diberitahu bahwa Source.fromFile bermasalah. Secara pribadi, saya punya masalah membuka file besar dengan Source.fromFile dan harus menggunakan Java InputStreams.

Solusi lain yang menarik adalah menggunakan scalax. Berikut adalah contoh beberapa kode yang berkomentar dengan baik yang membuka file log menggunakan ManagedResource untuk membuka file dengan scalax helpers: http://pastie.org/pastes/420714


6

Menggunakan getLines () di scala.io.Source membuang karakter apa yang digunakan untuk terminator garis (\ n, \ r, \ r \ n, dll.)

Berikut ini harus mempertahankannya karakter-untuk-karakter, dan tidak melakukan penggabungan string yang berlebihan (masalah kinerja):

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

Satu lagi: https://github.com/pathikrit/better-files#streams-and-codecs

Berbagai cara untuk menyeruput file tanpa memuat konten ke dalam memori:

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 

Anda juga dapat menyediakan codec Anda sendiri untuk apa pun yang melakukan baca / tulis (ini mengasumsikan scala.io.Codec.default jika Anda tidak menyediakannya):

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

Sama seperti di Jawa, menggunakan perpustakaan CommonsIO:

FileUtils.readFileToString(file, StandardCharsets.UTF_8)

Juga, banyak jawaban di sini melupakan Charset. Lebih baik untuk selalu memberikannya secara eksplisit, atau itu akan mencapai satu hari.


4

Untuk meniru sintaksis Ruby (dan menyampaikan semantik) dari membuka dan membaca file, pertimbangkan kelas implisit ini (Scala 2.10 dan lebih tinggi),

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

Lewat sini,

open("file.txt").read

3

karena beberapa orang menyebutkan scala.io.Source sebaiknya dihindari karena kebocoran koneksi.

Mungkin scalax dan lib java murni seperti commons-io adalah opsi terbaik sampai proyek inkubator baru (yaitu scala-io) digabung.


3

Anda juga dapat menggunakan Path dari scala io untuk membaca dan memproses file.

import scalax.file.Path

Sekarang Anda bisa mendapatkan jalur file menggunakan ini: -

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

Anda juga dapat menyertakan terminator tetapi secara default diatur ke false ..


3

Untuk keseluruhan lebih cepat membaca / mengunggah file (besar), pertimbangkan untuk meningkatkan ukuran bufferSize( Source.DefaultBufSizediatur ke 2048), misalnya sebagai berikut,

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

Catatan Source.scala . Untuk diskusi lebih lanjut lihat Scala file teks cepat baca dan unggah ke memori .


3

Anda tidak perlu menguraikan setiap baris dan kemudian menggabungkannya lagi ...

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

Saya lebih suka menggunakan ini:

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
}

Anda harus menutup streaming - jika kesalahan terjadi dival content = source.mkString
Andrzej Jozwik

+1 untuk Codec. Tes saya gagal sbt testkarena tidak dapat mengaturnya, sementara perintah uji Intellij lulus semua tes. Dan Anda dapat menggunakannya def usingdari ini
Mikhail Ionkin

3

Jika Anda tidak keberatan dengan ketergantungan pihak ketiga, Anda harus mempertimbangkan untuk menggunakan perpustakaan OS-Lib saya . Ini membuat membaca / menulis file dan bekerja dengan sistem file sangat nyaman:

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

dengan pembantu satu baris untuk membaca byte , bacaan bacaan , baris bacaan , dan banyak operasi lain yang bermanfaat / umum


2

Pertanyaan yang jelas adalah "mengapa Anda ingin membaca di seluruh file?" Ini jelas bukan solusi yang dapat diskalakan jika file Anda menjadi sangat besar. The scala.io.Sourcememberi Anda kembali sebuah Iterator[String]darigetLines metode, yang sangat berguna dan ringkas.

Tidak banyak pekerjaan untuk menghasilkan konversi implisit menggunakan utilitas IO java yang mendasari untuk mengkonversi a File, a Readeratau a InputStreamke String. Saya pikir kurangnya skalabilitas berarti mereka benar untuk tidak menambahkan ini ke API standar.


12
Serius? Berapa banyak file yang Anda benar-benar baca secara teratur yang memiliki masalah nyata dalam memori? Sebagian besar file di sebagian besar program yang pernah saya tangani cukup kecil untuk dimasukkan ke dalam memori. Terus terang, file data besar adalah pengecualian, dan Anda harus menyadari itu dan memprogram sesuai jika Anda akan membaca / menulisnya.
Christopher

8
oxbow_lakes, saya tidak setuju. Ada banyak situasi yang melibatkan file kecil yang ukurannya tidak akan bertambah di masa mendatang.
Brendan OConnor

4
Saya setuju bahwa mereka adalah pengecualian - tetapi saya pikir itu sebabnya membaca-seluruh-file-ke-memori tidak dalam JDK atau Scala SDK. Ini adalah metode utilitas 3 baris bagi Anda untuk menulis sendiri: dapatkan lebih dari itu
oxbow_lakes

1

cetak setiap baris, seperti gunakan Java BufferedReader baca setiap baris, dan cetak:

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

setara:

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

dalam argumen Anda dapat memberikan path file dan itu akan mengembalikan semua baris


3
Apa yang ditawarkan ini yang tidak dijawab oleh jawaban yang lain?
jwvh

Belum melihat jawaban lain ... hanya berpikir saya bisa berkontribusi di sini jadi diposting ... mudah-mudahan itu tidak akan membahayakan siapa pun :)
April

1
Anda benar-benar harus membacanya. Sebagian besar cukup informatif. Bahkan yang berusia 8 tahun memiliki informasi yang relevan.
jwvh
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.