Scala: เขียนสตริงลงไฟล์ในคำสั่งเดียว


144

สำหรับการอ่านไฟล์ใน Scala มีอยู่

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

มีวิธีที่เทียบเท่าและรัดกุมในการเขียนสตริงลงในไฟล์หรือไม่?

ภาษาส่วนใหญ่รองรับบางอย่างเช่นนั้น ฉันชอบคือ Groovy:

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

ฉันต้องการใช้รหัสสำหรับโปรแกรมที่มีตั้งแต่บรรทัดเดียวไปจนถึงหน้าสั้น ๆ ของรหัส ต้องใช้ห้องสมุดของคุณเองไม่เหมาะสม ฉันคาดหวังว่าภาษาสมัยใหม่จะให้ฉันเขียนบางสิ่งลงในไฟล์ได้อย่างสะดวก

มีโพสต์ที่คล้ายกันนี้ แต่พวกเขาไม่ได้ตอบคำถามที่แน่นอนของฉันหรือมุ่งเน้นไปที่ Scala รุ่นเก่า

ตัวอย่างเช่น:


ดูนี้คำถาม ฉันเห็นด้วยกับคำตอบที่ได้คะแนนสูงสุด - ดีกว่าที่จะมีห้องสมุดส่วนตัวของคุณเอง
แฟน

2
สำหรับกรณีนี้ฉันไม่เห็นด้วยที่จะต้องเขียนห้องสมุดส่วนตัวของตัวเอง โดยปกติเมื่อคุณเขียนโปรแกรมชิ้นเล็ก ๆ เพื่อทำสิ่งต่าง ๆ (บางทีฉันแค่อยากจะเขียนมันเป็นสคริปต์หน้าสกาล่าหน้าเดียวหรือใน REPL) จากนั้นการเข้าถึงห้องสมุดส่วนตัวจะกลายเป็นความเจ็บปวด
smartnut007

โดยทั่วไปดูเหมือนว่าจะไม่มีสิ่งใดในสกาล่า 2.9 สำหรับจุดนี้ ไม่แน่ใจว่าฉันควรเปิดคำถามนี้ไว้ยังไง
smartnut007

1
หากคุณค้นหา java.io ในซอร์สโค้ดของ Scala คุณจะไม่พบสิ่งที่เกิดขึ้นมากมายและยังน้อยกว่าสำหรับการดำเนินการแสดงผลโดยเฉพาะอย่างยิ่ง PrintWriter ดังนั้นจนกว่าห้องสมุด Scala-IO จะกลายเป็นส่วนหนึ่งอย่างเป็นทางการของ Scala เราจะต้องใช้จาวาบริสุทธิ์ตามที่แสดงโดยกระบวนทัศน์
PhiLho

ใช่ปัญหายังต้องการสกาล่า - io ที่เข้ากันได้กับการปรับปรุง jdk7 IO
smartnut007

คำตอบ:


77

กระชับหนึ่งบรรทัด:

import java.io.PrintWriter
new PrintWriter("filename") { write("file contents"); close }

