Jak pisać do pliku w Scali?


157

Do czytania przydatna jest abstrakcja Source. Jak mogę zapisać linie do pliku tekstowego?


1
Jeśli wiesz, jak to zrobić w Javie, możesz użyć tego samego w Scali. Czy Twoje pytanie dotyczy standardowej biblioteki Scala?
Wheaties

1
@wheaties yes najlepszy sposób na zrobienie tego w scala
yura

Ta biblioteka jest naprawdę dobra: github.com/pathikrit/better-files
Robin

Biblioteka Lihaoyi OS-Lib github.com/lihaoyi/os-lib
WeiChing 林 煒 清

Odpowiedzi:


71

Edytuj 2019 (8 lat później), Scala-IO nie jest zbyt aktywna, jeśli w ogóle, Li Haoyi sugeruje własną bibliotekę lihaoyi/os-lib, którą przedstawia poniżej .

Czerwiec 2019, Xavier Guihot wspomina w swojej odpowiedzi o bibliotece Usingnarzędzie do automatycznego zarządzania zasobami.


Edycja (wrzesień 2011): odkąd Eduardo Costa pyta o Scala2.9 i od kiedy Rick-777 komentuje, że historia zmian scalax.IO praktycznie nie istnieje od połowy 2009 roku ...

Scala-IO zmieniło miejsce: zobacz repozytorium GitHub od Jesse Eichara (również na SO ):

Projekt parasolowy Scala IO składa się z kilku podprojektów dotyczących różnych aspektów i rozszerzeń IO.
Istnieją dwa główne składniki Scala IO:

  • Core - Core zajmuje się głównie odczytywaniem i zapisywaniem danych do iz dowolnych źródeł i ujść. Kamień węgielny cechy są Input, Outputi Seekablektóre stanowią rdzeń API.
    Inne klasy znaczenie mają Resource, ReadCharsi WriteChars.
  • Plik - plik jest File(nazywanym Path) interfejsem API opartym na połączeniu systemu plików Java 7 NIO i interfejsów API SBT PathFinder.
    Pathi FileSystemsą głównymi punktami wejścia do 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)

Oryginalna odpowiedź (styczeń 2011), ze starym miejscem na scala-io:

Jeśli nie chcesz czekać na Scala2.9, możesz skorzystać z biblioteki scala-incubator / scala-io .
(jak wspomniano w sekcjiDlaczego Scala Source nie zamyka bazowego InputStream? ”)

Zobacz próbki

{ // 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
A co z wersją Scala 2.9? :)
Eduardo Costa,

Projekt scalax wydaje się martwy (brak zobowiązań od czerwca 2009). Czy to jest poprawne? historia zatwierdzeń scalax
Rick-777

@Eduardo: Uzupełniłem swoją odpowiedź o nowe miejsce na bibliotekę scala-io (która została zaktualizowana dla Scala2.9: github.com/jesseeichar/scala-io/issues/20 )
VonC

10
Czy to naprawdę aktualna sugestia dotycząca Scali 2.10? Używać Scala IO? Nie ma jeszcze nic w rdzeniu Scala?
Phil,

2
Nigdy nie używałem scalax.io, ale sądząc po tych przykładowych wierszach, wygląda na to, że jego projekt API jest dość zły. Mieszanie metod dla danych znakowych i binarnych w jednym interfejsie nie ma sensu i najprawdopodobniej doprowadzi do błędów w kodowaniu, które są trudne do znalezienia. Projekt java.io (Reader / Writer vs. InputStream / OutputStream) wydaje się znacznie lepszy.
jcsahnwaldt Przywróć Monikę

211

Jest to jedna z funkcji brakujących w standardowej Scali, która okazała się tak przydatna, że ​​dodaję ją do mojej osobistej biblioteki. (Prawdopodobnie powinieneś też mieć osobistą bibliotekę.) Kod wygląda tak:

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

i jest używany w ten sposób:

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

1
new java.io.PrintWriter () używa domyślnego kodowania platformy, co prawdopodobnie oznacza, że ​​plik wynikowy nie jest zbyt przenośny. Na przykład, jeśli chcesz utworzyć plik, który możesz później wysłać pocztą e-mail, prawdopodobnie powinieneś użyć konstruktora PrintWriter, który pozwala określić kodowanie.
jcsahnwaldt Przywróć Monikę

