Scala: tulis string ke file dalam satu pernyataan

144

Untuk membaca file di Scala, ada

Source.fromFile("file.txt").mkString

Apakah ada cara yang setara dan ringkas untuk menulis string ke file?

Sebagian besar bahasa mendukung sesuatu seperti itu. Favorit saya adalah Groovy:

def f = new File("file.txt")
// Read
def s = f.text
// Write
f.text = "file contents"

Saya ingin menggunakan kode untuk program mulai dari satu baris hingga satu halaman kode pendek. Harus menggunakan perpustakaan Anda sendiri tidak masuk akal di sini. Saya berharap bahasa modern memungkinkan saya menulis sesuatu ke file dengan nyaman.

Ada posting yang mirip dengan ini, tetapi mereka tidak menjawab pertanyaan saya yang tepat atau berfokus pada versi Scala yang lebih lama.

Sebagai contoh:

smartnut007
sumber
Lihat pertanyaan ini . Saya setuju dengan jawaban berperingkat tertinggi - lebih baik memiliki perpustakaan pribadi Anda sendiri.
berteman
2
untuk kasus ini saya tidak setuju bahwa seseorang harus menulis perpustakaan pribadi mereka sendiri. Biasanya, ketika Anda sedang menulis potongan kecil program untuk melakukan hal-hal ad hoc (mungkin saya hanya ingin menulisnya sebagai skrip skala satu halaman atau hanya di REPL). Maka mengakses perpustakaan pribadi menjadi menyusahkan.
smartnut007
Pada dasarnya, sepertinya tidak ada dalam scala 2.9 untuk ini pada saat ini. Tidak yakin bagaimana jika saya harus membuka pertanyaan ini.
smartnut007
1
Jika Anda mencari java.io dalam kode sumber Scala, Anda tidak akan menemukan banyak kemunculan, dan bahkan lebih sedikit untuk operasi keluaran, khususnya PrintWriter. Jadi, sampai perpustakaan Scala-IO menjadi bagian resmi dari Scala, kita harus menggunakan Java murni, seperti yang ditunjukkan oleh paradigmatik.
PhiLho
yeah, prob juga membutuhkan scala-io thats kompatibel dengan peningkatan IO jdk7.
smartnut007

Jawaban:

77

Satu baris ringkas:

import java.io.PrintWriter
new PrintWriter("filename") { write("file contents"); close }
LRLucena
sumber
14
Meskipun pendekatan ini terlihat bagus, itu bukan pengecualian-aman atau pengkodean-aman. Jika pengecualian terjadi write(), closetidak akan pernah dipanggil, dan file tidak akan ditutup. PrintWriterjuga menggunakan pengkodean sistem default, yang sangat buruk untuk portabilitas. Dan akhirnya, pendekatan ini menghasilkan kelas terpisah khusus untuk baris ini (namun, mengingat bahwa Scala sudah menghasilkan banyak kelas bahkan untuk kode sederhana, ini bukan kelemahan dalam dirinya sendiri).
Vladimir Matveev
Per komentar di atas, meskipun ini hanya satu liner, tidak aman. Jika Anda ingin lebih aman sambil memiliki lebih banyak opsi di sekitar lokasi dan / atau buffer input, lihat jawaban saya baru saja diposting di utas serupa: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
2
Untuk debug-logging sementara saya menggunakannew PrintWriter(tmpFile) { try {write(debugmsg)} finally {close} }
Randall Whitman
Secara gaya mungkin lebih baik untuk digunakan close(), saya pikir
Casey
Ini adalah dua baris dan dapat dengan mudah dibuat menjadi satu baris seperti ini:new java.io.PrintWriter("/tmp/hello") { write("file contents"); close }
Philluminati
163

Sungguh aneh bahwa tidak ada yang menyarankan operasi NIO.2 (tersedia sejak Java 7):

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

Files.write(Paths.get("file.txt"), "file contents".getBytes(StandardCharsets.UTF_8))

Saya pikir ini sejauh ini adalah cara yang paling sederhana dan termudah dan paling idiomatis, dan tidak memerlukan dependensi dari Java itu sendiri.

