0
0

更好的Scala I/O: better-files

鸟窝 发表于 2016年06月15日 10:43 | Hits: 1629
Tag: Scala

对于使用Scala的程序员来说, I/O操作如文件的读写通常使用scala.io.Source来实现。但是这个类功能还是欠缺的,而且功能混乱,因此在Scala类库的增强提案(Scala IO fix-up/overhaul)中如何改进它争论相当的大,甚至有些开发者提议将这个库废掉,让社区实现的第三方来完成这方面的工作,或者引导开发者使用java.nio来实现I/O操作。

当然,作为一个使用Scala的公司来说,可能会自己实现了辅助的I/O操作的方法, 比如类似FileUtils等名称的一些类。Java程序员可能已经熟悉了使用java.nio.file.FilesGuavaApache common-iojodd FileUtil等开源I/O库,但是如果使用Scala进行I/O操作时,虽然还是可以使用这些Java I/O库,但是毕竟还是不是那么纯粹,因此,我们可以关注一下Scala实现的I/O库,比如sbt ioAmmonite-Opsbetter-files等。

本文为你推荐better-files

为什么推荐better-files呢?让我们看看它的功能,就明白它的简单而强大了,就像一把I/O操作的瑞士军刀。

要使用better-files,只需加入下面的依赖:

1
libraryDependencies += "com.github.pathikrit" %% "better-files" % version

它对Java NIO库进行了包装,不依赖其它的第三方库。

以下介绍摘译自better-files的官方文档

实例化

下面的实例都是等价的,才可以采用多种方式得到File对象。可以通过字符串、String interpolator, Java File、隐式转换、定义的常量和操作符"/"等产生File对象。

1234567891011
import better.files._import java.io.{File => JFile}val f = File("/User/johndoe/Documents")                      // using constructorval f1: File = file"/User/johndoe/Documents"                 // using string interpolatorval f2: File = "/User/johndoe/Documents".toFile              // convert a string path to a fileval f3: File = new JFile("/User/johndoe/Documents").toScala  // convert a Java file to Scalaval f4: File = root/"User"/"johndoe"/"Documents"             // using root helper to start from rootval f5: File = `~` / "Documents"                             // also equivalent to `home / "Documents"`val f6: File = "/User"/"johndoe"/"Documents"                 // using file separator DSLval f7: File = home/"Documents"/"presentations"/`..`         // Use `..` to navigate up to parent

文件读写

可以一行搞定:

1234
val file = root/"tmp"/"test.txt"file.overwrite("hello")file.appendLine().append("world")assert(file.contentAsString == "hello\nworld")

类似C++/Shell风格的读写,和上面的功能一样:

123
file < "hello"     // same as file.overwrite("hello")file << "world"    // same as file.appendLines("world")assert(file! == "hello\nworld")

或者这样:

123
"hello" `>:` file"world" >>: fileval bytes: Array[Byte] = file.loadBytes

流式接口风格:

12345678
(root/"tmp"/"diary.txt") .createIfNotExists()   .appendLine() .appendLines("My name is", "Inigo Montoya") .moveTo(home/"Documents") .renameTo("princess_diary.txt") .changeExtensionTo(".md") .lines

Stream和编码

产生迭代器:

1234
val bytes  : Iterator[Byte]            = file.bytesval chars  : Iterator[Char]            = file.charsval lines  : Iterator[String]          = file.linesval source : scala.io.BufferedSource   = file.newBufferedSource // needs to be closed, unlike the above APIs which auto closes when iterator ends

编解码:

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

与Java交互

转换成Java对象:

1234567891011121314
val file: File = tmp / "hello.txt"val javaFile     : java.io.File                 = file.toJavaval uri          : java.net.uri                 = file.urival reader       : java.io.BufferedReader       = file.newBufferedReader val outputstream : java.io.OutputStream         = file.newOutputStream val writer       : java.io.BufferedWriter       = file.newBufferedWriter val inputstream  : java.io.InputStream          = file.newInputStreamval path         : java.nio.file.Path           = file.pathval fs           : java.nio.file.FileSystem     = file.fileSystemval channel      : java.nio.channel.FileChannel = file.newFileChannelval ram          : java.io.RandomAccessFile     = file.newRandomAccessval fr           : java.io.FileReader           = file.newFileReaderval fw           : java.io.FileWriter           = file.newFileWriter(append = true)val printer      : java.io.PrintWriter          = file.newPrintWriter

以及

