Đọc toàn bộ tập tin trong Scala?


312

Cách đơn giản và chuẩn để đọc toàn bộ tệp vào bộ nhớ trong Scala là gì? (Lý tưởng nhất là kiểm soát mã hóa ký tự.)

Điều tốt nhất tôi có thể đưa ra là:

scala.io.Source.fromPath("file.txt").getLines.reduceLeft(_+_)

hoặc tôi phải sử dụng một trong những thành ngữ tuyệt vời của Java, trong đó thành ngữ tốt nhất (không sử dụng thư viện bên ngoài) dường như là:

import java.util.Scanner
import java.io.File
new Scanner(new File("file.txt")).useDelimiter("\\Z").next()

Từ việc đọc các cuộc thảo luận về danh sách gửi thư, đối với tôi, scala.io.Source thậm chí còn được coi là thư viện I / O chuẩn. Tôi không hiểu mục đích dự định của nó là gì, chính xác.

... Tôi muốn một cái gì đó chết - đơn giản và dễ nhớ. Ví dụ, trong các ngôn ngữ này, thật khó để quên thành ngữ ...

Ruby    open("file.txt").read
Ruby    File.read("file.txt")
Python  open("file.txt").read()

12
Java không tệ nếu bạn biết các công cụ phù hợp. nhập org.apache.commons.io.FileUtils; FileUtils.readFileToString (Tệp mới ("file.txt", "UTF-8")
smartnut007

25
Nhận xét này bỏ lỡ điểm của thiết kế ngôn ngữ. Do đó, bất kỳ ngôn ngữ nào có sẵn một chức năng thư viện đơn giản cho chính xác thao tác bạn muốn thực hiện cũng tốt như cú pháp gọi hàm của nó. Với một thư viện ghi nhớ vô hạn và 100%, tất cả các chương trình sẽ được thực hiện với một lệnh gọi hàm duy nhất. Một ngôn ngữ lập trình là tốt khi nó cần ít thành phần pre-fab hơn để đạt được kết quả cụ thể.
Chris Mountford

Câu trả lời:


429
val lines = scala.io.Source.fromFile("file.txt").mkString

Nhân tiện, " scala." không thực sự cần thiết, vì dù sao nó luôn nằm trong phạm vi và dĩ nhiên, bạn có thể nhập nội dung của io, đầy đủ hoặc một phần và tránh phải trả trước "io". quá.

Ở trên để các tập tin mở, tuy nhiên. Để tránh sự cố, bạn nên đóng nó như thế này:

val source = scala.io.Source.fromFile("file.txt")
val lines = try source.mkString finally source.close()

Một vấn đề khác với mã ở trên là nó chậm khủng khiếp do tính chất thực thi của nó. Đối với các tệp lớn hơn nên sử dụng:

source.getLines mkString "\n"

48
Tôi đến bữa tiệc quá muộn, nhưng tôi ghét mọi người không biết họ có thể làm "io.File (" / etc / passwd "). Slurp" trong cốp xe.
psp

28
@extempore Nếu bạn thực sự nghĩ rằng tôi vô ơn, tôi thực sự xin lỗi. Tôi đánh giá cao sự hỗ trợ của bạn đối với ngôn ngữ Scala và mỗi lần bạn trực tiếp xem xét một vấn đề tôi đưa ra, đề xuất một giải pháp cho một vấn đề tôi gặp phải hoặc giải thích điều gì đó cho tôi. Sau đó, tôi sẽ nắm lấy cơ hội để cảm ơn bạn vì đã biến scala.io thành một thứ gì đó đàng hoàng và xứng đáng. Tôi sẽ nói nhiều hơn trong lời cảm ơn từ bây giờ, nhưng tôi vẫn ghét cái tên đó, xin lỗi.
Daniel C. Sobral

49
"Slurp" là tên để đọc toàn bộ tệp cùng một lúc trong Perl trong nhiều năm. Perl có truyền thống đặt tên trực quan và không chính thức hơn họ ngôn ngữ C, điều mà một số người có thể thấy khó chịu, nhưng trong trường hợp này tôi nghĩ nó phù hợp: đó là một từ xấu xí cho một thực hành xấu xí. Khi bạn nhếch nhác (), bạn biết bạn đang làm điều gì đó nghịch ngợm vì bạn chỉ cần gõ nó.
Marcus Downing

15
File.read () sẽ là một tên đẹp hơn và phù hợp với Ruby và Python bên cạnh đó.
Brendan OConnor

26
@extempore: bạn không thể ngăn mọi người chán ghét. Nó chỉ là như vậy. Bạn không nên bận tâm rằng một số người không thích mọi lựa chọn bạn đã thực hiện. Đó chỉ là cuộc sống, bạn không thể làm hài lòng tất cả mọi người :)
Alex Baranosky