14
ในขณะที่วิธีการนี้ดูดี แต่ก็ไม่ใช่ทั้งข้อยกเว้นและการเข้ารหัสที่ปลอดภัย หากมีข้อยกเว้นที่เกิดขึ้นในwrite(), closeจะไม่ถูกเรียกและไฟล์จะไม่ถูกปิด PrintWriterยังใช้การเข้ารหัสระบบเริ่มต้นซึ่งเป็นมากที่ไม่ดีสำหรับการพกพา และในที่สุดวิธีการนี้จะสร้างคลาสแยกต่างหากสำหรับบรรทัดนี้โดยเฉพาะ (อย่างไรก็ตามเนื่องจาก Scala สร้างคลาสจำนวนมากถึงแม้ว่าจะใช้รหัสง่าย ๆ
Vladimir Matveev

ตามความคิดเห็นข้างต้นในขณะที่มันเป็นหนึ่งซับมันไม่ปลอดภัย หากคุณต้องการความปลอดภัยมากขึ้นในขณะที่มีตัวเลือกเพิ่มเติมรอบ ๆ ตำแหน่งและ / หรือการบัฟเฟอร์อินพุตให้ดูคำตอบที่ฉันเพิ่งโพสต์ในเธรดที่คล้ายกัน: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

2
สำหรับการบันทึกการดีบักชั่วคราวฉันใช้new PrintWriter(tmpFile) { try {write(debugmsg)} finally {close} }
Randall Whitman

Stylistically มันอาจจะดีกว่าที่จะใช้close()ผมคิดว่า
เคซี่ย์

นี่คือสองบรรทัดและสามารถสร้างเป็นบรรทัดเดียวได้อย่างง่ายดาย:new java.io.PrintWriter("/tmp/hello") { write("file contents"); close }
Philluminati

163

เป็นเรื่องแปลกที่ไม่มีใครแนะนำการทำงาน NIO.2 (มีให้ตั้งแต่ 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))

ฉันคิดว่านี่เป็นวิธีที่ง่ายที่สุดและง่ายที่สุดและใช้สำนวนมากที่สุดและไม่จำเป็นต้องพึ่งพา Java เลย


สิ่งนี้จะละเว้นอักขระบรรทัดใหม่ด้วยเหตุผลบางประการ
Kakaji

@Kakaji คุณช่วยอธิบายเพิ่มเติมได้ไหม? ฉันเพิ่งทดสอบกับสตริงที่มีบรรทัดใหม่และทำงานได้อย่างสมบูรณ์ มันไม่สามารถกรองสิ่งใด ๆ ได้ - Files.write () เขียนอาร์เรย์ไบต์เป็นหยดโดยไม่ต้องวิเคราะห์เนื้อหา ท้ายที่สุดในข้อมูลไบนารี่ 0x0d บางไบต์อาจมีความหมายสำคัญอื่นที่ไม่ใช่บรรทัด
Vladimir Matveev

4
หมายเหตุเพิ่มเติม: หากคุณมีไฟล์เพียง '.toPath' เพื่อรับพารามิเตอร์แรก
akauppi

1
หนึ่งนี้เป็นเกรดอุตสาหกรรมอื่น ๆ (เนื่องจากเป็นทางเลือกที่ charsets อย่างชัดเจน) แต่ขาดความเรียบง่าย (และหนึ่งซับ .. ) reflect.io.File.writeAll(contents)ของ ทำไม? สามบรรทัดเมื่อคุณรวมคำสั่งการนำเข้าสองรายการ บางที IDE ทำเพื่อคุณโดยอัตโนมัติ แต่ถ้าใน REPL มันไม่ใช่เรื่องง่าย
javadba

3
@javadba เราอยู่ใน JVM การนำเข้าค่อนข้างจะไม่นับเป็น 'บรรทัด' โดยเฉพาะอย่างยิ่งเพราะทางเลือกนั้นมักจะเพิ่มการพึ่งพาไลบรารีใหม่เสมอ อย่างไรก็ตามFiles.writeยังยอมรับjava.lang.Iterableเป็นอาร์กิวเมนต์ที่สองและเราสามารถได้รับจากที่สกาล่าIterableคือสวยมาก ๆ ชนิดคอลเลกชันโดยใช้ตัวแปลง: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

นี่คือซับในแบบย่อที่ใช้ reflect.io.file ซึ่งทำงานกับ Scala 2.12:

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

อีกวิธีหนึ่งถ้าคุณต้องการใช้ไลบรารี Java คุณสามารถแฮ็คนี้:

Some(new PrintWriter("filename")).foreach{p => p.write("hello world"); p.close}

1
+1 ใช้งานได้ดี Some/ foreachคำสั่งผสมเป็นขี้ขลาดบิต แต่มันมาพร้อมกับประโยชน์ของการไม่มีลอง / จับ / สุดท้าย
Brent Faust

