转载

更好的Scala I/O: better-files

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

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

本文为你推荐 better-files 。

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

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

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

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

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

实例化

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

importbetter.files._ importjava.io.{File => JFile}  valf = File("/User/johndoe/Documents")// using constructor valf1: File = file"/User/johndoe/Documents"// using string interpolator valf2: File ="/User/johndoe/Documents".toFile// convert a string path to a file valf3: File =newJFile("/User/johndoe/Documents").toScala// convert a Java file to Scala valf4: File = root/"User"/"johndoe"/"Documents"// using root helper to start from root valf5: File = `~` /"Documents"// also equivalent to `home / "Documents"` valf6: File ="/User"/"johndoe"/"Documents"// using file separator DSL valf7: File = home/"Documents"/"presentations"/`..`// Use `..` to navigate up to parent 

文件读写

可以一行搞定:

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

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

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

或者这样:

"hello"`>:` file "world">>: file valbytes: Array[Byte] = file.loadBytes 

流式接口风格:

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

Stream和编码

产生迭代器:

valbytes : Iterator[Byte] = file.bytes valchars : Iterator[Char] = file.chars vallines : Iterator[String] = file.lines valsource : scala.io.BufferedSource = file.newBufferedSource// needs to be closed, unlike the above APIs which auto closes when iterator ends 

编解码:

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

与Java交互

转换成Java对象:

valfile: File = tmp /"hello.txt" valjavaFile : java.io.File = file.toJava valuri : java.net.uri = file.uri valreader : java.io.BufferedReader = file.newBufferedReader valoutputstream : java.io.OutputStream = file.newOutputStream valwriter : java.io.BufferedWriter = file.newBufferedWriter valinputstream : java.io.InputStream = file.newInputStream valpath : java.nio.file.Path = file.path valfs : java.nio.file.FileSystem = file.fileSystem valchannel : java.nio.channel.FileChannel = file.newFileChannel valram : java.io.RandomAccessFile = file.newRandomAccess valfr : java.io.FileReader = file.newFileReader valfw : java.io.FileWriter = file.newFileWriter(append =true) valprinter : java.io.PrintWriter = file.newPrintWriter 

以及

file1.reader > file2.writer // pipes a reader to a writer System.in > file2.out // pipes an inputstream to an outputstream src.pipeTo(sink) // if you don't like symbols  valbytes : Iterator[Byte] = inputstream.bytes valbis : BufferedInputStream = inputstream.buffered valbos : BufferedOutputStream = outputstream.buffered valreader : InputStreamReader = inputstream.reader valwriter : OutputStreamWriter = outputstream.writer valprinter : PrintWriter = outputstream.printWriter valbr : BufferedReader = reader.buffered valbw : BufferedWriter = writer.buffered valmm : MappedByteBuffer = fileChannel.toMappedByteBuffer 

模式匹配