Vladimir Matveev
sumber
Ini mengabaikan karakter baris baru karena beberapa alasan.
Kakaji
@ Kakaji, bisa tolong jelaskan? Saya baru saja mengujinya dengan string dengan baris baru, dan itu berfungsi dengan baik. Itu tidak bisa menyaring apa pun - Files.write () menulis array byte sebagai gumpalan, tanpa menganalisis isinya. Bagaimanapun, dalam beberapa data biner, byte 0x0d mungkin memiliki arti penting selain baris baru.
Vladimir Matveev
4
Catatan tambahan: jika Anda memiliki File, cukup '.toPath' untuk mendapatkan parameter pertama.
akauppi
1
Yang satu ini lebih kelas industri (karena pilihan CharSets eksplisit) tetapi tidak memiliki kesederhanaan (dan satu liner ..) dari reflect.io.File.writeAll(contents). Mengapa? Tiga baris saat Anda memasukkan dua pernyataan impor. Mungkin IDE melakukannya untuk Anda secara otomatis tetapi jika dalam REPL itu tidak mudah.
javadba
3
@javadba kita berada di JVM, impor cukup banyak tidak dihitung sebagai 'baris' terutama karena alternatifnya hampir selalu menambahkan ketergantungan perpustakaan baru. Bagaimanapun, Files.writejuga menerima java.lang.Iterablesebagai argumen kedua, dan kita dapat memperolehnya dari Scala Iterable, yaitu hampir semua jenis koleksi, menggunakan konverter:import java.nio.file.{Files, Paths}; import scala.collection.JavaConverters.asJavaIterableConverter; val output = List("1", "2", "3"); Files.write(Paths.get("output.txt"), output.asJava)
Yawar
83

Berikut ini adalah one-liner ringkas yang menggunakan reflect.io.file, ini bekerja dengan Scala 2.12:

reflect.io.File("filename").writeAll("hello world")

Atau, jika Anda ingin menggunakan perpustakaan Java Anda bisa melakukan hack ini:

Some(new PrintWriter("filename")).foreach{p => p.write("hello world"); p.close}
Garrett Hall
sumber
1
+1 Bermanfaat. The Some/ foreachcombo adalah yang funky sedikit, tapi ia datang dengan manfaat tidak mencoba / menangkap / akhirnya.
Brent Faust
3
Nah jika menulis melempar pengecualian Anda mungkin ingin menutup file jika Anda berencana untuk memulihkan dari pengecualian dan membaca / menulis file lagi. Untungnya scala menyediakan satu-garis untuk melakukan ini juga.
Garrett Hall
25
Ini tidak disarankan karena paket scala.tools.nsc.io bukan bagian dari API publik tetapi digunakan oleh kompiler.
Giovanni Botta
3
The Some/ foreachhack adalah alasan mengapa banyak orang membenci Scala karena kode yang tidak terbaca yang bisa dihasilkan oleh peretas.
Erik Kaplun
3
scala.tootls.nsc.io.Fileadalah alias untuk reflect.io.File. Masih internal API, tetapi setidaknya sedikit lebih pendek.
kostja
41

Jika Anda menyukai sintaks Groovy, Anda dapat menggunakan pola desain Pimp-My-Library untuk membawanya ke Scala:

import java.io._
import scala.io._

class RichFile( file: File ) {

  def text = Source.fromFile( file )(Codec.UTF8).mkString

  def text_=( s: String ) {
    val out = new PrintWriter( file , "UTF-8")
    try{ out.print( s ) }
    finally{ out.close }
  }
}

object RichFile {

  implicit def enrichFile( file: File ) = new RichFile( file )

}

Ini akan berfungsi seperti yang diharapkan:

scala> import RichFile.enrichFile
import RichFile.enrichFile

scala> val f = new File("/tmp/example.txt")
f: java.io.File = /tmp/example.txt

scala> f.text = "hello world"

scala> f.text
res1: String = 
"hello world
paradigmatik
sumber
2
Anda tidak pernah memanggil tutup pada contoh Source.fromFile dikembalikan yang berarti sumber daya tidak ditutup sampai GCed (Pengumpulan Sampah). Dan PrintWriter Anda tidak memiliki buffer, jadi ia menggunakan buffer standar JVM kecil sebesar 8 byte, yang berpotensi memperlambat IO Anda secara signifikan. Saya baru saja membuat jawaban pada utas serupa yang berkaitan dengan masalah ini: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
1
Kamu benar. Tetapi solusi yang saya berikan di sini berfungsi dengan baik untuk program yang berumur pendek dengan IO kecil. Saya tidak merekomendasikannya untuk proses server atau data besar (sebagai aturan praktis, lebih dari 500 MB).
paradigmatik
23
import sys.process._
"echo hello world" #> new java.io.File("/tmp/example.txt") !
xiefei
sumber
Itu tidak bekerja untuk saya di REPL. Tidak ada errormessage, tetapi jika saya melihat /tmp/example.txt tidak ada.
pengguna tidak diketahui
@ pengguna tidak diketahui, Maaf karena melewatkan '!' di akhir perintah, perbaiki sekarang.
xiefei
Hebat! Sekarang ini berfungsi, saya ingin tahu mengapa. Apa #>, apa fungsinya !?
pengguna tidak dikenal
10
Solusi ini tidak portabel! hanya bekerja di sistem * nix.
Giovanni Botta
2
Ini tidak akan berfungsi untuk menulis string arbitrer. Ini hanya akan berfungsi untuk string pendek yang bisa Anda berikan sebagai argumen ke echoalat baris perintah .
Kaya
15

