Làm thế nào để ghi vào một tập tin trong Scala?


157

Đối với việc đọc, có sự trừu tượng hữu ích Source. Làm thế nào tôi có thể viết các dòng vào một tập tin văn bản?


1
Nếu bạn biết cách làm như vậy trong Java thì bạn có thể sử dụng tương tự trong Scala. Là câu hỏi của bạn cụ thể với thư viện tiêu chuẩn Scala?
Wheaties

1
@wheaties có cách tốt nhất để làm điều này trong scala
yura

Thư viện này thực sự tốt: github.com/pathikrit/better-files
Robin

Thư viện OS-Lib của
Lihaoyi

Câu trả lời:


71

Chỉnh sửa 2019 (8 năm sau), Scala-IO không hoạt động nhiều, nếu có, Li Haoyi gợi ý thư viện của riêng mình lihaoyi/os-lib, mà anh trình bày dưới đây .

Tháng 6 năm 2019, Xavier Guihot đề cập đến câu trả lời của ông về thư viện Using, một tiện ích để thực hiện quản lý tài nguyên tự động.


Chỉnh sửa (tháng 9 năm 2011): kể từ khi Eduardo Costa hỏi về Scala2.9, và vì Rick-777 nhận xét rằng scalax.IO cam kết lịch sử không tồn tại nhiều kể từ giữa năm 2009 ...

Scala-IO đã thay đổi địa điểm: xem repo GitHub của nó, từ Jesse Eichar (cũng trên SO ):

Dự án ô Scala IO bao gồm một vài dự án phụ cho các khía cạnh và phần mở rộng khác nhau của IO.
Có hai thành phần chính của Scala IO:

  • Core - Core chủ yếu liên quan đến việc đọc và ghi dữ liệu đến và từ các nguồn và mức tùy ý. Các đặc điểm đá góc là Input, OutputSeekablecung cấp các API lõi.
    Các lớp quan trọng khác là Resource, ReadCharsWriteChars.
  • Tệp - Tệp là API File(được gọi Path) dựa trên sự kết hợp của hệ thống tệp NIO Java 7 và API SBT Path Downloader.
    PathFileSystemlà các điểm nhập chính vào API tệp Scala IO.
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)

Câu trả lời gốc (tháng 1 năm 2011), với vị trí cũ cho scala-io:

Nếu bạn không muốn đợi Scala2.9, bạn có thể sử dụng thư viện scala-incubator / scala-io .
(như đã đề cập trong " Tại sao Nguồn Scala không đóng InputStream bên dưới? ")

Xem các mẫu

{ // 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
Còn phiên bản Scala 2.9 thì sao? :)
Eduardo Costa

Dự án scalax dường như đã chết (không có cam kết kể từ tháng 6 năm 2009). Thê nay đung không? lịch sử cam kết của scalax
Rick-777

@Eduardo: Tôi đã hoàn thành câu trả lời của mình với vị trí mới cho thư viện scala-io (đã được cập nhật cho Scala2.9: github.com/jesseeichar/scala-io/issues/20 )
VonC

10
Đây thực sự là gợi ý hiện tại cho Scala 2.10? Sử dụng Scala IO? Vẫn chưa có gì trong Scala cốt lõi?
Phil

2
Tôi chưa bao giờ sử dụng scalax.io, nhưng đánh giá từ các dòng ví dụ này, có vẻ như thiết kế API của nó khá tệ. Việc trộn các phương thức cho dữ liệu ký tự và nhị phân trong một giao diện có ý nghĩa rất nhỏ và rất có thể sẽ dẫn đến các lỗi mã hóa khó tìm. Thiết kế của java.io (Reader / Writer so với InputStream / OutputStream) có vẻ tốt hơn nhiều.
jcsahnwaldt Phục hồi lại

211