/**  * @returntrueiffileisa directorywithnochildrenora filewithnocontents  */ def isEmpty(file: File): Boolean = file match { caseFile.Type.SymbolicLink(to)=>isEmpty(to)//thismust be firstcasestatementifyou want to handle symlinks specially;elsewill follow link caseFile.Type.Directory(files)=>files.isEmpty caseFile.Type.RegularFile(content)=>content.isEmpty case_=>file.notExists//a file maynotbe oneofthe above e.g. UNIX pipes, sockets, devices etc } //oras extractorsonLHS: val File.Type.Directory(researchDocs) = home/"Downloads"/"research" 

通配符

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

甚至使用正则表达式:

valmatches = dir.glob("^//w*$")(syntax = File.PathMatcherSyntax.regex) 

文件系统操作

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

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 contents file.renameTo(newName: String) file.moveTo(destination) file.copyTo(destination)// unlike the default API, also works on directories (copies recursively) file.linkTo(destination)// ln file destination file.symbolicLinkTo(destination)// ln -s file destination file.{checksum, md5, sha1, sha256, sha512, digest}// also works for directories file.setOwner(user: String)// chown user file file.setGroup(group: String)// chgrp group file Seq(file1, file2) >: file3 // same as cat file1 file2 > file3 Seq(file1, file2) >>: file3 // same as cat file1 file2 >> file3 file.isReadLocked/ file.isWriteLocked /file.isLocked File.newTemporaryDirectory()/ File.newTemporaryFile() // create temp dir/file 

UNIX DSL

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

import better.files_, Cmds._ // must import Cmds._ to bring in these utils pwd / cwd // current dir cp(file1, file2) mv(file1, file2) rm(file)/*or*/del(file) ls(file)/*or*/dir(file) ln(file1, file2)// hard link ln_s(file1, file2) // soft link cat(file1) cat(file1) >>: file touch(file) mkdir(file) mkdirs(file)// mkdir -p chown(owner, file) chgrp(owner, file) chmod_+(permission, files)// add permission chmod_-(permission, files)// remove permission md5(file) / sha1(file) / sha256(file) / sha512(file) unzip(zipFile)(targetDir) zip(file*)(zipFile) 

文件属性

file.name // simpler than java.io.File#getName file.extension file.contentType file.lastModifiedTime // returns JSR-310time file.owner /file.group file.isDirectory /file.isSymbolicLink /file.isRegularFile file.isHidden file.hide() /file.unhide() file.isOwnerExecutable /file.isGroupReadable // etc. seefile.permissions file.size //foradirectory, computes the directory size file.posixAttributes /file.dosAttributes // seefile.attributes file.isEmpty // trueiffilehasnocontent (ornochildrenifdirectory)ordoes not exist file.isParentOf /file.isChildOf /file.isSiblingOf /file.siblings 

chmod :

importjava.nio.file.attribute.PosixFilePermission file.addPermission(PosixFilePermission.OWNER_EXECUTE) // chmod +X file file.removePermission(PosixFilePermission.OWNER_WRITE) // chmod -w file assert(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) 

文件比较

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

排序:

valfiles = myDir.list.toSeq files.sorted(File.Order.byName)  files.max(File.Order.bySize)  files.min(File.Order.byDepth)  files.max(File.Order.byModificationTime)  files.sorted(File.Order.byDirectoriesFirst) 

压缩解压缩

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

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

Auto-close Java closeables:

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

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

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

可以写成:

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

Scanner

valdata = t1 << s"""  | Hello World  | 1 true 2 3 """.stripMargin valscanner: 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文件监控:

importjava.nio.file.{StandardWatchEventKinds => EventType} valservice: java.nio.file.WatchService = myDir.newWatchService myDir.register(service, events = Seq(EventType.ENTRY_CREATE, EventType.ENTRY_DELETE)) 

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

valwatcher =newThreadBackedFileMonitor(myDir, recursive =true) { overridedefonCreate(file: File) = println(s"$file got created") overridedefonModify(file: File) = println(s"$file got modified") overridedefonDelete(file: File) = println(s"$file got deleted") } watcher.start() 

或者用下面的写法:

importjava.nio.file.{Path, StandardWatchEventKinds => EventType, WatchEvent}  valwatcher =newThreadBackedFileMonitor(myDir, recursive =true) { overridedefdispatch(eventType: WatchEvent.Kind[Path], file: File) = eventTypematch{ caseEventType.ENTRY_CREATE => println(s"$file got created") caseEventType.ENTRY_MODIFY => println(s"$file got modified") caseEventType.ENTRY_DELETE => println(s"$file got deleted")  } } 

Akka 风格的文件监控

importakka.actor.{ActorRef, ActorSystem} importbetter.files._, FileWatcher._  implicit valsystem = ActorSystem("mySystem")  valwatcher: ActorRef = (home/"Downloads").newWatcher(recursive =true)  // register partial function for an event watcher ! on(EventType.ENTRY_DELETE) {  casefileiffile.isDirectory => println(s"$file got deleted") }  // watch for multiple events watcher ! 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/
正文到此结束
Loading...