Scala: ghi chuỗi vào tệp trong một câu lệnh


144

Để đọc các tập tin trong Scala, có

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

Có một cách tương đương và ngắn gọn để viết một chuỗi vào tập tin?

Hầu hết các ngôn ngữ hỗ trợ một cái gì đó như thế. Yêu thích của tôi là Groovy:

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

Tôi muốn sử dụng mã cho các chương trình từ một dòng đến một trang mã ngắn. Phải sử dụng thư viện của riêng bạn không có ý nghĩa ở đây. Tôi mong đợi một ngôn ngữ hiện đại sẽ cho phép tôi viết một cái gì đó vào một tệp một cách thuận tiện.

Có những bài viết tương tự như vậy, nhưng chúng không trả lời chính xác câu hỏi của tôi hoặc tập trung vào các phiên bản Scala cũ hơn.

Ví dụ:


Xem câu hỏi này . Tôi đồng ý với câu trả lời được đánh giá cao nhất - tốt hơn là có thư viện cá nhân của riêng bạn.

2
trong trường hợp này tôi không đồng ý rằng người ta phải viết thư viện cá nhân của riêng họ. Thông thường, khi bạn đang viết các phần nhỏ của chương trình để thực hiện những điều đặc biệt (có thể tôi chỉ muốn viết nó dưới dạng một kịch bản scala trang duy nhất hoặc chỉ trong REPL). Sau đó truy cập vào một thư viện cá nhân trở thành một nỗi đau.
smartnut007

Về cơ bản, có vẻ như không có gì trong scala 2.9 cho điều này vào thời điểm này. Không chắc chắn làm thế nào nếu tôi nên giữ câu hỏi này mở.
smartnut007

1
Nếu bạn tìm kiếm java.io trong mã nguồn Scala, bạn sẽ không tìm thấy nhiều lần xuất hiện và thậm chí ít hơn cho các hoạt động đầu ra, đặc biệt là PrintWriter. Vì vậy, cho đến khi thư viện Scala-IO trở thành một phần chính thức của Scala, chúng ta phải sử dụng Java thuần túy, như được thể hiện bởi mô hình.
PhiLho

vâng, thăm dò cũng cần một scala-io tương thích với các cải tiến IO của jdk7.
smartnut007

Câu trả lời:


77

Một dòng ngắn gọn:

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

14
Mặc dù cách tiếp cận này có vẻ tốt, nhưng nó không an toàn ngoại lệ hay an toàn mã hóa. Nếu có ngoại lệ xảy ra write(), closesẽ không bao giờ được gọi và tệp sẽ không bị đóng. PrintWritercũng sử dụng mã hóa hệ thống mặc định, rất tệ cho tính di động. Và cuối cùng, cách tiếp cận này tạo ra một lớp riêng cho dòng này (tuy nhiên, do Scala đã tạo ra hàng tấn các lớp ngay cả đối với một mã đơn giản, bản thân nó hầu như không phải là một nhược điểm).
Vladimir Matveev

Theo nhận xét trên, trong khi đây là một lót, nó không an toàn. Nếu bạn muốn an toàn hơn trong khi có nhiều tùy chọn hơn xung quanh vị trí và / hoặc đệm đầu vào, hãy xem câu trả lời tôi vừa đăng trên một chủ đề tương tự: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

2
Để ghi nhật ký gỡ lỗi tạm thời tôi đã sử dụngnew PrintWriter(tmpFile) { try {write(debugmsg)} finally {close} }
Randall Whitman

Về mặt phong cách có lẽ tốt hơn để sử dụng close(), tôi nghĩ
Casey

Đây là hai dòng và có thể dễ dàng được tạo thành một dòng như thế này:new java.io.PrintWriter("/tmp/hello") { write("file contents"); close }
Philluminati

163

Điều kỳ lạ là không ai từng đề xuất các hoạt động NIO.2 (có sẵn từ 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))

Tôi nghĩ rằng đây là cách đơn giản nhất và dễ nhất và thành ngữ nhất, và nó không cần bất kỳ sự phụ thuộc nào trong chính Java.


Điều này bỏ qua nhân vật dòng mới vì một số lý do.
Kakaji

@Kakaji, bạn có thể vui lòng giải thích? Tôi vừa thử nghiệm nó với các chuỗi với dòng mới, và nó hoạt động hoàn hảo. Nó chỉ không thể lọc bất cứ thứ gì - Files.write () ghi mảng byte dưới dạng blob, mà không phân tích nội dung của nó. Rốt cuộc, trong một số dữ liệu nhị phân byte 0x0d có thể có ý nghĩa quan trọng khác với dòng mới.
Vladimir Matveev

4
Lưu ý thêm: nếu bạn có Tệp, chỉ cần '.toPath' để nhận tham số đầu tiên.
akauppi