3
ถ้าการเขียนมีข้อยกเว้นคุณอาจต้องการปิดไฟล์ถ้าคุณวางแผนที่จะกู้คืนจากข้อยกเว้นและอ่าน / เขียนไฟล์อีกครั้ง โชคดีที่สกาล่าให้หนึ่ง liners สำหรับการทำเช่นนี้
Garrett Hall

25
ไม่แนะนำเนื่องจากแพ็คเกจ scala.tools.nsc.io ไม่ได้เป็นส่วนหนึ่งของ API สาธารณะ แต่ใช้โดยคอมไพเลอร์
Giovanni Botta

3
Some/ foreachสับเป็นเหตุผลว่าทำไมหลาย ๆ คนเกลียด Scala สำหรับรหัสที่อ่านไม่ได้จะทำให้แฮกเกอร์สามารถผลิต
Erik Kaplun

3
scala.tootls.nsc.io.Fileเป็นนามแฝงสำหรับ reflect.io.Fileๆ ยังคงเป็น API ภายใน แต่อย่างน้อยก็สั้นกว่าเล็กน้อย
kostja

41

ถ้าคุณชอบไวยากรณ์ Groovy คุณสามารถใช้รูปแบบการออกแบบPimp-My-Libraryเพื่อนำมาที่ 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 )

}

มันจะทำงานได้ตามที่คาดไว้:

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

2
คุณไม่เคยโทรปิดอินสแตนซ์ที่มาของ Source.fromFile ซึ่งหมายความว่าทรัพยากรจะไม่ถูกปิดจนกว่าจะเป็น GCed (รวบรวมขยะ) และ PrintWriter ของคุณไม่ได้ถูกบัฟเฟอร์ดังนั้นจึงใช้บัฟเฟอร์เริ่มต้นขนาดเล็ก JVM ขนาด 8 ไบต์ซึ่งอาจทำให้ IO ของคุณช้าลงอย่างมาก ฉันเพิ่งสร้างคำตอบในเธรดที่คล้ายกันซึ่งเกี่ยวข้องกับปัญหาเหล่านี้: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

1
คุณพูดถูก แต่วิธีที่ฉันให้ที่นี่ทำงานได้ดีสำหรับโปรแกรมระยะสั้นที่มี IO ขนาดเล็ก ฉันไม่แนะนำสำหรับกระบวนการเซิร์ฟเวอร์หรือข้อมูลขนาดใหญ่ (ตามกฎทั่วไป, มากกว่า 500 MB)
กระบวนทัศน์

22
import sys.process._
"echo hello world" #> new java.io.File("/tmp/example.txt") !

มันใช้งานไม่ได้กับฉันใน REPL ไม่มีข้อความผิดพลาด แต่ถ้าฉันดู /tmp/example.txt ไม่มี
ผู้ใช้ไม่ทราบ

ไม่ทราบ @user ผู้ใช้ขออภัยที่พลาด '!' ที่ส่วนท้ายของคำสั่งแก้ไขในขณะนี้
xiefei

ยอดเยี่ยม! ตอนนี้มันได้ผลฉันอยากจะรู้ว่าทำไม อะไรคือ#>สิ่งที่!ทำ
ผู้ใช้ไม่ทราบ

10
โซลูชันนี้ไม่สามารถพกพาได้! ใช้ได้กับระบบ * nix เท่านั้น
Giovanni Botta

2
วิธีนี้ใช้ไม่ได้กับการเขียนสตริงเอง มันจะทำงานสำหรับสตริงสั้น ๆ ที่คุณสามารถส่งเป็นอาร์กิวเมนต์ไปยังechoเครื่องมือบรรทัดคำสั่ง
รวย

15

ห้องสมุดขนาดเล็กที่ฉันเขียน: https://github.com/pathikrit/better-files

file.write("Hi!")

หรือ

file << "Hi!"