58

Chỉ cần mở rộng giải pháp của Daniel, bạn có thể rút ngắn rất nhiều thứ bằng cách chèn phần nhập sau vào bất kỳ tệp nào yêu cầu thao tác tệp:

import scala.io.Source._

Với điều này, bây giờ bạn có thể làm:

val lines = fromFile("file.txt").getLines

Tôi sẽ cảnh giác khi đọc toàn bộ một tập tin thành một String. Đó là một thói quen rất xấu, một thói quen sẽ cắn bạn sớm hơn và khó hơn bạn nghĩ. Các getLinesphương thức trả về một giá trị kiểu Iterator[String]. Đó thực sự là một con trỏ lười biếng vào tập tin, cho phép bạn chỉ kiểm tra dữ liệu bạn cần mà không gặp rủi ro về bộ nhớ.

Ồ, và để trả lời câu hỏi ngụ ý của bạn về Source: có, đó là thư viện I / O chính tắc. Hầu hết các mã kết thúc bằng cách sử dụng java.iodo giao diện cấp thấp hơn và khả năng tương thích tốt hơn với các khung hiện có, nhưng bất kỳ mã nào có lựa chọn nên được sử dụng Source, đặc biệt là cho thao tác tệp đơn giản.


ĐỒNG Ý. Có một câu chuyện cho ấn tượng tiêu cực của tôi về Nguồn: Tôi đã từng ở một tình huống khác so với bây giờ, nơi tôi có một tệp rất lớn không phù hợp với bộ nhớ. Sử dụng Nguồn khiến chương trình bị sập; Hóa ra nó đang cố đọc toàn bộ mọi thứ cùng một lúc.
Brendan OConnor

7
Nguồn không phải là để đọc toàn bộ tập tin vào bộ nhớ. Nếu bạn sử dụng toList sau getLines hoặc một số phương thức khác sẽ tạo ra một bộ sưu tập, thì bạn sẽ đưa mọi thứ vào bộ nhớ. Bây giờ, Source là một hack , dự định để hoàn thành công việc, không phải là một thư viện được suy nghĩ cẩn thận. Nó sẽ được cải thiện trong Scala 2.8, nhưng chắc chắn sẽ có cơ hội cho cộng đồng Scala trở nên tích cực trong việc xác định API I / O tốt.
Daniel C. Sobral

36
// for file with utf-8 encoding
val lines = scala.io.Source.fromFile("file.txt", "utf-8").getLines.mkString

6
Thêm "getLines" vào câu trả lời ban đầu sẽ xóa tất cả các dòng mới. Phải là "Source.fromFile (" file.txt "," utf-8 "). MkString".
Joe23

9
Xem thêm nhận xét của tôi trong câu trả lời của Daniel C. Sobral - việc sử dụng này sẽ không đóng phiên bản Nguồn, vì vậy Scala có thể giữ lại khóa trên tệp.
djb

26

(EDIT: Điều này không hoạt động trong scala 2.9 và có thể không phải 2.8)

Sử dụng thân cây:

scala> io.File("/etc/passwd").slurp
res0: String = 
##
# User Database
# 
... etc

14
" slurp"? Chúng tôi đã thực sự bỏ tên rõ ràng, trực quan? Vấn đề với slurpnó là nó có thể có ý nghĩa sau thực tế, đối với một người có tiếng Anh là ngôn ngữ đầu tiên, ít nhất, nhưng bạn sẽ không bao giờ nghĩ về nó để bắt đầu!
Daniel C. Sobral

5
Chỉ vấp vào câu hỏi / câu trả lời này. Filekhông còn trong 2.8.0, phải không?
huynhjl