@JonaChristopherSahnwaldt - Jasne, w szczególnych przypadkach możesz chcieć określić kodowanie. Wartość domyślna dla platformy jest średnio najbardziej rozsądną wartością domyślną. To samo co w przypadku Source(domyślne kodowanie). Możesz oczywiście dodać np. enc: Option[String] = NoneParametr po, fjeśli uznasz to za powszechną potrzebę.
Rex Kerr,

6
@RexKerr - Nie zgadzam się. Prawie we wszystkich przypadkach należy określić kodowanie. Większość błędów kodowania, które napotykam, ma miejsce, ponieważ ludzie nie rozumieją lub nie myślą o kodowaniu. Używają wartości domyślnej i nawet o tym nie wiedzą, ponieważ zbyt wiele interfejsów API pozwala im uciec. Obecnie najbardziej rozsądnym domyślnym byłoby prawdopodobnie UTF-8. Może pracujesz tylko z językiem angielskim i innymi językami, które można zapisać w ASCII. Szczęściarz. Mieszkam w Niemczech i musiałem naprawić więcej połamanych umlautów, niż bym pamiętał.
jcsahnwaldt Przywróć Monikę

3
@JonaChristopherSahnwaldt - To jest powód, aby mieć rozsądne domyślne kodowanie, a nie zmuszać wszystkich do ciągłego określania go. Ale jeśli korzystasz z komputera Mac, a twoje pliki napisane przez Javę są gobbledygook, ponieważ nie są zakodowane w systemie Mac OS Roman, nie jestem pewien, czy przyniesie to więcej korzyści niż szkody. Myślę, że to wina platform, że nie zgodziły się na zestaw znaków. Jako indywidualny programista wpisanie łańcucha naprawdę nie rozwiąże problemu. (Wszyscy deweloperzy zgadzając się na UTF-8 będzie, ale potem, że można po prostu przejść się jako domyślne.)
Rex Kerr

@JonaChristopherSahnwaldt +10 za naprawienie wszystkich zepsutych umlautów. Nie możesz użyć młotka, może dziurkacza? A może to już dziury, które trzeba wypełnić, może ten facet może pomóc youtube.com/watch?v=E-eBBzWEpwE Ale poważnie, wpływ ASCII jest tak szkodliwy na świecie, zgadzam się, że powinien być określony i domyślnie jako UTF- 8
Davos

50

Podobna do odpowiedzi Rexa Kerra, ale bardziej ogólna. Najpierw używam funkcji pomocniczej:

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

Następnie używam tego jako:

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

i

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

itp.


39
Nie zrozum mnie źle, podoba mi się Twój kod i jest bardzo pouczający, ale im więcej widzę takie konstrukcje dla prostych problemów, tym bardziej przypomina mi on stary żart z „hello world”: ariel.com.au/jokes/The_Evolution_of_a_Programmer .html :-) (+1 głos ode mnie).
greenoldman

4
Jeśli piszesz jednolinijki, nic się nie liczy. Jeśli piszesz znaczące programy (duże z ciągłą potrzebą konserwacji i ewolucji), tego rodzaju myślenie prowadzi do najszybszego i najbardziej zgubnego rodzaju degradacji jakości oprogramowania.
Randall Schulz

3
Nie każdy będzie miał „oczy łuskowate” aż do pewnego poziomu praktyki - zabawne jest widzieć, że ten przykład kodu pochodzi ze „Początkowej” Scali
asyncwait

asyncwait "początek" scala ... najbardziej ironiczny tytuł wszechczasów, uwaga: mam książkę ... i dopiero teraz zaczynam ją rozumieć .. Przypuszczam, że byłem o krok przed "początkującym" lol: D ........
user1050817

1
Problem polega mniej na sztuczkach Scali, ale w gadatliwości i kiepskim stylu. Zredagowałem to, aby było bardziej czytelne. Po moim refaktorze to tylko 4 linie (dobrze, 4 z długością linii IDE, użyte tutaj 6, aby zmieścić się na ekranie). IMHO to teraz bardzo fajna odpowiedź.
samthebest

38

Prosta odpowiedź:

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 czy mógłbyś dodać biblioteki, importz których korzystasz?
Daniel,

