จะเขียนไฟล์ใน Scala ได้อย่างไร?


157

Sourceสำหรับการอ่านมีความเป็นนามธรรมที่มีประโยชน์ ฉันจะเขียนบรรทัดลงในไฟล์ข้อความได้อย่างไร


1
หากคุณรู้วิธีการใช้งาน Java คุณสามารถใช้ Scala ได้เหมือนกัน คำถามของคุณเป็นพิเศษกับห้องสมุดมาตรฐานของสกาล่าหรือไม่?
ข้าวสาลี

1
@wheaties ใช่วิธีที่ดีที่สุดในการทำเช่นนี้ใน scala
yura

ห้องสมุดนี้ดีจริงๆ: github.com/pathikrit/better-files
Robin

ไลบรารี่ OS-Lib ของ
Lihaoyi

คำตอบ:


71

แก้ไข 2019 (8 ปีต่อมา), Scala-IOเป็นไม่ได้ใช้งานมากถ้าใด ๆLi Haoyiแสดงให้เห็นห้องสมุดของเขาเองlihaoyi/os-libที่เขานำเสนอดังต่อไปนี้

มิถุนายน 2019 ซาเวียร์กุยhotกล่าวถึงคำตอบในห้องสมุดUsingซึ่งเป็นยูทิลิตี้สำหรับการจัดการทรัพยากรอัตโนมัติ


แก้ไข (กันยายน 2011): เนื่องจากEduardo Costaถามเกี่ยวกับ Scala2.9 และเนื่องจากความคิดเห็นของRick-777ที่scalax.IO ยืนยันประวัติค่อนข้างไม่มีอยู่จริงตั้งแต่กลางปี ​​2009 ...

Scala-IOเปลี่ยนสถานที่แล้ว: ดู repo GitHubจาก Jesse Eichar (บน SO ด้วย ):

โครงการ Scala IO ร่มประกอบด้วยโครงการย่อยไม่กี่สำหรับด้านที่แตกต่างและส่วนขยายของ IO
Scala IO มีสององค์ประกอบหลัก:

  • แกนหลัก - หลักเกี่ยวกับการอ่านและการเขียนข้อมูลไปยังและจากแหล่งที่มาและอ่างล้างมือโดยพลการ ลักษณะหินมุมInput, OutputและSeekableที่ให้ API หลัก
    ชั้นเรียนอื่น ๆ ที่มีความสำคัญเป็นResource, และReadCharsWriteChars
  • ไฟล์ - ไฟล์เป็นAPI File(เรียกว่าPath) ที่ใช้การรวมกันของระบบไฟล์ Java 7 NIO และ SBT PathFinder API
    PathและFileSystemเป็นจุดเข้าหลักใน Scala IO File API
import scalax.io._

val output:Output = Resource.fromFile("someFile")

// Note: each write will open a new connection to file and 
//       each write is executed at the begining of the file,
//       so in this case the last write will be the contents of the file.
// See Seekable for append and patching files
// Also See openOutput for performing several writes with a single connection

output.writeIntsAsBytes(1,2,3)
output.write("hello")(Codec.UTF8)
output.writeStrings(List("hello","world")," ")(Codec.UTF8)

คำตอบเดิม (มกราคม 2011) กับสถานที่เก่าแก่สำหรับ scala-io:

หากคุณไม่ต้องการรอ Scala2.9 คุณสามารถใช้ห้องสมุดscala-incubator / scala-io
(ดังที่กล่าวไว้ใน " ทำไมแหล่งสกาล่าไม่ปิด InputStream พื้นฐาน ")

ดูตัวอย่าง

{ // several examples of writing data
    import scalax.io.{
      FileOps, Path, Codec, OpenOption}
    // the codec must be defined either as a parameter of ops methods or as an implicit
    implicit val codec = scalax.io.Codec.UTF8


    val file: FileOps = Path ("file")

    // write bytes
    // By default the file write will replace
    // an existing file with the new data
    file.write (Array (1,2,3) map ( _.toByte))

    // another option for write is openOptions which allows the caller
    // to specify in detail how the write should take place
    // the openOptions parameter takes a collections of OpenOptions objects
    // which are filesystem specific in general but the standard options
    // are defined in the OpenOption object
    // in addition to the definition common collections are also defined
    // WriteAppend for example is a List(Create, Append, Write)
    file.write (List (1,2,3) map (_.toByte))

    // write a string to the file
    file.write("Hello my dear file")

    // with all options (these are the default options explicitely declared)
    file.write("Hello my dear file")(codec = Codec.UTF8)

    // Convert several strings to the file
    // same options apply as for write
    file.writeStrings( "It costs" :: "one" :: "dollar" :: Nil)

    // Now all options
    file.writeStrings("It costs" :: "one" :: "dollar" :: Nil,
                    separator="||\n||")(codec = Codec.UTF8)
  }

15
รุ่น Scala 2.9 เป็นอย่างไร? :)
Eduardo Costa

โครงการ scalax ดูเหมือนจะตาย (ไม่มีข้อผูกมัดตั้งแต่มิถุนายน 2009) ถูกต้องหรือไม่ scalax คอมมิชชันประวัติ
Rick-777