1
Đây là loại công nghiệp hơn (do sự lựa chọn CharSets rõ ràng) nhưng thiếu tính đơn giản (và một lớp lót ..) của reflect.io.File.writeAll(contents). Tại sao? Ba dòng khi bạn bao gồm hai báo cáo nhập khẩu. Có thể IDE sẽ tự động làm điều đó cho bạn nhưng nếu trong REPL thì không dễ như vậy.
javadba

3
@javadba chúng tôi đang ở trong JVM, nhập khẩu khá nhiều không được tính là 'dòng', đặc biệt là vì sự thay thế hầu như luôn luôn thêm một phụ thuộc thư viện mới. Dù sao, Files.writecũng chấp nhận một java.lang.Iterableđối số thứ hai và chúng ta có thể có được điều đó từ Scala Iterable, tức là khá nhiều loại bộ sưu tập, sử dụng trình chuyển đổi: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

Dưới đây là một lớp lót ngắn gọn sử dụng Refl.io.file, cách này hoạt động với Scala 2.12:

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

1
+1 Hoạt động tuyệt vời. Các Some/ foreachcombo là một chút sôi nổi, nhưng nó đi kèm với lợi ích của không try / catch / finally.
Brent Faust

3
Chà, nếu ghi sẽ ném một ngoại lệ, bạn có thể muốn đóng tệp nếu bạn có kế hoạch khôi phục từ ngoại lệ đó và đọc / ghi lại tệp. May mắn thay, scala cung cấp một lớp lót để làm điều này là tốt.
Hội trường Garrett

25
Điều này không được khuyến nghị vì gói scala.tools.nsc.io không phải là một phần của API công khai nhưng được trình biên dịch sử dụng.
Giovanni Botta

3
Các Some/ foreachhack là chính xác lý do tại sao nhiều người ghét Scala cho mã đọc được nó gây ra hacker sản.
Erik Kaplun

3
scala.tootls.nsc.io.Filelà một bí danh cho reflect.io.File. Vẫn là API nội bộ, nhưng ít nhất một chút ngắn hơn.
kostja

41

Nếu bạn thích cú pháp Groovy, bạn có thể sử dụng mẫu thiết kế Pimp-My-Library để đưa nó đến 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 )

}

Nó sẽ hoạt động như mong đợi:

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
Bạn không bao giờ gọi đóng trên cá thể được trả về Source.fromFile, điều đó có nghĩa là tài nguyên không bị đóng cho đến khi nó được xử lý (Thu gom rác). Và PrintWriter của bạn không được đệm, do đó, nó đang sử dụng bộ đệm mặc định JVM nhỏ gồm 8 byte, có khả năng làm chậm đáng kể IO của bạn. Tôi vừa tạo một câu trả lời về một chủ đề tương tự liên quan đến các vấn đề này: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

1
Bạn đúng rồi. Nhưng giải pháp tôi đưa ra ở đây hoạt động tốt cho các chương trình ngắn hạn với IO nhỏ. Tôi không đề xuất nó cho quy trình máy chủ hoặc dữ liệu lớn (theo nguyên tắc chung, hơn 500 MB).
mô hình

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

Nó không hoạt động với tôi trong REPL. Không có lỗi, nhưng nếu tôi nhìn vào /tmp/example.txt thì không có.
người dùng không xác định

@user không rõ, Xin lỗi vì đã bỏ lỡ '!' vào cuối lệnh, cố định ngay bây giờ.
xiefei

Tuyệt vời! Bây giờ nó hoạt động, tôi muốn biết tại sao. Là gì #>, !làm gì?
người dùng không xác định

10
Giải pháp này không phải là di động! chỉ hoạt động trong các hệ thống * nix.
Giovanni Botta

2
Điều này sẽ không làm việc để viết các chuỗi tùy ý. Nó sẽ chỉ hoạt động đối với các chuỗi ngắn mà bạn có thể chuyển dưới dạng đối số cho echocông cụ dòng lệnh .
Giàu

15

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

file.write("Hi!")

hoặc là

file << "Hi!"

3
Yêu thư viện của bạn! Câu hỏi này là một trong những câu hỏi hay nhất 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

12

Bạn có thể dễ dàng sử dụng các tập tin Apache . Nhìn vào chức năng writeStringToFile. Chúng tôi sử dụng thư viện này trong các dự án của chúng tôi.


3
Tôi sử dụng nó tất cả các thời gian quá. Nếu bạn đọc kỹ câu hỏi, tôi đã hiểu tại sao tôi không muốn sử dụng thư viện.
smartnut007

Không sử dụng thư viện, tôi đã tạo ra một giải pháp xử lý các ngoại lệ trong khi đọc / ghi VÀ có thể được đệm ngoài các mặc định bộ đệm nhỏ do các thư viện Java cung cấp: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

7

Một cái cũng có định dạng này, cả hai đều súc tích và thư viện bên dưới được viết rất đẹp (xem mã nguồn):

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

Điều này đủ ngắn gọn, tôi đoán:

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
Đủ công bằng, nhưng "đủ ngắn gọn" này không được phân loại là "một tuyên bố": P
Erik Kaplun