Perpustakaan mikro yang saya tulis: https://github.com/pathikrit/better-files

file.write("Hi!")

atau

file << "Hi!"
pathikrit
sumber
3
Cintai perpustakaan Anda! Pertanyaan ini adalah salah satu hit teratas ketika mencari cara untuk menulis file dengan scala - setelah proyek Anda bertambah besar, Anda mungkin ingin sedikit memperluas jawaban?
asac
12

Anda dapat dengan mudah menggunakan Utilitas File Apache . Lihatlah fungsinya writeStringToFile. Kami menggunakan perpustakaan ini dalam proyek kami.

RyuuGan
sumber
3
Saya juga menggunakannya sepanjang waktu. Jika Anda membaca pertanyaan dengan seksama, saya sudah tahu mengapa saya tidak ingin menggunakan perpustakaan.
smartnut007
Tanpa menggunakan pustaka, saya telah membuat solusi yang menangani pengecualian saat membaca / menulis DAN mampu buffer di luar standar buffer kecil yang disediakan oleh pustaka Java: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
7

Seseorang juga memiliki format ini, yang ringkas dan pustaka yang mendasarinya ditulis dengan indah (lihat kode sumber):

import scalax.io.Codec
import scalax.io.JavaConverters._

implicit val codec = Codec.UTF8

new java.io.File("hi-file.txt").asOutput.write("I'm a hi file! ... Really!")
Dibbeke
sumber
7

Ini cukup ringkas, saya kira:

scala> import java.io._
import java.io._

scala> val w = new BufferedWriter(new FileWriter("output.txt"))
w: java.io.BufferedWriter = java.io.BufferedWriter@44ba4f

scala> w.write("Alice\r\nBob\r\nCharlie\r\n")

scala> w.close()
Luigi Sgro
sumber
4
Cukup adil, tetapi "cukup ringkas" ini tidak diklasifikasikan sebagai "satu pernyataan": P
Erik Kaplun
Kode ini eptimo banyak masalah yang dirasakan Jawa. Sayangnya Scala tidak menganggap IO cukup penting sehingga perpustakaan standar tidak memasukkan IO.
Chris
Jawabannya menyembunyikan masalah sumber daya yatim dengan FileWriter baru. Jika FileWriter baru berhasil, tetapi BufferedWriter baru gagal, instance FileWriter tidak pernah terlihat lagi dan tetap terbuka sampai GCed (Pengumpulan Sampah), dan mungkin tidak ditutup bahkan saat itu (karena cara menyelesaikan jaminan bekerja di JVM). Saya telah menulis jawaban untuk pertanyaan serupa yang membahas masalah ini: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
2

Anda dapat melakukan ini dengan campuran perpustakaan Java dan Scala. Anda akan memiliki kontrol penuh atas pengkodean karakter. Namun sayangnya, file handle tidak akan ditutup dengan benar.

scala> import java.io.ByteArrayInputStream
import java.io.ByteArrayInputStream

scala> import java.io.FileOutputStream
import java.io.FileOutputStream

scala> BasicIO.transferFully(new ByteArrayInputStream("test".getBytes("UTF-8")), new FileOutputStream("test.txt"))
stefan.schwetschke
sumber
Anda memiliki masalah instance sumber daya yatim di kode Anda. Karena Anda tidak menangkap instance sebelum panggilan Anda, jika salah satu melemparkan pengecualian sebelum metode yang Anda panggil telah melewati parameter, sumber daya yang berhasil dipakai tidak akan setiap ditutup sampai GCed (Pengumpulan Sampah), dan bahkan maka mungkin bukan karena cara kerja jaminan GC. Saya telah menulis jawaban untuk pertanyaan serupa yang membahas masalah ini: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
1
Anda benar dan solusi Anda cukup bagus. Tapi di sini persyaratannya melakukannya dalam satu baris. Dan ketika Anda membaca dengan seksama, saya telah menyebutkan sumber kebocoran dalam jawaban saya sebagai batasan yang datang dengan persyaratan ini dan dengan pendekatan saya. Solusi Anda bagus, tetapi tidak akan cocok dengan persyaratan satu baris itu.
stefan.schwetschke
2