@Eduardo: ฉันได้ตอบคำถามของฉันเรียบร้อยแล้วด้วยสถานที่ใหม่สำหรับห้องสมุด scala-io (ซึ่งได้รับการปรับปรุงสำหรับ Scala2.9: github.com/jesseeichar/scala-io/issues/20 )
VonC

10
นี่เป็นข้อเสนอแนะปัจจุบันสำหรับ Scala 2.10 จริงหรือไม่ ใช้ Scala IO หรือไม่ ยังไม่มีอะไรใน core Scala หรือยัง
Phil

2
ฉันไม่เคยใช้ scalax.io แต่ตัดสินจากบรรทัดตัวอย่างเหล่านี้ดูเหมือนว่าการออกแบบ API นั้นค่อนข้างแย่ วิธีการผสมสำหรับข้อมูลตัวละครและไบนารีในอินเทอร์เฟซเดียวทำให้มีความรู้สึกน้อยและอาจนำไปสู่การเข้ารหัสข้อบกพร่องที่หายาก การออกแบบของ java.io (Reader / Writer vs. InputStream / OutputStream) ดูเหมือนดีขึ้นมาก
jcsahnwaldt Reinstate Monica

211

นี่เป็นหนึ่งในคุณสมบัติที่ขาดหายไปจาก Scala มาตรฐานที่ฉันพบว่ามีประโยชน์มากที่ฉันเพิ่มลงในห้องสมุดส่วนตัวของฉัน (คุณอาจมีห้องสมุดส่วนตัวด้วย) โค้ดก็เป็นเช่นนั้น:

def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) {
  val p = new java.io.PrintWriter(f)
  try { op(p) } finally { p.close() }
}

และมันถูกใช้แบบนี้:

import java.io._
val data = Array("Five","strings","in","a","file!")
printToFile(new File("example.txt")) { p =>
  data.foreach(p.println)
}

1
ใหม่ java.io.PrintWriter () ใช้การเข้ารหัสเริ่มต้นของแพลตฟอร์มซึ่งอาจหมายความว่าไฟล์ผลลัพธ์นั้นไม่สามารถพกพาได้ ตัวอย่างเช่นหากคุณต้องการสร้างไฟล์ที่คุณสามารถส่งทางอีเมลได้ในภายหลังคุณควรใช้ตัวสร้าง PrintWriter ที่อนุญาตให้คุณระบุการเข้ารหัส
jcsahnwaldt Reinstate Monica

@JonaChristopherSahnwaldt - แน่นอนว่าในกรณีพิเศษคุณอาจต้องการระบุการเข้ารหัส ค่าเริ่มต้นสำหรับแพลตฟอร์มนั้นเป็นค่าเริ่มต้นที่สมเหตุสมผลที่สุดโดยเฉลี่ย เหมือนกับSource(การเข้ารหัสเริ่มต้นโดยค่าเริ่มต้น) แน่นอนคุณสามารถเพิ่มเช่นenc: Option[String] = Noneพารามิเตอร์หลังจากfถ้าคุณพบว่านี่เป็นความต้องการทั่วไป
Rex Kerr

6
@RexKerr - ฉันไม่เห็นด้วย หนึ่งควรระบุการเข้ารหัสในเกือบทุกกรณี ข้อผิดพลาดในการเข้ารหัสส่วนใหญ่ที่ฉันพบเกิดขึ้นเพราะผู้คนไม่เข้าใจหรือไม่คิดถึงการเข้ารหัส พวกเขาใช้ค่าเริ่มต้นและไม่รู้ด้วยซ้ำเพราะ APIs มากเกินไปปล่อยให้พวกมันหนีไป ทุกวันนี้ค่าเริ่มต้นที่สมเหตุสมผลที่สุดน่าจะเป็น UTF-8 บางทีคุณอาจทำงานกับภาษาอังกฤษและภาษาอื่น ๆ ที่สามารถเขียนเป็น ASCII ได้ โชคดีนะคุณ. ฉันอาศัยอยู่ในประเทศเยอรมนีและต้องซ่อม umlauts ที่ชำรุดมากกว่าที่ฉันจำได้
jcsahnwaldt Reinstate Monica

3
@JonaChristopherSahnwaldt - นี่คือเหตุผลที่มีการเข้ารหัสเริ่มต้นที่สมเหตุสมผลไม่ได้บังคับให้ทุกคนระบุไว้ตลอดเวลา แต่ถ้าคุณใช้ Mac และไฟล์ของคุณที่เขียนโดย Java นั้นเป็น gobbledygook เพราะมันไม่ได้เข้ารหัสด้วย Mac OS Roman ฉันไม่แน่ใจว่ามันดีกว่าอันตราย ฉันคิดว่ามันเป็นความผิดของแพลตฟอร์มที่พวกเขาไม่เห็นด้วยกับชุดอักขระ ในฐานะนักพัฒนารายบุคคลการพิมพ์สตริงจะไม่สามารถแก้ปัญหาได้ (นักพัฒนาซอฟต์แวร์ทุกคนยอมรับ UTF-8 แต่จะเป็นไปตามค่าเริ่มต้น)
Rex Kerr

@JonaChristopherSahnwaldt +10 สำหรับการแก้ไข umlaut ที่เสียหายทั้งหมด ใช้ค้อนไม่ได้หรืออาจเป็นรูเจาะใช่ไหม หรือว่าพวกเขามีช่องโหว่ที่ต้องการการเติมบางทีผู้ชายคนนี้อาจช่วยคุณyoutube.com/watch?v=E-eBBzWEpwEแต่อย่างจริงจังอิทธิพลของ ASCII นั้นสร้างความเสียหายให้กับโลกยอมรับว่าควรระบุและเริ่มต้นเป็น UTF- 8
Davos