Mã này giải quyết nhiều vấn đề về Java. Thật không may Scala không coi IO đủ quan trọng nên thư viện tiêu chuẩn không bao gồm một.
Chris

Câu trả lời che giấu một vấn đề tài nguyên mồ côi với FileWriter mới. Nếu FileWriter mới thành công, nhưng BufferedWriter mới không thành công, thì đối tượng FileWriter sẽ không bao giờ được nhìn thấy nữa và vẫn bị treo cho đến khi GCed (Garbage Sưu tập) và có thể không bị đóng ngay cả khi đó (do cách hoàn thiện bảo đảm hoạt động trong JVM). Tôi đã viết một câu trả lời cho một câu hỏi tương tự giải quyết các vấn đề này: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

2

Bạn có thể làm điều này với sự kết hợp của các thư viện Java và Scala. Bạn sẽ có toàn quyền kiểm soát mã hóa ký tự. Nhưng thật không may, các tập tin xử lý sẽ không được đóng đúng cách.

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

Bạn có một vấn đề tài nguyên mồ côi trong mã của bạn. Vì bạn không nắm bắt được các phiên bản trước cuộc gọi của mình, nếu một trong hai ngoại lệ trước khi phương thức bạn đang gọi đã được truyền tham số, các tài nguyên được khởi tạo thành công sẽ không bị đóng cho đến khi được phân loại (Thu gom rác) và thậm chí sau đó có thể không phải do cách thức bảo đảm của GC hoạt động. Tôi đã viết một câu trả lời cho một câu hỏi tương tự giải quyết các vấn đề này: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

1
Bạn đã đúng và giải pháp của bạn là khá tốt đẹp. Nhưng ở đây yêu cầu đã được thực hiện trong một dòng. Và khi bạn đọc kỹ, tôi đã đề cập đến sự rò rỉ tài nguyên trong câu trả lời của tôi như là một hạn chế đi kèm với yêu cầu này và với cách tiếp cận của tôi. Giải pháp của bạn là tốt, nhưng sẽ không phù hợp với yêu cầu một dòng.
stefan.schwetschke

2

Tôi biết đó không phải là một dòng, nhưng nó giải quyết các vấn đề an toàn theo như tôi có thể nói;

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

Nếu bạn không quan tâm đến việc xử lý ngoại lệ thì bạn có thể viết

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

1

CẬP NHẬT: Tôi đã tạo ra một giải pháp hiệu quả hơn mà tôi đã xây dựng ở đây: https://stackoverflow.com/a/34277491/501113

Tôi thấy mình làm việc nhiều hơn và nhiều hơn trong Bảng tính Scala trong IDE Scala cho Eclipse (và tôi tin rằng có một cái gì đó tương đương trong IntelliJ IDEA). Dù sao, tôi cần có khả năng thực hiện một lớp lót để xuất một số nội dung khi tôi nhận được "Đầu ra vượt quá giới hạn cắt". nếu tôi đang làm bất cứ điều gì quan trọng, đặc biệt là với các bộ sưu tập Scala.

Tôi đã đưa ra một lớp lót tôi chèn vào đầu mỗi Bảng tính Scala mới để đơn giản hóa việc này (và vì vậy tôi không phải thực hiện toàn bộ bài tập nhập thư viện bên ngoài cho một nhu cầu rất đơn giản). Nếu bạn là một stickler và nhận thấy rằng về mặt kỹ thuật là hai dòng, nó chỉ để làm cho nó dễ đọc hơn trong diễn đàn này. Nó là một dòng duy nhất trong Bảng tính Scala của tôi.

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

Và cách sử dụng chỉ đơn giản là:

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

Điều này cho phép tôi tùy ý cung cấp tên tệp nếu tôi muốn có các tệp bổ sung ngoài mặc định (sẽ ghi đè hoàn toàn tệp mỗi khi phương thức được gọi).

Vì vậy, cách sử dụng thứ hai chỉ đơn giản là:

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

Thưởng thức!


1

Sử dụng thư viện ops ammonite . Cú pháp rất nhỏ, nhưng chiều rộng của thư viện gần như rộng như những gì người ta mong đợi khi thử một tác vụ như vậy trong một ngôn ngữ kịch bản lệnh shell như bash.

Trên trang tôi liên kết đến, nó hiển thị nhiều thao tác người ta có thể thực hiện với thư viện, nhưng để trả lời câu hỏi này, đây là một ví dụ về việc ghi vào tệp

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

-1

Thông qua sự kỳ diệu của dấu chấm phẩy, bạn có thể làm bất cứ điều gì bạn thích một lớp lót.

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

Không có tranh luận ở đây. Tôi quyết định không ghi nhớ API nào cần tôi tuôn ra một phần cuộc sống của mình, vì vậy tôi chỉ luôn làm điều đó.
Ion Freeman
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.