3
รักห้องสมุดของคุณ! คำถามนี้เป็นคำถามยอดฮิตอันดับหนึ่งเมื่อ googling วิธีเขียนไฟล์ด้วยสกาล่าตอนนี้โครงการของคุณเริ่มใหญ่ขึ้นแล้วคุณอาจต้องการขยายคำตอบเล็กน้อย?
asac

12

คุณสามารถใช้Apache Utils writeStringToFileดูที่ฟังก์ชั่น เราใช้ห้องสมุดนี้ในโครงการของเรา


3
ฉันใช้มันตลอดเวลาเช่นกัน ถ้าคุณอ่านคำถามนี้อย่างระมัดระวังฉันได้คิดไปแล้วว่าทำไมฉันถึงไม่ต้องการใช้ห้องสมุด
smartnut007

โดยไม่ใช้ไลบรารีฉันได้สร้างโซลูชันที่จัดการข้อยกเว้นระหว่างการอ่าน / เขียนและสามารถถูกบัฟเฟอร์เกินกว่าค่าเริ่มต้นบัฟเฟอร์เล็ก ๆ ที่จัดทำโดยไลบรารี Java: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

7

หนึ่งยังมีรูปแบบนี้ซึ่งเป็นทั้งรัดกุมและห้องสมุดพื้นฐานเขียนอย่างสวยงาม (ดูซอร์สโค้ด):

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

7

นี่มันกระชับพอแล้วฉันเดาว่า:

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

4
ยุติธรรมเพียงพอ แต่ "รัดกุมพอ" ไม่จัดว่าเป็น "หนึ่งคำสั่ง": P
Erik Kaplun

รหัสนี้ eptimozes ปัญหาการรับรู้จำนวนมากของ Java น่าเสียดายที่สกาล่าไม่ได้พิจารณาว่า IO สำคัญพอที่จะทำให้ไลบรารีมาตรฐานไม่ได้รวมไว้
Chris

คำตอบจะซ่อนปัญหาทรัพยากรที่ถูกโยงถึงด้วย FileWriter ใหม่ หาก FileWriter ใหม่ประสบความสำเร็จ แต่ BufferedWriter ใหม่ล้มเหลวอินสแตนซ์ FileWriter จะไม่เห็นอีกครั้งและยังคงเปิดค้างอยู่จนกระทั่ง GCed (รวบรวมขยะ) และอาจไม่สามารถปิดได้ (เนื่องจากวิธีการรับประกันการทำงานใน JVM) ฉันได้เขียนคำตอบสำหรับคำถามที่คล้ายกันซึ่งจัดการปัญหาเหล่านี้: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

2

คุณสามารถทำได้ด้วยการผสมผสานระหว่าง Java และ Scala คุณจะสามารถควบคุมการเข้ารหัสอักขระได้อย่างสมบูรณ์ แต่น่าเสียดายที่การจัดการไฟล์จะไม่ถูกปิดอย่างถูกต้อง

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

คุณมีปัญหาทรัพยากรอินสแตนซ์กำพร้าในรหัสของคุณ เนื่องจากคุณไม่ได้จับอินสแตนซ์ก่อนการโทรของคุณหากมีข้อผิดพลาดก่อนที่วิธีการโทรของคุณจะถูกส่งผ่านพารามิเตอร์ทรัพยากรที่อินสแตนซ์ที่ประสบความสำเร็จจะไม่ถูกปิดจนกว่าจะ GCed (รวบรวมขยะ) และแม้แต่ อาจไม่ใช่เพราะวิธีการที่ GC รับประกันงาน ฉันได้เขียนคำตอบสำหรับคำถามที่คล้ายกันซึ่งจัดการปัญหาเหล่านี้: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