50

คล้ายกับคำตอบของเร็กซ์เคอร์ แต่ทั่วไปมากกว่า ก่อนอื่นฉันใช้ฟังก์ชั่นตัวช่วย:

/**
 * Used for reading/writing to database, files, etc.
 * Code From the book "Beginning Scala"
 * http://www.amazon.com/Beginning-Scala-David-Pollak/dp/1430219890
 */
def using[A <: {def close(): Unit}, B](param: A)(f: A => B): B =
try { f(param) } finally { param.close() }

จากนั้นฉันใช้สิ่งนี้เป็น:

def writeToFile(fileName:String, data:String) = 
  using (new FileWriter(fileName)) {
    fileWriter => fileWriter.write(data)
  }

และ

def appendToFile(fileName:String, textData:String) =
  using (new FileWriter(fileName, true)){ 
    fileWriter => using (new PrintWriter(fileWriter)) {
      printWriter => printWriter.println(textData)
    }
  }

เป็นต้น


39
อย่าเข้าใจฉันผิดฉันชอบรหัสของคุณและเป็นเรื่องเกี่ยวกับการศึกษา แต่ยิ่งฉันเห็นสิ่งปลูกสร้างเหล่านี้สำหรับปัญหาที่เรียบง่ายมากเท่าไหร่มันก็ยิ่งทำให้ฉันนึกถึงเรื่องตลก "hello world" เก่า: ariel.com.au/jokes/The_Evolution_of_a_Programmer .html :-) (+1 คะแนนจากฉัน)
greenoldman

4
หากคุณกำลังเขียนหนึ่งสมุทรไม่ต้องทำอะไรเลย หากคุณกำลังเขียนโปรแกรมที่สำคัญ (ขนาดใหญ่ที่มีความต้องการการบำรุงรักษาและวิวัฒนาการอย่างต่อเนื่อง) การคิดแบบนี้จะนำไปสู่การลดลงของคุณภาพซอฟต์แวร์ที่รวดเร็วและเป็นอันตรายที่สุด
Randall Schulz

3
ไม่ใช่ทุกคนที่จะมี "ดวงตาสกาล่า" จนกว่าจะมีการฝึกฝนในระดับหนึ่ง - มันเป็นเรื่องตลกที่จะเห็นตัวอย่างโค้ดนี้มาจาก "เริ่มต้น" สกาล่า
asyncwait

asyncwait "เริ่มต้น" สกาล่า ... ชื่อที่น่าขันที่สุดเท่าที่เคยมีมาหมายเหตุ: ฉันมีหนังสือ ... และตอนนี้ฉันเริ่มเข้าใจแล้ว .. ฉันคิดว่าฉันเป็นขั้นตอนก่อน "เริ่มต้น" lol: D ........
user1050817

1
ปัญหานั้นน้อยกว่าเทคนิคของ Scala ที่นี่ แต่มีความละเอียดและสไตล์ที่ไม่ดี ฉันแก้ไขสิ่งนี้ให้อ่านง่ายขึ้นมากขึ้น หลังจาก refactor ของฉันมันเป็นเพียง 4 บรรทัด (ดี, 4 กับความยาวบรรทัด IDE, ใช้ 6 ที่นี่เพื่อให้พอดีกับหน้าจอ) IMHO เป็นคำตอบที่ดีมาก
samthebest

38

คำตอบง่ายๆ:

import java.io.File
import java.io.PrintWriter

def writeToFile(p: String, s: String): Unit = {
    val pw = new PrintWriter(new File(p))
    try pw.write(s) finally pw.close()
  }

1
@samthebest คุณสามารถเพิ่มห้องสมุดที่คุณต้องการได้importหรือไม่
Daniel

1
ในฐานะของ java 7 ให้ใช้ java.nio.file แทน: def writeToFile (ไฟล์: String, stringToWrite: String): Unit = {val writer = Files.newBufferedWriter (Paths.get (ไฟล์)) ในที่สุดลองใช้ writer.write (stringToWrite) writer.close ()}
E Shindler

20

ให้คำตอบอื่นเพราะการแก้ไขคำตอบอื่น ๆ ของฉันถูกปฏิเสธ