Saya tahu itu bukan satu baris, tapi itu memecahkan masalah keselamatan sejauh yang saya tahu;

// This possibly creates a FileWriter, but maybe not
val writer = Try(new FileWriter(new File("filename")))
// If it did, we use it to write the data and return it again
// If it didn't we skip the map and print the exception and return the original, just in-case it was actually .write() that failed
// Then we close the file
writer.map(w => {w.write("data"); w}).recoverWith{case e => {e.printStackTrace(); writer}}.map(_.close)

Jika Anda tidak peduli tentang penanganan pengecualian maka Anda dapat menulis

writer.map(w => {w.writer("data"); w}).recoverWith{case _ => writer}.map(_.close)
J_mie6
sumber
1

UPDATE: Sejak itu saya telah membuat solusi yang lebih efektif yang telah saya uraikan di sini: https://stackoverflow.com/a/34277491/501113

Saya menemukan diri saya semakin banyak bekerja di Lembar Kerja Scala dalam IDE Scala untuk Eclipse (dan saya percaya ada sesuatu yang setara di IntelliJ IDEA). Lagi pula, saya harus bisa melakukan satu-liner untuk menampilkan beberapa konten karena saya mendapatkan "Output melebihi batas cutoff." pesan jika saya melakukan sesuatu yang signifikan, terutama dengan koleksi Scala.

Saya datang dengan satu-liner yang saya masukkan ke bagian atas setiap Lembar Kerja Scala baru untuk menyederhanakan ini (dan jadi saya tidak harus melakukan seluruh latihan impor perpustakaan eksternal untuk kebutuhan yang sangat sederhana). Jika Anda ngotot dan memperhatikan bahwa secara teknis ada dua baris, itu hanya agar lebih mudah dibaca di forum ini. Ini adalah satu baris di Lembar Kerja Scala saya.

def printToFile(content: String, location: String = "C:/Users/jtdoe/Desktop/WorkSheet.txt") =
  Some(new java.io.PrintWriter(location)).foreach{f => try{f.write(content)}finally{f.close}}

Dan penggunaannya sederhana:

printToFile("A fancy test string\ncontaining newlines\nOMG!\n")

Ini memungkinkan saya untuk secara opsional memberikan nama file jika saya ingin memiliki file tambahan di luar standar (yang sepenuhnya menimpa file setiap kali metode dipanggil).

Jadi, penggunaan kedua adalah:

printToFile("A fancy test string\ncontaining newlines\nOMG!\n", "C:/Users/jtdoe/Desktop/WorkSheet.txt")

Nikmati!

chaotic3quilibrium
sumber
1

Gunakan ops ammon perpustakaan . Sintaksnya sangat minim, tetapi luasnya perpustakaan hampir seluas apa yang diharapkan dari mencoba tugas seperti itu dalam bahasa skrip shell seperti bash.

Pada halaman yang saya tautkan, ini menunjukkan banyak operasi yang dapat dilakukan dengan perpustakaan, tetapi untuk menjawab pertanyaan ini, ini adalah contoh penulisan ke file

import ammonite.ops._
write(pwd/'"file.txt", "file contents")
smac89
sumber
-1

Melalui keajaiban titik koma, Anda dapat membuat apa pun yang Anda suka satu garis.

import java.io.PrintWriter
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.charset.StandardCharsets
import java.nio.file.StandardOpenOption
val outfile = java.io.File.createTempFile("", "").getPath
val outstream = new PrintWriter(Files.newBufferedWriter(Paths.get(outfile)
  , StandardCharsets.UTF_8
  , StandardOpenOption.WRITE)); outstream.println("content"); outstream.flush(); outstream.close()
Ion Freeman
sumber
Tidak ada argumen di sini. Saya memutuskan untuk tidak mengingat API mana yang membutuhkan saya untuk menyiram bagian dari hidup saya, jadi saya selalu melakukannya.
Ion Freeman