1
Od wersji java 7 użyj zamiast tego java.nio.file: def writeToFile (file: String, stringToWrite: String): Unit = {val writer = Files.newBufferedWriter (Paths.get (file)) try writer.write (stringToWrite) w końcu writer.close ()}
E Shindler

20

Udzielenie innej odpowiedzi, ponieważ moje zmiany innych odpowiedzi zostały odrzucone.

To najbardziej zwięzła i prosta odpowiedź (podobna do odpowiedzi Garret Hall)

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

Jest to podobne do Jus12, ale bez gadatliwości i z poprawnym stylem kodu

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

Zauważ, że NIE potrzebujesz nawiasów klamrowych dla try finallyani lambd i zwróć uwagę na użycie składni zastępczej. Zwróć także uwagę na lepsze nazewnictwo.


2
Przepraszamy, ale twój kod jest możliwy do wyobrażenia, nie spełnia on implementedwarunku wstępnego. Nie możesz użyć kodu, który nie został zaimplementowany. Chodzi mi o to, że musisz powiedzieć, jak go znaleźć, ponieważ nie jest on domyślnie dostępny i nie jest dobrze znany.
Val

15

Oto zwięzły, jednowierszowy tekst wykorzystujący bibliotekę kompilatora Scala:

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

Alternatywnie, jeśli chcesz użyć bibliotek Java, możesz to zrobić:

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

Jaki import? tj. skąd pochodzi plik?
Ben Hutchison,

Biblioteka kompilatora Scala.
Garrett Hall

3
Już nieopłacalne (nie w Scali 2.11)
Brent Faust

1
O czym mówisz? scala.tools.nsc.io.File("/tmp/myFile.txt")działa w Scali 2.11.8.

1
Jest teraz w scala.reflect.io.File
Keith Nordstrom

13

Jedna wkładka do zapisywania / odczytu do / z String, za pomocą 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
}

To nie jest odpowiednie dla dużych plików, ale wystarczy.

Niektóre linki:

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


Dlaczego nie jest to odpowiednie dla dużych plików?
Chetan Bhasin,

2
@ChetanBhasin Prawdopodobnie dlatego, writeże skopiuje contentsdo nowej tablicy bajtów zamiast przesyłać ją strumieniowo do pliku, przez co w szczytowym momencie zużyje dwa razy więcej pamięci niż contentssam.
Daniel Werner

10

Niestety dla najlepszej odpowiedzi, Scala-IO nie żyje. Jeśli nie masz nic przeciwko korzystaniu z zależności od innej firmy, rozważ użycie mojej biblioteki OS-Lib . To sprawia, że ​​praca z plikami, ścieżkami i systemem plików jest bardzo łatwa:

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

Ma jednowierszowe do zapisywania do plików , dołączania do plików , nadpisywania plików i wielu innych użytecznych / typowych operacji


Dziękuję za tę aktualizację. Głosowano za. Odwołałem się do Twojej odpowiedzi w mojej własnej powyżej, aby uzyskać lepszą widoczność.
VonC

7

Mikro biblioteka, którą napisałem: https://github.com/pathikrit/better-files

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

lub

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

Również tutaj - to pytanie jest jednym z największych hitów podczas wyszukiwania w Google, jak napisać plik za pomocą scali - teraz, gdy Twój projekt urósł, możesz nieco rozszerzyć swoją odpowiedź?
asac

6

Począwszy Scala 2.13od biblioteki standardowej dostępne jest dedykowane narzędzie do zarządzania zasobami:Using .

Można go użyć w tym przypadku z zasobami takimi jak PrintWriterlub BufferedWriterktóre rozszerzają się AutoCloseable, aby zapisać do pliku i, bez względu na wszystko, później zamknąć zasób:

  • Na przykład w przypadku java.iointerfejsu API:

    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)
    }
  • Lub z 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

AKTUALIZACJA w dniu 2019 / wrzesień / 01:

  • Począwszy od Scala 2.13, preferuj używanie scala.util
  • Naprawiono błąd polegający na tym, finallyże połknął oryginał Exceptionrzucony przez, tryjeśli finallykod wyrzucił plikException