4
tiếng lóng ngóng thật tuyệt. :) Tôi không mong đợi nó, nhưng tôi cũng không mong đợi màn hình được đặt tên là 'in'. slurpthật tuyệt vời :) Thật tuyệt vời phải không? Tôi không tìm thấy nó. ; (
người dùng không xác định

5
trong scala-2.10.0 tên gói là scala.reflect.io.File Và một câu hỏi về "Tệp" này. extempore, tại sao tập tin này được đánh dấu là "thử nghiệm"? Nó có an toàn không? Nó có miễn phí một khóa cho hệ thống tập tin?
VasiliNovikov

4
slurp có một lịch sử lâu dài cho mục đích này bắt nguồn, tôi nghĩ, từ perl
Chris Mountford

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

new String(Files.readAllBytes(Paths.get("file.txt")), UTF_8)

Kiểm soát mã hóa ký tự và không có tài nguyên để dọn dẹp. Ngoài ra, có thể được tối ưu hóa (ví dụ: Files.readAllBytesphân bổ một mảng byte phù hợp với kích thước của tệp).


7

Tôi đã được thông báo rằng Source.fromFile có vấn đề. Cá nhân tôi đã gặp sự cố khi mở các tệp lớn với Source.fromFile và đã phải dùng đến Java InputStreams.

Một giải pháp thú vị khác là sử dụng scalax. Dưới đây là ví dụ về một số mã được nhận xét tốt mở tệp nhật ký bằng ManagedResource để mở tệp với trình trợ giúp scalax: http://pastie.org/pastes/420714


6

Sử dụng getLines () trên scala.io.Source sẽ loại bỏ các ký tự được sử dụng cho các đầu cuối dòng (\ n, \ r, \ r \ n, v.v.)

Sau đây nên giữ nguyên ký tự cho từng ký tự và không thực hiện nối chuỗi quá mức (vấn đề về hiệu năng):

def fileToString(file: File, encoding: String) = {
  val inStream = new FileInputStream(file)
  val outStream = new ByteArrayOutputStream
  try {
    var reading = true
    while ( reading ) {
      inStream.read() match {
        case -1 => reading = false
        case c => outStream.write(c)
      }
    }
    outStream.flush()
  }
  finally {
    inStream.close()
  }
  new String(outStream.toByteArray(), encoding)
}

6

Thêm một điều nữa: https://github.com/pathikrit/better-files#streams-and-codecs

Nhiều cách khác nhau để nhét một tập tin mà không tải nội dung vào bộ nhớ:

val bytes  : Iterator[Byte]            = file.bytes
val chars  : Iterator[Char]            = file.chars
val lines  : Iterator[String]          = file.lines
val source : scala.io.BufferedSource   = file.content 

Bạn cũng có thể cung cấp codec của riêng mình cho bất kỳ thứ gì đọc / ghi (nó giả sử scala.io.Codec.default nếu bạn không cung cấp một cái):

val content: String = file.contentAsString  // default codec
// custom codec:
import scala.io.Codec
file.contentAsString(Codec.ISO8859)
//or
import scala.io.Codec.string2codec
file.write("hello world")(codec = "US-ASCII")

5

Giống như trong Java, sử dụng thư viện CommonsIO:

FileUtils.readFileToString(file, StandardCharsets.UTF_8)

Ngoài ra, nhiều câu trả lời ở đây quên Charset. Tốt hơn là luôn luôn cung cấp nó một cách rõ ràng, hoặc nó sẽ đạt được một ngày.


4

Để mô phỏng cú pháp Ruby (và truyền đạt ngữ nghĩa) của việc mở và đọc tệp, hãy xem xét lớp ẩn này (Scala 2.10 trở lên),

import java.io.File

def open(filename: String) = new File(filename)

implicit class RichFile(val file: File) extends AnyVal {
  def read = io.Source.fromFile(file).getLines.mkString("\n")
}

Theo cách này,

open("file.txt").read

3

như một vài người đã đề cập đến scala.io.Source là tốt nhất nên tránh do rò rỉ kết nối.

Có lẽ scalax và java lib thuần như commons-io là những lựa chọn tốt nhất cho đến khi dự án ươm tạo mới (tức là scala-io) được hợp nhất.


3

bạn cũng có thể sử dụng Đường dẫn từ scala io để đọc và xử lý tệp.

import scalax.file.Path

Bây giờ bạn có thể nhận đường dẫn tệp bằng cách này: -

val filePath = Path("path_of_file_to_b_read", '/')
val lines = file.lines(includeTerminator = true)

Bạn cũng có thể Bao gồm các đầu cuối nhưng theo mặc định, nó được đặt thành false ..



3

Bạn không cần phải phân tích từng dòng một và sau đó nối chúng lại ...

Source.fromFile(path)(Codec.UTF8).mkString

Tôi thích sử dụng này:

import scala.io.{BufferedSource, Codec, Source}
import scala.util.Try

def readFileUtf8(path: String): Try[String] = Try {
  val source: BufferedSource = Source.fromFile(path)(Codec.UTF8)
  val content = source.mkString
  source.close()
  content
}

Bạn nên đóng luồng - nếu xảy ra lỗi trongval content = source.mkString
Andrzej Jozwik

+1 cho Codec. Tôi đã thử nghiệm thất bại sbt testvì không thể thiết lập nó, trong khi lệnh thử nghiệm của Intellij vượt qua tất cả các thử nghiệm. Và bạn có thể sử dụng def usingtừ này
Mikhail Ionkin

3

Nếu bạn không bận tâm đến sự phụ thuộc của bên thứ ba, bạn nên xem xét sử dụng thư viện OS-Lib của tôi . Điều này làm cho việc đọc / ghi tệp và làm việc với hệ thống tệp rất thuận tiện:

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

với các trình trợ giúp một dòng để đọc byte , đọc các đoạn , đọc các dòng và nhiều hoạt động hữu ích / phổ biến khác


2

Câu hỏi rõ ràng là "tại sao bạn muốn đọc trong toàn bộ tập tin?" Đây rõ ràng không phải là một giải pháp có thể mở rộng nếu các tệp của bạn trở nên rất lớn. Việc scala.io.Sourcecung cấp cho bạn một Iterator[String]từ getLinesphương thức, rất hữu ích và súc tích.

Không có nhiều công việc để đưa ra một chuyển đổi ngầm bằng cách sử dụng các tiện ích java IO cơ bản để chuyển đổi a File, a Readerhoặc InputStreama thành a String. Tôi nghĩ rằng việc thiếu khả năng mở rộng có nghĩa là họ đúng khi không thêm điều này vào API tiêu chuẩn.


12
Nghiêm túc? Có bao nhiêu tập tin bạn thực sự đọc một cách thường xuyên có vấn đề thực sự phù hợp trong bộ nhớ? Phần lớn các tệp trong phần lớn các chương trình tôi từng xử lý dễ dàng đủ nhỏ để phù hợp với bộ nhớ. Thành thật mà nói, các tệp dữ liệu lớn là ngoại lệ, và bạn nên nhận ra điều đó và lập trình phù hợp nếu bạn sẽ đọc / ghi chúng.
Christopher

8
oxbow_lakes, tôi không đồng ý. Có nhiều tình huống liên quan đến các tệp nhỏ có kích thước sẽ không tăng trong tương lai.
Brendan OConnor

4
Tôi đồng ý rằng chúng là ngoại lệ - nhưng tôi nghĩ đó là lý do tại sao một tệp đọc toàn bộ tệp vào bộ nhớ không có trong JDK hoặc SDK Scala. Đây là phương pháp tiện ích 3 dòng để bạn tự viết: vượt qua nó
oxbow_lakes

1

in mọi dòng, như sử dụng Java BufferedReader đọc dòng ervery và in nó:

scala.io.Source.fromFile("test.txt" ).foreach{  print  }

tương đương:

scala.io.Source.fromFile("test.txt" ).foreach( x => print(x))

0
import scala.io.source
object ReadLine{
def main(args:Array[String]){
if (args.length>0){
for (line <- Source.fromLine(args(0)).getLine())
println(line)
}
}

trong các đối số bạn có thể đưa ra đường dẫn tệp và nó sẽ trả về tất cả các dòng


3
Điều này cung cấp những gì mà câu trả lời khác không?
jwvh

Không thấy câu trả lời nào khác ... chỉ nghĩ rằng tôi có thể đóng góp ở đây nên đã đăng ... hy vọng điều đó sẽ không gây hại cho ai :)
Apurw

1
Bạn thực sự nên đọc chúng. Hầu hết là khá nhiều thông tin. Ngay cả những người 8 tuổi có thông tin liên quan.
jwvh
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.