12345678910111213
file1.reader > file2.writer       // pipes a reader to a writerSystem.in > file2.out             // pipes an inputstream to an outputstreamsrc.pipeTo(sink)                  // if you don't like symbolsval bytes   : Iterator[Byte]        = inputstream.bytesval bis     : BufferedInputStream   = inputstream.buffered  val bos     : BufferedOutputStream  = outputstream.buffered   val reader  : InputStreamReader     = inputstream.readerval writer  : OutputStreamWriter    = outputstream.writerval printer : PrintWriter           = outputstream.printWriterval br      : BufferedReader        = reader.bufferedval bw      : BufferedWriter        = writer.bufferedval mm      : MappedByteBuffer      = fileChannel.toMappedByteBuffer

模式匹配

1234567891011
/** * @return true if file is a directory with no children or a file with no contents */def isEmpty(file: File): Boolean = file match {  case File.Type.SymbolicLink(to) => isEmpty(to)  // this must be first case statement if you want to handle symlinks specially; else will follow link  case File.Type.Directory(files) => files.isEmpty  case File.Type.RegularFile(content) => content.isEmpty  case _ => file.notExists    // a file may not be one of the above e.g. UNIX pipes, sockets, devices etc}// or as extractors on LHS:val File.Type.Directory(researchDocs) = home/"Downloads"/"research"

通配符

1234
val dir = "src"/"test"val matches: Iterator[File] = dir.glob("**/*.{java,scala}")// above code is equivalent to:dir.listRecursively.filter(f => f.extension == Some(".java") || f.extension == Some(".scala"))

甚至使用正则表达式:

1
val matches = dir.glob("^\\w*$")(syntax = File.PathMatcherSyntax.regex)

文件系统操作

文件系统操作也非常的便利:

123456789101112131415
file.touch()file.delete()     // unlike the Java API, also works on directories as expected (deletes children recursively)file.clear()      // If directory, deletes all children; if file clears contentsfile.renameTo(newName: String)file.moveTo(destination)file.copyTo(destination)       // unlike the default API, also works on directories (copies recursively)file.linkTo(destination)                     // ln file destinationfile.symbolicLinkTo(destination)             // ln -s file destinationfile.{checksum, md5, sha1, sha256, sha512, digest}   // also works for directoriesfile.setOwner(user: String)    // chown user filefile.setGroup(group: String)   // chgrp group fileSeq(file1, file2) >: file3     // same as cat file1 file2 > file3Seq(file1, file2) >>: file3    // same as cat file1 file2 >> file3file.isReadLocked / file.isWriteLocked / file.isLockedFile.newTemporaryDirectory() / File.newTemporaryFile() // create temp dir/file

UNIX DSL

甚至提供了UNIX命令风格的操作:

1234567891011121314151617181920
import better.files_, Cmds._   // must import Cmds._ to bring in these utilspwd / cwd     // current dircp(file1, file2)mv(file1, file2)rm(file) /*or*/ del(file)ls(file) /*or*/ dir(file)ln(file1, file2)     // hard linkln_s(file1, file2)   // soft linkcat(file1)cat(file1) >>: filetouch(file)mkdir(file)mkdirs(file)         // mkdir -pchown(owner, file)chgrp(owner, file)chmod_+(permission, files)  // add permissionchmod_-(permission, files)  // remove permissionmd5(file) / sha1(file) / sha256(file) / sha512(file)unzip(zipFile)(targetDir)zip(file*)(zipFile)

文件属性

12345678910111213
file.name       // simpler than java.io.File#getNamefile.extensionfile.contentTypefile.lastModifiedTime     // returns JSR-310 timefile.owner / file.groupfile.isDirectory / file.isSymbolicLink / file.isRegularFilefile.isHiddenfile.hide() / file.unhide()file.isOwnerExecutable / file.isGroupReadable // etc. see file.permissionsfile.size                 // for a directory, computes the directory sizefile.posixAttributes / file.dosAttributes  // see file.attributesfile.isEmpty      // true if file has no content (or no children if directory) or does not existfile.isParentOf / file.isChildOf / file.isSiblingOf / file.siblings

chmod:

123456789
import java.nio.file.attribute.PosixFilePermissionfile.addPermission(PosixFilePermission.OWNER_EXECUTE)      // chmod +X filefile.removePermission(PosixFilePermission.OWNER_WRITE)     // chmod -w fileassert(file.permissionsAsString == "rw-r--r--")// The following are all equivalent:assert(file.permissions contains PosixFilePermission.OWNER_EXECUTE)assert(file(PosixFilePermission.OWNER_EXECUTE))assert(file.isOwnerExecutable)

文件比较

1234
file1 == file2    // equivalent to `file1.isSamePathAs(file2)`file1 === file2   // equivalent to `file1.isSameContentAs(file2)` (works for regular-files and directories)file1 != file2    // equivalent to `!file1.isSamePathAs(file2)`file1 =!= file2   // equivalent to `!file1.isSameContentAs(file2)`

排序:

123456
val files = myDir.list.toSeqfiles.sorted(File.Order.byName) files.max(File.Order.bySize) files.min(File.Order.byDepth) files.max(File.Order.byModificationTime) files.sorted(File.Order.byDirectoriesFirst)

压缩解压缩

1234567891011121314
// Unzipping:val zipFile: File = file"path/to/research.zip"val research: File = zipFile.unzipTo(destination = home/"Documents"/"research") // Zipping:val zipFile: File = directory.zipTo(destination = home/"Desktop"/"toEmail.zip")// Zipping/Unzipping to temporary files/directories:val someTempZipFile: File = directory.zip()val someTempDir: File = zipFile.unzip()assert(directory === someTempDir)// Gzip handling:File("countries.gz").newInputStream.gzipped.lines.take(10).foreach(println)

轻量级的ARM (自动化的资源管理)

Auto-close Java closeables:

1234
for {  in <- file1.newInputStream.autoClosed  out <- file2.newOutputStream.autoClosed} in.pipeTo(out)

better-files提供了更加便利的管理,因此下面的代码

123
for { reader <- file.newBufferedReader.autoClosed} foo(reader)

可以写成:

123456
for { reader <- file.bufferedReader    // returns ManagedResource[BufferedReader]} foo(reader)// or simply:file.bufferedReader.map(foo)

Scanner

1234567891011
val data = t1 << s"""  | Hello World  | 1 true 2 3""".stripMarginval scanner: Scanner = data.newScanner()assert(scanner.next[String] == "Hello")assert(scanner.lineNumber == 1)assert(scanner.next[String] == "World")assert(scanner.next[(Int, Boolean)] == (1, true))assert(scanner.tillEndOfLine() == " 2 3")assert(!scanner.hasNext)

你甚至可以写定制的Scanner。

文件监控

普通的Java文件监控:

123
import java.nio.file.{StandardWatchEventKinds => EventType}val service: java.nio.file.WatchService = myDir.newWatchServicemyDir.register(service, events = Seq(EventType.ENTRY_CREATE, EventType.ENTRY_DELETE))

better-files抽象了一个简单的接口:

123456
val watcher = new ThreadBackedFileMonitor(myDir, recursive = true) {  override def onCreate(file: File) = println(s"$file got created")  override def onModify(file: File) = println(s"$file got modified")  override def onDelete(file: File) = println(s"$file got deleted")}watcher.start()

或者用下面的写法:

123456789
import java.nio.file.{Path, StandardWatchEventKinds => EventType, WatchEvent}val watcher = new ThreadBackedFileMonitor(myDir, recursive = true) {  override def dispatch(eventType: WatchEvent.Kind[Path], file: File) = eventType match {    case EventType.ENTRY_CREATE => println(s"$file got created")    case EventType.ENTRY_MODIFY => println(s"$file got modified")    case EventType.ENTRY_DELETE => println(s"$file got deleted")  }}

Akka 风格的文件监控

1234567891011121314151617
import akka.actor.{ActorRef, ActorSystem}import better.files._, FileWatcher._implicit val system = ActorSystem("mySystem")val watcher: ActorRef = (home/"Downloads").newWatcher(recursive = true)// register partial function for an eventwatcher ! on(EventType.ENTRY_DELETE) {      case file if file.isDirectory => println(s"$file got deleted") }// watch for multiple eventswatcher ! when(events = EventType.ENTRY_CREATE, EventType.ENTRY_MODIFY) {     case (EventType.ENTRY_CREATE, file) => println(s"$file got created")  case (EventType.ENTRY_MODIFY, file) => println(s"$file got modified")}

原文链接: http://colobu.com/2016/05/11/better-files-Simple-safe-and-intuitive-Scala-I-O/

0     0

我要给这篇文章打分:

可以不填写评论, 而只是打分. 如果发表评论, 你可以给的分值是-5到+5, 否则, 你只能评-1, +1两种分数. 你的评论可能需要审核.

评价列表(0)