Po przejrzeniu wszystkich tych odpowiedzi, jak łatwo napisać plik w Scali, a niektóre z nich są całkiem fajne, miałem trzy problemy:

  1. W odpowiedzi Jus12 , użycie curry do metody pomocniczej jest nieoczywiste dla początkujących Scala / FP
  2. Musi hermetyzować błędy niższego poziomu za pomocą scala.util.Try
  3. Musi pokazać programistom Java, którzy nie znają Scala / FP, jak prawidłowo zagnieżdżać zasoby zależne, aby closemetoda była wykonywana na każdym zasobie zależnym w odwrotnej kolejności - Uwaga: zamykanie zasobów zależnych w odwrotnej kolejności, SZCZEGÓLNIE W PRZYPADKU AWARII jest rzadko rozumianym wymogiem java.lang.AutoCloseablespecyfikacja, która często prowadzi do bardzo złośliwa i trudno znaleźć błędy i awarie w czasie run

Przed rozpoczęciem moim celem nie jest zwięzłość. Ma to na celu ułatwienie zrozumienia dla początkujących użytkowników Scala / FP, zazwyczaj tych pochodzących z języka Java. Na samym końcu połączę wszystkie bity razem, a następnie zwiększę zwięzłość.

Po pierwsze, usingmetoda musi zostać zaktualizowana, aby mogła być używana Try(ponownie, zwięzłość nie jest tutaj celem). Nazwa zostanie zmieniona na 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
              }
          }
      }
    )

Początek powyższej tryUsingAutoCloseablemetody może być mylący, ponieważ wydaje się, że ma dwie listy parametrów zamiast zwyczajowej listy pojedynczych parametrów. Nazywa się to curry. I nie będę szczegółowo omawiać, jak działa curry ani gdzie występuje sporadycznie przydatne. Okazuje się, że dla tej konkretnej przestrzeni problemowej jest to odpowiednie narzędzie do pracy.

Następnie musimy stworzyć metodę, tryPrintToFilektóra utworzy (lub nadpisze istniejącą) Filei zapisze List[String]. Używa, FileWriterktóry jest zamknięty przez a, BufferedWriterktóry z kolei jest zamknięty w PrintWriter. Aby podnieść wydajność, BufferedWriterzdefiniowano domyślny rozmiar buforu znacznie większy niż domyślny dla ,defaultBufferSize i przypisano wartość 65536.

Oto kod (i znowu zwięzłość nie jest tutaj celem):

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

Powyższa tryPrintToFilemetoda jest przydatna, ponieważ wymaga plikuList[String] dane wejściowe i wysyła je do pliku File. Utwórzmy teraz tryWriteToFilemetodę, która pobiera a Stringi zapisuje ją w pliku File.

Oto kod (i pozwolę ci odgadnąć priorytet zwięzłości tutaj):

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

Na koniec warto mieć możliwość pobrania zawartości pliku Filejako pliku String. Chociaż scala.io.Sourcezapewnia wygodną metodę łatwego uzyskiwania zawartości pliku File, closemetoda ta musi być używana w Sourcecelu zwolnienia podstawowej maszyny JVM i uchwytów systemu plików. Jeśli tak się nie stanie, zasób nie zostanie zwolniony, dopóki JVM GC (moduł wyrzucania elementów bezużytecznych) nie wydaSource samej instancji. Nawet wtedy istnieje tylko słaba JVM gwarancja, że finalizemetoda zostanie wywołana przez GC do closezasobu. Oznacza to, że obowiązkiem klienta jest jawne wywołanie pliku . W tym celu potrzebujemy drugiej definicji metody using, która obsługuje .close metody, tak samo jak klient jest odpowiedzialny za oparcie się closena wystąpieniujava.lang.AutoCloseablescala.io.Source

Oto kod do tego (nadal nie jest zwięzły):

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

A oto przykład jego użycia w super prostym czytniku plików strumieniowych (obecnie używany do odczytu plików rozdzielanych tabulatorami z danych wyjściowych bazy danych):

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

Zaktualizowana wersja powyższej funkcji został dostarczony jako odpowiedź na inny, ale powiązanego StackOverflow pytanie .