นี่เป็นคำตอบที่กระชับและง่ายที่สุด (คล้ายกับ Garret Hall's)

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

สิ่งนี้คล้ายกับ Jus12 แต่ไม่มี verbosity และมีสไตล์ของรหัสที่ถูกต้อง

def using[A <: {def close(): Unit}, B](resource: A)(f: A => B): B =
  try f(resource) finally resource.close()

def writeToFile(path: String, data: String): Unit = 
  using(new FileWriter(path))(_.write(data))

def appendToFile(path: String, data: String): Unit =
  using(new PrintWriter(new FileWriter(path, true)))(_.println(data))

หมายเหตุคุณไม่ต้องการวงเล็บปีกกาสำหรับtry finallyหรือ lambdas และบันทึกการใช้ไวยากรณ์ตัวยึดตำแหน่ง ยังทราบการตั้งชื่อที่ดีขึ้น


2
ขออภัยรหัสของคุณเป็นจริงไม่ได้ตามimplementedข้อกำหนดเบื้องต้น คุณไม่สามารถใช้รหัสที่ไม่ได้ใช้งานได้ ฉันหมายความว่าคุณต้องบอกวิธีการค้นหาเนื่องจากไม่สามารถใช้งานได้ตามค่าเริ่มต้นและไม่เป็นที่รู้จัก
Val

15

นี่คือซับในหนึ่งรัดกุมโดยใช้ไลบรารีคอมไพเลอร์ Scala:

scala.tools.nsc.io.File("filename").writeAll("hello world")

หรือถ้าคุณต้องการใช้ห้องสมุด Java คุณสามารถแฮ็คนี้:

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

นำเข้าอะไร คือไฟล์มาจากไหน?
เบ็นฮัทชิสัน

ไลบรารีคอมไพเลอร์ Scala
Garrett Hall

3
ไม่สามารถใช้งานได้อีกต่อไป (ไม่ใช่ใน Scala 2.11)
Brent Faust

1
คุณกำลังพูดเรื่องอะไร scala.tools.nsc.io.File("/tmp/myFile.txt")ทำงานใน Scala 2.11.8

1
ตอนนี้อยู่ใน scala.reflect.io.File
Keith Nordstrom

13

หนึ่งสมุทรสำหรับการบันทึก / อ่าน / จากการใช้Stringjava.nio

import java.nio.file.{Paths, Files, StandardOpenOption}
import java.nio.charset.{StandardCharsets}
import scala.collection.JavaConverters._

def write(filePath:String, contents:String) = {
  Files.write(Paths.get(filePath), contents.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE)
}

def read(filePath:String):String = {
  Files.readAllLines(Paths.get(filePath), StandardCharsets.UTF_8).asScala.mkString
}

ไม่เหมาะสำหรับไฟล์ขนาดใหญ่ แต่จะใช้งานได้

ลิงค์บางส่วน:

java.nio.file.Files.write
java.lang.String.getBytes
scala.collection.JavaConverters
scala.collection.immutable.List.mkString


ทำไมจึงไม่เหมาะกับไฟล์ขนาดใหญ่
Chetan Bhasin

2
@ChetanBhasin อาจเป็นเพราะwriteจะคัดลอกcontentsไปยังอาร์เรย์ไบต์ใหม่แทนที่จะสตรีมไปยังไฟล์ดังนั้นที่จุดสูงสุดจึงใช้หน่วยความจำมากถึงสองเท่ามากกว่าที่contentsอยู่คนเดียว
Daniel Werner

10

น่าเสียดายสำหรับคำตอบที่ดีที่สุด Scala-IO ก็ตายแล้ว หากคุณไม่สนใจใช้การอ้างอิงจากบุคคลที่สามให้ลองใช้ไลบรารี่ OS-Libของฉัน สิ่งนี้ทำให้การทำงานกับไฟล์พา ธ และระบบไฟล์ง่ายมาก:

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

แต่ก็มีหนึ่งสมุทรสำหรับการเขียนไปยังไฟล์ , ผนวกกับไฟล์ , เขียนทับไฟล์และการดำเนินงานทั่วไปอื่น ๆ อีกมากมายที่มีประโยชน์ /


ขอขอบคุณสำหรับการอัปเดตนี้ upvoted ฉันได้อ้างอิงคำตอบของคุณด้วยตัวเองด้านบนเพื่อให้มองเห็นได้มากขึ้น
VonC

7

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

file.appendLine("Hello", "World")

หรือ

file << "Hello" << "\n" << "World"

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

6

เริ่มต้นที่ห้องสมุดมาตรฐานให้ยูทิลิตี้การจัดการทรัพยากรโดยเฉพาะ:Scala 2.13Using

มันสามารถใช้ในกรณีนี้กับทรัพยากรเช่นPrintWriterหรือBufferedWriterที่ขยายAutoCloseableเพื่อเขียนไปยังไฟล์และไม่ว่าอะไรจะปิดทรัพยากรหลังจากนั้น:

  • ตัวอย่างเช่นด้วยjava.ioapi:

    import scala.util.Using, java.io.{PrintWriter, File}
    
    // val lines = List("hello", "world")
    Using(new PrintWriter(new File("file.txt"))) {
      writer => lines.foreach(writer.println)
    }
  • หรือด้วยjava.nioapi:

    import scala.util.Using, java.nio.file.{Files, Paths}, java.nio.charset.Charset
    
    // val lines = List("hello", "world")
    Using(Files.newBufferedWriter(Paths.get("file.txt"), Charset.forName("UTF-8"))) {
      writer => lines.foreach(line => writer.write(line + "\n"))
    }

6

อัปเดตเมื่อวันที่ 2019 / Sep / 01:

  • เริ่มต้นด้วย Scala 2.13 ให้เลือกใช้scala.util.Using
  • แก้ไขข้อผิดพลาดที่finallyจะกลืนต้นฉบับExceptionถูกโยนโดยtryถ้าfinallyรหัสโยนException

หลังจากตรวจสอบคำตอบทั้งหมดเหล่านี้เกี่ยวกับวิธีการเขียนไฟล์ใน Scala อย่างง่ายดายและบางส่วนของพวกเขาค่อนข้างดีฉันมีสามประเด็น:

  1. ในคำตอบของ Jus12การใช้การแกงสำหรับวิธีใช้ตัวช่วยไม่ชัดเจนสำหรับผู้เริ่มต้น Scala / FP
  2. ความต้องการในการหุ้มข้อผิดพลาดระดับล่างด้วย scala.util.Try
  3. ความต้องการที่จะแสดงให้นักพัฒนา Java ใหม่เพื่อ Scala / FP วิธีการอย่างถูกรังขึ้นอยู่กับทรัพยากรดังนั้นcloseวิธีการที่จะดำเนินการในแต่ละทรัพยากรขึ้นอยู่ในลำดับที่กลับ - หมายเหตุ:ปิดทรัพยากรขึ้นอยู่ในลำดับที่กลับโดยเฉพาะอย่างยิ่งในกรณีของความล้มเหลวเป็นความต้องการที่ไม่ค่อยเข้าใจของjava.lang.AutoCloseableเปคซึ่งมีแนวโน้มที่จะนำไปสู่อันตรายมากและยากที่จะหาข้อบกพร่องและความล้มเหลวในเวลาทำงาน

ก่อนที่จะเริ่มเป้าหมายของฉันไม่รัดกุม มันจะช่วยให้เข้าใจง่ายขึ้นสำหรับผู้เริ่มต้น Scala / FP โดยทั่วไปเป็นผู้ที่มาจาก Java ในตอนท้ายฉันจะดึงบิตทั้งหมดเข้าด้วยกันแล้วเพิ่มความกระชับ

ขั้นแรกusingต้องอัปเดตวิธีใช้Try(อีกครั้งความรัดกุมไม่ใช่เป้าหมายที่นี่) มันจะถูกเปลี่ยนชื่อเป็นtryUsingAutoCloseable:

def tryUsingAutoCloseable[A <: AutoCloseable, R]
  (instantiateAutoCloseable: () => A) //parameter list 1
  (transfer: A => scala.util.Try[R])  //parameter list 2
: scala.util.Try[R] =
  Try(instantiateAutoCloseable())
    .flatMap(
      autoCloseable => {
        var optionExceptionTry: Option[Exception] = None
        try
          transfer(autoCloseable)
        catch {
          case exceptionTry: Exception =>
            optionExceptionTry = Some(exceptionTry)
            throw exceptionTry
        }
        finally
          try
            autoCloseable.close()
          catch {
            case exceptionFinally: Exception =>
              optionExceptionTry match {
                case Some(exceptionTry) =>
                  exceptionTry.addSuppressed(exceptionFinally)
                case None =>
                  throw exceptionFinally
              }
          }
      }
    )

การเริ่มต้นของtryUsingAutoCloseableวิธีการด้านบนอาจทำให้เกิดความสับสนเนื่องจากดูเหมือนจะมีสองรายการพารามิเตอร์แทนรายการพารามิเตอร์แบบกำหนด สิ่งนี้เรียกว่าการแกง และฉันจะไม่อธิบายรายละเอียดเกี่ยวกับการทำงานของแกงกะหรี่หรือเป็นประโยชน์ในบางครั้ง ปรากฎว่าพื้นที่ที่มีปัญหานี้เป็นเครื่องมือที่เหมาะสมสำหรับงาน

ต่อไปเราต้องสร้างวิธีการtryPrintToFileที่จะสร้าง (หรือเขียนทับที่มีอยู่) และการเขียนFile List[String]มันใช้FileWriterซึ่งจะห่อหุ้มด้วยซึ่งเป็นในทางกลับกันห่อหุ้มด้วยBufferedWriter PrintWriterและเพื่อยกระดับประสิทธิภาพขนาดบัฟเฟอร์เริ่มต้นมีขนาดใหญ่กว่าค่าเริ่มต้นที่BufferedWriterกำหนดไว้defaultBufferSizeมากและกำหนดค่า 65536

นี่คือรหัส (และอีกครั้งความกระชับไม่ใช่เป้าหมายที่นี่):

val defaultBufferSize: Int = 65536

def tryPrintToFile(
  lines: List[String],
  location: java.io.File,
  bufferSize: Int = defaultBufferSize
): scala.util.Try[Unit] = {
  tryUsingAutoCloseable(() => new java.io.FileWriter(location)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
    fileWriter =>
      tryUsingAutoCloseable(() => new java.io.BufferedWriter(fileWriter, bufferSize)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
        bufferedWriter =>
          tryUsingAutoCloseable(() => new java.io.PrintWriter(bufferedWriter)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
            printWriter =>
              scala.util.Try(
                lines.foreach(line => printWriter.println(line))
              )
          }
      }
  }
}

ข้างต้นtryPrintToFileเป็นวิธีที่มีประโยชน์ในการที่จะใช้เป็นข้อมูลและส่งไปยังList[String] Fileตอนนี้ขอสร้างtryWriteToFileวิธีการที่จะใช้เวลาและเขียนไปยังStringFile

นี่คือรหัส (และฉันจะให้คุณเดาความสำคัญของความรัดกุมที่นี่):

def tryWriteToFile(
  content: String,
  location: java.io.File,
  bufferSize: Int = defaultBufferSize
): scala.util.Try[Unit] = {
  tryUsingAutoCloseable(() => new java.io.FileWriter(location)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
    fileWriter =>
      tryUsingAutoCloseable(() => new java.io.BufferedWriter(fileWriter, bufferSize)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
        bufferedWriter =>
          Try(bufferedWriter.write(content))
      }
  }
}

ในที่สุดก็เป็นประโยชน์เพื่อให้สามารถดึงข้อมูลเนื้อหาของการเป็นFile Stringในขณะที่scala.io.Sourceจัดให้มีวิธีการที่สะดวกสบายสำหรับการได้รับเนื้อหาของ a อย่างง่ายดายFileแต่closeต้องใช้วิธีการSourceเพื่อปล่อย JVM และระบบไฟล์ที่เกี่ยวข้อง หากสิ่งนี้ยังไม่เสร็จสิ้นทรัพยากรจะไม่ถูกปล่อยออกมาจนกว่า JVM GC (Garbage Collector) จะได้รับการปล่อยSourceอินสแตนซ์เอง และถึงแม้จะมีเพียง JVM ที่อ่อนแอเท่านั้นที่รับประกันfinalizeว่า GC จะถูกเรียกใช้กับcloseทรัพยากร ซึ่งหมายความว่ามันเป็นความรับผิดชอบของลูกค้าที่จะเรียกcloseวิธีการอย่างชัดเจนเช่นเดียวกับที่เป็นความรับผิดชอบของลูกค้าที่จะสูงcloseในกรณีของjava.lang.AutoCloseable. scala.io.Sourceสำหรับเรื่องนี้เราต้องมีความหมายที่สองของการใช้วิธีการที่จับ

นี่คือรหัสสำหรับสิ่งนี้ (ยังไม่กระชับ):

def tryUsingSource[S <: scala.io.Source, R]
  (instantiateSource: () => S)
  (transfer: S => scala.util.Try[R])
: scala.util.Try[R] =
  Try(instantiateSource())
    .flatMap(
      source => {
        var optionExceptionTry: Option[Exception] = None
        try
          transfer(source)
        catch {
          case exceptionTry: Exception =>
            optionExceptionTry = Some(exceptionTry)
            throw exceptionTry
        }
        finally
          try
            source.close()
          catch {
            case exceptionFinally: Exception =>
              optionExceptionTry match {
                case Some(exceptionTry) =>
                  exceptionTry.addSuppressed(exceptionFinally)
                case None =>
                  throw exceptionFinally
              }
          }
      }
    )

และนี่คือตัวอย่างการใช้งานในตัวอ่านไฟล์สตรีมมิ่งบรรทัดแบบง่าย ๆ (ปัจจุบันใช้เพื่ออ่านไฟล์ที่คั่นด้วยแท็บจากเอาต์พุตฐานข้อมูล):

def tryProcessSource(
    file: java.io.File
  , parseLine: (String, Int) => List[String] = (line, index) => List(line)
  , filterLine: (List[String], Int) => Boolean = (values, index) => true
  , retainValues: (List[String], Int) => List[String] = (values, index) => values
  , isFirstLineNotHeader: Boolean = false
): scala.util.Try[List[List[String]]] =
  tryUsingSource(scala.io.Source.fromFile(file)) {
    source =>
      scala.util.Try(
        ( for {
            (line, index) <-
              source.getLines().buffered.zipWithIndex
            values =
              parseLine(line, index)
            if (index == 0 && !isFirstLineNotHeader) || filterLine(values, index)
            retainedValues =
              retainValues(values, index)
          } yield retainedValues
        ).toList //must explicitly use toList due to the source.close which will
                 //occur immediately following execution of this anonymous function
      )
  )

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


ตอนนี้การนำสิ่งนั้นมารวมกับการนำเข้าที่แยกแล้ว (ทำให้ง่ายต่อการวางลงในแผ่นงาน Scala ที่มีอยู่ในทั้ง Eclipse ScalaIDE และปลั๊กอิน IntelliJ Scala เพื่อให้ง่ายต่อการถ่ายโอนข้อมูลเอาต์พุตไปยังเดสก์ท็อป นี่คือโค้ดที่ดูเหมือน (เพิ่มความกระชับ):

import scala.io.Source
import scala.util.Try
import java.io.{BufferedWriter, FileWriter, File, PrintWriter}

val defaultBufferSize: Int = 65536

def tryUsingAutoCloseable[A <: AutoCloseable, R]
  (instantiateAutoCloseable: () => A) //parameter list 1
  (transfer: A => scala.util.Try[R])  //parameter list 2
: scala.util.Try[R] =
  Try(instantiateAutoCloseable())
    .flatMap(
      autoCloseable => {
        var optionExceptionTry: Option[Exception] = None
        try
          transfer(autoCloseable)
        catch {
          case exceptionTry: Exception =>
            optionExceptionTry = Some(exceptionTry)
            throw exceptionTry
        }
        finally
          try
            autoCloseable.close()
          catch {
            case exceptionFinally: Exception =>
              optionExceptionTry match {
                case Some(exceptionTry) =>
                  exceptionTry.addSuppressed(exceptionFinally)
                case None =>
                  throw exceptionFinally
              }
          }
      }
    )

def tryUsingSource[S <: scala.io.Source, R]
  (instantiateSource: () => S)
  (transfer: S => scala.util.Try[R])
: scala.util.Try[R] =
  Try(instantiateSource())
    .flatMap(
      source => {
        var optionExceptionTry: Option[Exception] = None
        try
          transfer(source)
        catch {
          case exceptionTry: Exception =>
            optionExceptionTry = Some(exceptionTry)
            throw exceptionTry
        }
        finally
          try
            source.close()
          catch {
            case exceptionFinally: Exception =>
              optionExceptionTry match {
                case Some(exceptionTry) =>
                  exceptionTry.addSuppressed(exceptionFinally)
                case None =>
                  throw exceptionFinally
              }
          }
      }
    )

def tryPrintToFile(
  lines: List[String],
  location: File,
  bufferSize: Int = defaultBufferSize
): Try[Unit] =
  tryUsingAutoCloseable(() => new FileWriter(location)) { fileWriter =>
    tryUsingAutoCloseable(() => new BufferedWriter(fileWriter, bufferSize)) { bufferedWriter =>
      tryUsingAutoCloseable(() => new PrintWriter(bufferedWriter)) { printWriter =>
          Try(lines.foreach(line => printWriter.println(line)))
      }
    }
  }

def tryWriteToFile(
  content: String,
  location: File,
  bufferSize: Int = defaultBufferSize
): Try[Unit] =
  tryUsingAutoCloseable(() => new FileWriter(location)) { fileWriter =>
    tryUsingAutoCloseable(() => new BufferedWriter(fileWriter, bufferSize)) { bufferedWriter =>
      Try(bufferedWriter.write(content))
    }
  }

def tryProcessSource(
    file: File,
  parseLine: (String, Int) => List[String] = (line, index) => List(line),
  filterLine: (List[String], Int) => Boolean = (values, index) => true,
  retainValues: (List[String], Int) => List[String] = (values, index) => values,
  isFirstLineNotHeader: Boolean = false
): Try[List[List[String]]] =
  tryUsingSource(() => Source.fromFile(file)) { source =>
    Try(
      ( for {
          (line, index) <- source.getLines().buffered.zipWithIndex
          values = parseLine(line, index)
          if (index == 0 && !isFirstLineNotHeader) || filterLine(values, index)
          retainedValues = retainValues(values, index)
        } yield retainedValues
      ).toList
    )
  }

ในฐานะที่เป็นมือใหม่ของ Scala / FP ฉันได้เผาผลาญไปหลายชั่วโมง ฉันหวังว่านี่จะช่วยมือใหม่ Scala / FP คนอื่น ๆ ให้เรียนรู้เรื่องนี้ได้เร็วขึ้น


2
การปรับปรุงอย่างไม่น่าเชื่อ ปัญหาเดียวคือตอนนี้คุณมีโค้ดเหมือน 100 บรรทัดซึ่งสามารถแทนที่try-catch-finallyได้ ยังคงรักความรักของคุณ
สังเกตการณ์

1
@Overserver ฉันจะยืนยันว่าเป็นคำสั่งที่ไม่ถูกต้อง รูปแบบที่ฉันกำลังอธิบายคือการลดจำนวนของต้นแบบที่ลูกค้าต้องเขียนเพื่อให้แน่ใจว่าการจัดการการปิด AutoCloseables ที่เหมาะสมในขณะที่เปิดใช้งานรูปแบบ Scala idiomatic FP ของการใช้ scala.util.Try หากคุณพยายามทำเอฟเฟ็กต์แบบเดียวกันกับฉันโดยการเขียนบล็อกลอง / จับ / ในที่สุดด้วยตนเองฉันคิดว่าคุณจะพบว่าท้ายที่สุดก็มีส่วนต่อเติมมากกว่าที่คุณจินตนาการเอาไว้ ดังนั้นจึงมีค่าความสามารถในการอ่านที่สำคัญในการผลักแผ่นความร้อนทั้งหมดเข้าไปในฟังก์ชัน Scala 100 บรรทัด
chaotic3quilibrium

1
ขออภัยถ้าฟังดูไม่เหมาะสม แต่อย่างใด ถึงกระนั้นประเด็นของฉันก็คือว่าไม่จำเป็นต้องใช้โค้ดในจำนวนนี้เพราะสามารถทำได้โดยใช้วิธีที่ไม่ใช้งานและมีความเรียบง่ายมากกว่า โดยส่วนตัวแล้วฉันจะเขียนลองในที่สุดด้วยการตรวจสอบเพิ่มเติมบางอย่าง มันสั้นกว่านี้ ถ้าฉันต้องการใช้ wrappers, ApacheUtils อยู่ที่นั่นเพื่อใช้งานสกปรกทั้งหมด ยิ่งไปกว่านั้นเครื่องอ่าน / เขียนมาตรฐานทั้งหมดจะปิดสตรีมพื้นฐานเพื่อไม่จำเป็นต้องใช้มัลติแวป PS: ฉันเปลี่ยนคะแนนจากลบหนึ่งเป็นบวกหนึ่งเพื่อสนับสนุนความพยายามของคุณ ดังนั้นโปรดอย่าสงสัยฉันด้วยความตั้งใจที่ไม่ดี
สังเกตการณ์

ไม่มีการกระทำความผิด
chaotic3quilibrium

1
ผมเข้าใจมุมมองของคุณ. ขอบคุณสำหรับการอภิปรายฉันต้องคิดเกี่ยวกับมันเล็กน้อย ขอให้มีความสุขมาก ๆ ในวันนี้!
สังเกตการณ์

3

นี่คือตัวอย่างของการเขียนเส้นบางไปยังแฟ้มใช้scalaz สตรีม

import scalaz._
import scalaz.stream._

def writeLinesToFile(lines: Seq[String], file: String): Task[Unit] =
  Process(lines: _*)              // Process that enumerates the lines
    .flatMap(Process(_, "\n"))    // Add a newline after each line
    .pipe(text.utf8Encode)        // Encode as UTF-8
    .to(io.fileChunkW(fileName))  // Buffered write to the file
    .runLog[Task, Unit]           // Get this computation as a Task
    .map(_ => ())                 // Discard the result

writeLinesToFile(Seq("one", "two"), "file.txt").run

1

เพื่อแซงหน้าผู้มีชื่อเสียงและผู้มีส่วนร่วมต่อหน้าฉันฉันได้ปรับปรุงการตั้งชื่อและความรัดกุม:

  def using[A <: {def close() : Unit}, B](resource: A)(f: A => B): B =
    try f(resource) finally resource.close()

  def writeStringToFile(file: File, data: String, appending: Boolean = false) =
    using(new FileWriter(file, appending))(_.write(data))

สิ่งนี้ใช้ "การพิมพ์เป็ด" ซึ่งขึ้นอยู่กับการสะท้อน สำหรับบริบทหลายอย่างขึ้นอยู่กับการสะท้อนเป็น non-starter
chaotic3quilibrium

1

ไม่มีการอ้างอิงพร้อมการจัดการข้อผิดพลาด

  • ใช้วิธีการจากไลบรารีมาตรฐานโดยเฉพาะ
  • สร้างไดเร็กทอรีสำหรับไฟล์หากจำเป็น
  • ใช้Eitherสำหรับการจัดการข้อผิดพลาด

รหัส

def write(destinationFile: Path, fileContent: String): Either[Exception, Path] =
  write(destinationFile, fileContent.getBytes(StandardCharsets.UTF_8))

def write(destinationFile: Path, fileContent: Array[Byte]): Either[Exception, Path] =
  try {
    Files.createDirectories(destinationFile.getParent)
    // Return the path to the destinationFile if the write is successful
    Right(Files.write(destinationFile, fileContent))
  } catch {
    case exception: Exception => Left(exception)
  }

การใช้

val filePath = Paths.get("./testDir/file.txt")

write(filePath , "A test") match {
  case Right(pathToWrittenFile) => println(s"Successfully wrote to $pathToWrittenFile")
  case Left(exception) => println(s"Could not write to $filePath. Exception: $exception")
}

1

อัพเดต 2019:

ข้อมูลสรุป - Java NIO (หรือ NIO.2 สำหรับ async) ยังคงเป็นโซลูชันการประมวลผลไฟล์ที่ครอบคลุมที่สุดที่รองรับใน Scala รหัสต่อไปนี้สร้างและเขียนข้อความไปยังไฟล์ใหม่:

import java.io.{BufferedOutputStream, OutputStream}
import java.nio.file.{Files, Paths}

val testFile1 = Paths.get("yourNewFile.txt")
val s1 = "text to insert in file".getBytes()

val out1: OutputStream = new BufferedOutputStream(
  Files.newOutputStream(testFile1))

try {
  out1.write(s1, 0, s1.length)
} catch {
  case _ => println("Exception thrown during file writing")
} finally {
  out1.close()
}
  1. อิมพอร์ตไลบรารี Java: IO และ NIO
  2. สร้างPathวัตถุด้วยชื่อไฟล์ที่คุณเลือก
  3. แปลงข้อความของคุณที่คุณต้องการแทรกลงในไฟล์เป็นอาร์เรย์ไบต์
  4. รับไฟล์ของคุณเป็นสตรีม: OutputStream
  5. ส่งผ่านอาร์เรย์ไบต์ของคุณไปยังwriteฟังก์ชันเอาต์พุตสตรีม
  6. ปิดกระแส

1

คล้ายกับคำตอบนี้นี่คือตัวอย่างของfs2(เวอร์ชั่น 1.0.4):

import cats.effect._

import fs2._
import fs2.io

import java.nio.file._

import scala.concurrent.ExecutionContext
import scala.language.higherKinds
import cats.syntax.functor._

object ScalaApp extends IOApp {

  def write[T[_]](p: Path, s: String)
                 (implicit F: ConcurrentEffect[T], cs: ContextShift[T]): T[Unit] = {
    Stream(s)
      .covary[T]
      .through(text.utf8Encode)
      .through(
        io.file.writeAll(
          p,
          scala.concurrent.ExecutionContext.global,
          Seq(StandardOpenOption.CREATE)
        )
      )
      .compile
      .drain
  }


  def run(args: List[String]): IO[ExitCode] = {

    implicit val executionContext: ExecutionContext =
      scala.concurrent.ExecutionContext.Implicits.global

    implicit val contextShift: ContextShift[IO] =
      IO.contextShift(executionContext)

    val outputFile: Path = Paths.get("output.txt")

    write[IO](outputFile, "Hello world\n").as(ExitCode.Success)

  }
}

0

บรรทัดนี้ช่วยในการเขียนไฟล์จาก Array หรือ String

 new PrintWriter(outputPath) { write(ArrayName.mkString("")); close }

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