Đây là một trong những tính năng còn thiếu từ Scala tiêu chuẩn mà tôi thấy hữu ích đến mức tôi thêm nó vào thư viện cá nhân của mình. (Có lẽ bạn cũng nên có một thư viện cá nhân.) Đoạn mã giống như vậy:

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

và nó được sử dụng như thế này:

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 () mới sử dụng mã hóa mặc định của nền tảng, điều này có thể có nghĩa là tệp kết quả không dễ mang theo. Ví dụ: nếu bạn muốn tạo một tệp mà sau này bạn có thể gửi qua email, có lẽ bạn nên sử dụng hàm tạo PrintWriter cho phép bạn chỉ định mã hóa.
jcsahnwaldt phục hồi lại

@JonaChristopherSahnwaldt - Chắc chắn, trong trường hợp đặc biệt bạn có thể muốn chỉ định mã hóa. Mặc định cho nền tảng là trung bình mặc định hợp lý nhất. Tương tự như với Source(mã hóa mặc định theo mặc định). Tất nhiên bạn có thể thêm ví dụ như một enc: Option[String] = Nonetham số sau fnếu bạn thấy đây là một nhu cầu chung.
Rex Kerr

6
@RexKerr - Tôi không đồng ý. Người ta phải chỉ định mã hóa trong hầu hết các trường hợp. Hầu hết các lỗi mã hóa tôi gặp phải xảy ra do mọi người không hiểu hoặc không nghĩ về mã hóa. Họ sử dụng mặc định và thậm chí không biết điều đó vì quá nhiều API cho phép họ thoát khỏi nó. Ngày nay, mặc định hợp lý nhất có lẽ sẽ là UTF-8. Có lẽ bạn chỉ làm việc với tiếng Anh và các ngôn ngữ khác có thể được viết bằng ASCII. Bạn thật may mắn. Tôi sống ở Đức và phải sửa nhiều ô bị hỏng hơn tôi nhớ.
jcsahnwaldt phục hồi lại

3
@JonaChristopherSahnwaldt - Đây là một lý do để có một mã hóa mặc định hợp lý, không bắt buộc mọi người phải chỉ định nó mọi lúc. Nhưng nếu bạn đang sử dụng máy Mac và các tệp của bạn được viết bởi Java bị gobbledygook vì chúng không được mã hóa Mac OS Roman, tôi không chắc nó sẽ tốt hơn hại. Tôi nghĩ đó là lỗi của các nền tảng mà họ đã không đồng ý trên một bảng mã. Là một nhà phát triển cá nhân, gõ vào một chuỗi thực sự sẽ không giải quyết được vấn đề. (Tất cả các nhà phát triển đồng ý với UTF-8 sẽ, nhưng sau đó điều đó chỉ có thể đi vào mặc định.)
Rex Kerr

@JonaChristopherSahnwaldt +10 để sửa tất cả các ô bị hỏng. Không thể sử dụng búa, có thể là một cú đấm lỗ? Hoặc họ đã có những lỗ hổng cần lấp đầy, có lẽ anh chàng này có thể giúp youtube.com/watch?v=E-eBBzwepwE Nhưng nghiêm túc, ảnh hưởng của ASCII rất nguy hiểm trên thế giới, đồng ý rằng nó nên được chỉ định và mặc định là UTF- 8
Davos

50

Tương tự như câu trả lời của Rex Kerr, nhưng chung chung hơn. Đầu tiên tôi sử dụng chức năng trợ giúp:

/**
 * 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() }

Sau đó, tôi sử dụng như:

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

Vân vân.


39
Đừng hiểu sai ý tôi, tôi thích mã của bạn và nó rất giáo dục, nhưng tôi càng thấy các cấu trúc như vậy cho các vấn đề đơn giản, nó càng nhắc tôi về trò đùa "xin chào thế giới" cũ: ariel.com.au/jokes/The_Evolution_of_a_Programmer .html :-) (+1 phiếu bầu từ tôi).
greenoldman

4
Nếu bạn đang viết một lớp lót, không có vấn đề gì cả. Nếu bạn đang viết các chương trình quan trọng (lớn với nhu cầu bảo trì và phát triển liên tục), kiểu suy nghĩ này dẫn đến sự suy giảm chất lượng phần mềm nhanh chóng và nguy hiểm nhất.
Randall Schulz

3
Không phải ai cũng sẽ có "đôi mắt scala" cho đến một mức độ thực hành nào đó - thật buồn cười khi thấy ví dụ mã này đến từ "Bắt đầu" Scala
asyncwait

asyncwait "bắt đầu" scala ... tiêu đề mỉa mai nhất từ ​​trước đến nay, lưu ý: Tôi đã có cuốn sách ... và bây giờ tôi bắt đầu hiểu nó..Tôi giả sử tôi là một bước trước khi "người mới bắt đầu" lol: D ........
dùng1050817

1
Vấn đề là các thủ thuật Scala ở đây ít hơn, nhưng tính dài dòng và phong cách kém. Tôi đã chỉnh sửa nó để dễ đọc hơn nhiều. Sau bộ tái cấu trúc của tôi, nó chỉ có 4 dòng (tốt, 4 với độ dài dòng IDE, được sử dụng 6 ở đây để vừa với màn hình). IMHO bây giờ là câu trả lời rất hay.
samthebest

38

Một câu trả lời đơn giản:

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 bạn có thể thêm các thư viện bạn importtừ?
Daniel

1
Kể từ java 7, hãy sử dụng java.nio.file thay vào đó: def writeToFile (file: String, stringToWrite: String): Unit = {val wr = Files.newBufferedWriter (Paths.get (file)) cuối cùng hãy thử wr.write (stringToWrite) nhà văn.close ()}
E Shindler

20

Đưa ra một câu trả lời khác, bởi vì những chỉnh sửa của tôi về những câu trả lời khác bị từ chối.

Đây là câu trả lời ngắn gọn và đơn giản nhất (tương tự như Garret Hall)

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

Điều này tương tự với Jus12, nhưng không có độ dài và với kiểu mã chính xác

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

Lưu ý rằng bạn KHÔNG cần các dấu ngoặc nhọn cho try finally, cũng như lambdas và lưu ý sử dụng cú pháp giữ chỗ. Cũng lưu ý đặt tên tốt hơn.


2
Xin lỗi, nhưng mã của bạn có thể tưởng tượng được, nó không đáp ứng implementedđiều kiện tiên quyết. Bạn không thể sử dụng mã không được thực hiện. Tôi có nghĩa là bạn phải cho biết làm thế nào để tìm thấy nó vì nó không có sẵn theo mặc định và không nổi tiếng.
Val

15

Dưới đây là một lớp lót ngắn gọn bằng thư viện trình biên dịch Scala:

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

Ngoài ra, nếu bạn muốn sử dụng các thư viện Java, bạn có thể thực hiện việc hack này:

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

Nhập khẩu gì? tức là File đến từ đâu?
Ben Hutchison

Thư viện trình biên dịch Scala.
Hội trường Garrett

3
Không còn khả thi (không có trong Scala 2.11)
Brent Faust

1
Bạn đang nói về cái gì vậy? scala.tools.nsc.io.File("/tmp/myFile.txt")hoạt động trong Scala 2.11.8.

1
Bây giờ là trong scala.reflect.io.File
Keith Nordstrom

13

Một lớp lót để lưu / đọc đến / từ String, sử dụng java.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
}

Điều này không phù hợp với các tệp lớn, nhưng sẽ thực hiện công việc.

Một số liên kết:

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


Tại sao điều này không phù hợp với các tệp lớn?
Chetan Bhasin

2
@ChetanBhasin Có lẽ bởi vì writesẽ sao chép contentsvào một mảng byte mới thay vì truyền nó vào tệp, do đó, ở mức cao nhất của nó sử dụng bộ nhớ nhiều gấp đôi so với contentsmột mình.
Daniel Werner

10

Thật không may cho câu trả lời hàng đầu, Scala-IO đã chết. Nếu bạn không phiền khi sử dụng phụ thuộc của bên thứ ba, hãy xem xét sử dụng thư viện OS-Lib của tôi . Điều này làm cho việc làm việc với các tệp, đường dẫn và hệ thống tệp rất dễ dàng:

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

Nó có một lớp để ghi vào tập tin , nối thêm vào tệp , ghi đè tệp và nhiều hoạt động hữu ích / phổ biến khác


Cảm ơn bạn đã cập nhật này. Nâng cao. Tôi đã tham khảo câu trả lời của bạn ở trên của tôi ở trên để nhìn rõ hơn.
VonC

7

Một thư viện vi mô tôi đã viết: https://github.com/pathikrit/better-files

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

hoặc là

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

Cũng ở đây - Câu hỏi này là một trong những câu hỏi hàng đầu khi tìm cách viết một tệp bằng scala - bây giờ dự án của bạn đã lớn hơn, bạn có thể muốn mở rộng câu trả lời của mình một chút không?
Asac

6

Bắt đầu Scala 2.13, thư viện chuẩn cung cấp tiện ích quản lý tài nguyên chuyên dụng : Using.

Nó có thể được sử dụng trong trường hợp này với các tài nguyên như PrintWriterhoặc BufferedWritermở rộng AutoCloseableđể ghi vào một tệp và, bất kể là gì, hãy đóng tài nguyên sau đó:

  • Chẳng hạn, với 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)
    }
  • Hoặc với 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

CẬP NHẬT vào năm 2019 / tháng 9/01:

  • Bắt đầu với Scala 2.13, thích sử dụng scala.util.Using
  • Cố định lỗi mà finallysẽ nuốt gốc Exceptionném bởi trynếu finallyđang ném mộtException

Sau khi xem xét tất cả các câu trả lời về cách dễ dàng viết một tệp trong Scala và một số trong số chúng khá hay, tôi có ba vấn đề:

  1. Trong câu trả lời của Jus12 , việc sử dụng currying cho phương thức sử dụng helper là không rõ ràng đối với người mới bắt đầu Scala / FP
  2. Cần đóng gói các lỗi cấp thấp hơn với scala.util.Try
  3. Nhu cầu để hiển thị các nhà phát triển Java mới để Scala / FP làm thế nào để đúng cách tổ phụ thuộc nguồn lực để các closephương pháp được thực hiện trên mỗi tài nguyên phụ thuộc vào thứ tự ngược lại - Lưu ý: đóng nguồn lực phụ thuộc vào thứ tự ngược lại đặc biệt là trong trường hợp mất là một yêu cầu hiếm khi hiểu của các java.lang.AutoCloseableđặc điểm kỹ thuật mà có xu hướng dẫn đến rất nguy hại và khó khăn để tìm lỗi và thất bại thời gian chạy

Trước khi bắt đầu, mục tiêu của tôi không phải là sự đồng nhất. Đó là để tạo điều kiện dễ hiểu hơn cho người mới bắt đầu Scala / FP, điển hình là những người đến từ Java. Cuối cùng, tôi sẽ kéo tất cả các bit lại với nhau, và sau đó tăng độ đồng nhất.

Đầu tiên, usingphương pháp cần được cập nhật để sử dụng Try(một lần nữa, tính đồng nhất không phải là mục tiêu ở đây). Nó sẽ được đổi tên thành 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
              }
          }
      }
    )

Sự khởi đầu của tryUsingAutoCloseablephương thức trên có thể gây nhầm lẫn bởi vì nó dường như có hai danh sách tham số thay vì danh sách tham số duy nhất tùy chỉnh. Điều này được gọi là cà ri. Và tôi sẽ không đi vào chi tiết cách thức hoạt động của cà ri hoặc nơi đôi khi nó hữu ích. Nó chỉ ra rằng đối với không gian vấn đề cụ thể này, nó là công cụ phù hợp cho công việc.

Tiếp theo, chúng ta cần tạo phương thức, tryPrintToFilesẽ tạo một (hoặc ghi đè lên một cái hiện có) Filevà viết a List[String]. Nó sử dụng một FileWritercái được gói gọn bởi cái BufferedWritermà lần lượt được gói gọn bởi a PrintWriter. Và để nâng cao hiệu suất, kích thước bộ đệm mặc định lớn hơn nhiều so với mặc định BufferedWriterđược xác định,defaultBufferSize và được gán giá trị 65536.

Đây là mã (và một lần nữa, sự đồng nhất không phải là mục tiêu ở đây):

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

tryPrintToFilePhương thức trên hữu ích ở chỗ nó lấy một List[String]đầu vào và gửi nó đến a File. Bây giờ chúng ta hãy tạo một tryWriteToFilephương thức lấy Stringvà ghi nó vàoFile .

Đây là mã (và tôi sẽ cho phép bạn đoán mức độ ưu tiên của sự đồng nhất ở đây):

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

Cuối cùng, rất hữu ích khi có thể tìm nạp nội dung của a Filedưới dạng a String. Mặc dù scala.io.Sourcecung cấp một phương thức thuận tiện để dễ dàng lấy nội dung của a File, nhưng closephương thức này phải được sử dụng trên Sourceđể giải phóng JVM cơ bản và xử lý hệ thống tệp. Nếu điều này không được thực hiện, thì tài nguyên sẽ không được giải phóng cho đến khi JVM GC (Garbage Collector) có mặt để phát hành Sourcecá thể. Và thậm chí sau đó, chỉ có một JVM yếu đảm bảo finalizephương thức sẽ được gọi bởi GC tới closetài nguyên. Điều này có nghĩa là trách nhiệm của khách hàng là gọi một cách rõ ràngclose phương thức , giống như trách nhiệm của khách hàng đối với người caoclose trong trường hợpjava.lang.AutoCloseable. Đối với điều này, chúng ta cần một định nghĩa thứ hai về phương thức sử dụng xử lý scala.io.Source.

Đây là mã cho việc này (vẫn chưa được súc tích):

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

Và đây là một ví dụ sử dụng nó trong một trình đọc tệp phát trực tuyến siêu đơn giản (hiện đang sử dụng để đọc các tệp được phân định bằng tab từ đầu ra cơ sở dữ liệu):

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

Một phiên bản cập nhật của chức năng trên đã được cung cấp dưới dạng câu trả lời cho câu hỏi StackOverflow khác nhưng có liên quan .


Bây giờ, kết hợp tất cả cùng với các bản nhập được trích xuất (giúp dễ dàng dán vào Bảng tính Scala hơn trong cả plugin Eclipse ScalaIDE và IntelliJ Scala để dễ dàng chuyển đầu ra sang máy tính để bàn dễ dàng kiểm tra hơn bằng trình soạn thảo văn bản), đây là mã trông như thế nào (với độ đồng nhất tăng):

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

Là một người mới chơi Scala / FP, tôi đã đốt cháy nhiều giờ (trong sự thất vọng chủ yếu là đau đầu) để kiếm được kiến ​​thức và giải pháp trên. Tôi hy vọng điều này sẽ giúp những người mới Scala / FP khác vượt qua được khối học tập đặc biệt này nhanh hơn.


2
Cập nhật đáng kinh ngạc. Vấn đề duy nhất là bây giờ bạn có 100 dòng mã có thể được thay thế bằng try-catch-finally. Vẫn yêu đam mê của bạn.
Người quan sát

1
@Observer Tôi xin khẳng định đó là một tuyên bố không chính xác. Mẫu mà tôi đang mô tả thực sự là giảm số lượng nồi hơi mà khách hàng phải viết để đảm bảo xử lý đúng cách đóng AutoCloseables đồng thời cho phép mô hình FP thành ngữ Scala sử dụng scala.util.Try. Nếu bạn cố gắng đạt được các hiệu ứng tương tự mà tôi có bằng cách viết thủ công các khối thử / bắt / cuối cùng, tôi nghĩ bạn sẽ thấy bạn kết thúc với một bản tóm tắt khá nhiều so với bạn tưởng tượng. Vì vậy, có giá trị dễ đọc đáng kể trong việc đẩy tất cả các mẫu soạn thảo vào 100 dòng chức năng Scala.
hỗn loạn3quilibrium

1
Xin lỗi nếu điều đó nghe có vẻ xúc phạm trong bất kỳ cách nào. Tuy nhiên, quan điểm của tôi là không cần số lượng mã như vậy, bởi vì điều tương tự có thể đạt được thông qua cách tiếp cận phi chức năng với sự đơn giản hơn nhiều. Cá nhân, tôi sẽ viết thử cuối cùng với một số kiểm tra bổ sung. Nó chỉ ngắn hơn. Nếu tôi muốn sử dụng các hàm bao, ApacheUtils ở đó để sử dụng tất cả các công việc bẩn thỉu. Hơn nữa, tất cả các Trình đọc / Nhà văn tiêu chuẩn thực hiện đóng các luồng cơ bản để không cần đa luồng của bạn. Tái bút: Tôi đã thay đổi phiếu bầu của mình từ trừ một thành cộng một để ủng hộ những nỗ lực của bạn. Vì vậy, xin vui lòng, đừng nghi ngờ tôi trong một ý định xấu.
Người quan sát

Không có sự xúc phạm nào.
hỗn loạn3quilibrium

1
Tôi hiểu quan điểm của bạn. Cảm ơn đã thảo luận, tôi phải suy nghĩ về nó một chút. Chúc một ngày tốt lành!
Người quan sát

3

Đây là một ví dụ về việc viết một số dòng vào một tệp bằng cách sử dụng scalaz-stream .

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

Để vượt qua samthebest và những người đóng góp trước anh ấy, tôi đã cải thiện việc đặt tên và sự đồng nhất:

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

Điều này sử dụng "gõ vịt" phụ thuộc vào sự phản ánh. Đối với nhiều bối cảnh, tùy thuộc vào sự phản chiếu là không bắt đầu.
hỗn loạn3quilibrium

1

Không phụ thuộc, xử lý lỗi

  • Sử dụng các phương thức từ thư viện tiêu chuẩn
  • Tạo thư mục cho tập tin, nếu cần
  • Sử dụng Eitherđể xử lý lỗi

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

Sử dụng

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

Cập nhật 2019:

Tóm tắt - Java NIO (hoặc NIO.2 cho async) vẫn là giải pháp xử lý tệp toàn diện nhất được hỗ trợ trong Scala. Đoạn mã sau tạo và ghi một số văn bản vào một tệp mới:

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. Nhập thư viện Java: IO và NIO
  2. Tạo một Pathđối tượng với tên tệp bạn đã chọn
  3. Chuyển đổi văn bản của bạn mà bạn muốn chèn vào một tệp thành một mảng byte
  4. Nhận tệp của bạn dưới dạng luồng: OutputStream
  5. Truyền mảng byte của bạn vào writechức năng của luồng đầu ra của bạn
  6. Đóng luồng

1

Tương tự như câu trả lời này , đây là một ví dụ với fs2(phiên bản 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

Dòng này giúp viết một tệp từ Mảng hoặc Chuỗi.

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

0

Nếu bạn đang có các luồng Akka trong dự án của mình, nó sẽ cung cấp một lớp lót:

def writeToFile(p: Path, s: String)(implicit mat: Materializer): Unit = {
  Source.single(ByteString(s)).runWith(FileIO.toPath(p))
}

Tài liệu Akka> Truyền tệp IO

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.