Teraz, łącząc to wszystko razem z wyodrębnionymi importami (co znacznie ułatwia wklejenie do arkusza Scala obecnego zarówno we wtyczce Eclipse ScalaIDE, jak i IntelliJ Scala, aby ułatwić zrzucanie danych wyjściowych na pulpit, aby można je było łatwiej zbadać za pomocą edytora tekstu), tak wygląda kod (ze zwiększoną zwięzłością):

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

Jako nowicjusz w Scala / FP spędziłem wiele godzin (głównie w frustracji drapiącej głowę), zdobywając powyższą wiedzę i rozwiązania. Mam nadzieję, że pomoże to innym nowicjuszom w Scala / FP szybciej uporać się z tym garbem uczenia się.


2
Niesamowita aktualizacja. Jedynym problemem jest to, że teraz masz około 100 linii kodu, które można zastąpić try-catch-finally. Wciąż kochaj swoją pasję.
Observer

1
@Observer Chciałbym stwierdzić, że jest to niedokładne stwierdzenie. Opisywany przeze mnie wzorzec w rzeczywistości ogranicza liczbę gotowych schematów, które klient musi napisać, aby zapewnić prawidłową obsługę zamykania AutoCloseables, jednocześnie umożliwiając korzystanie ze schematu Scala idiomatic FP za pomocą scala.util.Try. Jeśli spróbujesz osiągnąć te same efekty, które mam, ręcznie wypisując bloki try / catch / final, myślę, że w końcu uzyskasz znacznie więcej schematów, niż sobie wyobrażasz. Tak więc, istnieje znacząca wartość czytelności we wpychaniu wszystkich schematów do 100 wierszy funkcji Scala.
chaotic3quilibrium

1
Przepraszam, jeśli brzmiało to w jakikolwiek sposób obraźliwe. Chodzi mi jednak o to, że nie ma takiej potrzeby w takiej ilości kodu, ponieważ to samo można by osiągnąć poprzez niefunkcjonalne podejście ze znacznie większą prostotą. Osobiście napisałbym próbę - wreszcie z dodatkowymi kontrolami. Jest po prostu krótszy. Gdybym chciał używać wrapperów, ApacheUtils są po to, aby wykorzystać całą brudną robotę. Co więcej, wszystkie standardowe czytniki / pisarze zamykają podstawowe strumienie, więc wielowarstwowe opakowanie nie jest potrzebne. PS: Zmieniłem swój głos z minus jeden na plus jeden, aby wesprzeć twoje wysiłki. Więc proszę, nie podejrzewaj mnie w złych intencjach.
Observer

Bez obrazy.
chaotic3quilibrium

1
Rozumiem twój punkt widzenia. Dzięki za dyskusję, muszę się trochę nad tym zastanowić. Miłego dnia!
Obserwator,

3

Oto przykład zapisu niektórych wierszy do pliku przy użyciu 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

Aby przewyższyć samthebest i współpracowników przed nim, poprawiłem nazewnictwo i zwięzłość:

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

To używa "kaczego pisania", które zależy od refleksji. W wielu kontekstach zależność od refleksji nie jest początkiem.
chaotic3quilibrium

1

Brak zależności, obsługa błędów

  • Używa wyłącznie metod z biblioteki standardowej
  • W razie potrzeby tworzy katalogi dla pliku
  • Zastosowania Eitherdo obsługi błędów

Kod

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

Stosowanie

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

Aktualizacja 2019:

Podsumowanie - Java NIO (lub NIO.2 dla asynchronii) jest nadal najbardziej wszechstronnym rozwiązaniem do przetwarzania plików obsługiwanym w Scali. Poniższy kod tworzy i zapisuje tekst w nowym pliku:

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. Importuj biblioteki Java: IO i NIO
  2. Utwórz Pathobiekt o wybranej nazwie pliku
  3. Przekonwertuj tekst, który chcesz wstawić do pliku, na tablicę bajtów
  4. Pobierz plik jako strumień: OutputStream
  5. Przekaż swoją tablicę bajtów do writefunkcji strumienia wyjściowego
  6. Zamknij strumień

1

Podobnie jak w tej odpowiedzi , oto przykład z fs2(wersja 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

Ta linia pomaga napisać plik z tablicy lub łańcucha.

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

0

Jeśli mimo wszystko masz w swoim projekcie strumienie Akka, zapewnia on jednowierszowy:

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

Akka docs> Przesyłanie strumieniowe pliku IO

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.