1
คุณพูดถูกและทางออกของคุณค่อนข้างดี แต่ที่นี่ความต้องการได้ทำในบรรทัดเดียว และเมื่อคุณอ่านอย่างละเอียดฉันได้กล่าวถึงการรั่วไหลของทรัพยากรในคำตอบของฉันเป็นข้อ จำกัด ที่มาพร้อมกับข้อกำหนดนี้และด้วยวิธีการของฉัน โซลูชันของคุณดี แต่จะไม่ตรงกับข้อกำหนดของบรรทัดเดียว
stefan.schwetschke

2

ฉันรู้ว่ามันไม่ใช่หนึ่งบรรทัด แต่มันแก้ไขปัญหาด้านความปลอดภัยเท่าที่ฉันสามารถบอกได้

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

หากคุณไม่สนใจเกี่ยวกับการจัดการข้อยกเว้นคุณสามารถเขียนได้

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

1

อัปเดต: ตั้งแต่ฉันได้สร้างโซลูชันที่มีประสิทธิภาพยิ่งขึ้นซึ่งฉันได้ทำรายละเอียดไว้ที่นี่: https://stackoverflow.com/a/34277491/501113

ฉันพบว่าตัวเองทำงานมากขึ้นเรื่อย ๆ ในแผ่นงาน Scala ภายใน Scala IDE สำหรับ Eclipse (และฉันเชื่อว่ามีบางสิ่งที่เทียบเท่าใน IntelliJ IDEA) อย่างไรก็ตามฉันต้องสามารถทำการซับหนึ่งครั้งเพื่อส่งออกเนื้อหาบางส่วนเมื่อฉันได้รับ "เอาต์พุตเกินขีด จำกัด การตัดออก" ข้อความถ้าฉันกำลังทำสิ่งที่สำคัญโดยเฉพาะอย่างยิ่งกับคอลเลกชันสกาล่า

ฉันคิดขึ้นมาหนึ่งบรรทัดที่ฉันแทรกไว้ด้านบนของแผ่นงาน Scala ใหม่แต่ละแผ่นเพื่อทำให้สิ่งนี้ง่ายขึ้น (และดังนั้นฉันจึงไม่ต้องทำแบบฝึกหัดการนำเข้าห้องสมุดภายนอกทั้งหมดเพื่อความต้องการที่ง่ายมาก) หากคุณเป็นคนขี้เหนียวและสังเกตว่ามันเป็นเทคนิคสองบรรทัดมันเป็นเพียงการทำให้อ่านง่ายขึ้นในฟอรัมนี้ มันเป็นบรรทัดเดียวใน Scala Worksheet ของฉัน

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

และการใช้งานเป็นเพียง:

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

สิ่งนี้ทำให้ฉันสามารถระบุชื่อไฟล์ได้หากฉันต้องการมีไฟล์เพิ่มเติมนอกเหนือจากค่าเริ่มต้น (ซึ่งจะเขียนทับไฟล์ทุกครั้งที่มีการเรียกเมธอด)

ดังนั้นการใช้งานที่สองคือ:

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

สนุก!


1

ใช้ไลบรารี่ของแอมโมไนต์ ไวยากรณ์มีน้อยมาก แต่ความกว้างของไลบรารีเกือบจะกว้างพอ ๆ กับที่คาดหวังจากการพยายามทำภารกิจในภาษาสคริปต์เชลล์เช่นทุบตี

ในหน้าที่ฉันเชื่อมโยงไปถึงมันแสดงการทำงานหลายอย่างที่สามารถทำได้กับห้องสมุด แต่เพื่อตอบคำถามนี้นี่เป็นตัวอย่างของการเขียนไปยังไฟล์

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

-1

ด้วยเวทย์มนตร์ของอัฒภาคคุณสามารถสร้างทุกสิ่งที่คุณต้องการได้ในสายการบินเดียว

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

ไม่มีข้อโต้แย้งที่นี่ ฉันตัดสินใจที่จะไม่จำ API ที่ต้องการให้ฉันล้างส่วนใดส่วนหนึ่งในชีวิตของฉันดังนั้นฉันจะทำเช่นนั้นเสมอ
ไอออนฟรีแมน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.