如何在Scala中将文件读取为字节数组

84

我可以找到很多例子,但它们似乎要么主要依赖Java库,要么只是读取字符/行等。

我只想使用Scala库读取一些文件并获得一个字节数组 - 有人可以帮我吗?


3
我认为依赖Java库是(几乎?)每个人都会做的事情,包括Scala库。例如,请参阅scala.io.Source的源代码。 - Philippe
2
你并没有使用不同的编程语言,而是使用了一个标准的JVM API,它已经被证明足够好,不需要被替换! - Duncan McGregor
4
Java类是如何实现的呢?在深处,有一个本地方法:它只有签名,没有Java实现,并依赖于特定于操作系统的C实现。这难道也不算作弊吗? :) - Philippe
2
应该说,Scala在.Net上确实使这个问题更加紧迫。 - Duncan McGregor
4
@Philippe:当然,使用C只不过是在汇编语言上作弊 :P ... 我的意思只是,通常语言之间的界限非常明确,而Scala和Java有点融为一体。 - fgysin
显示剩余4条评论
8个回答

143

Java 7:

import java.nio.file.{Files, Paths}

val byteArray = Files.readAllBytes(Paths.get("/path/to/file"))

我相信这是可能的最简单的方式。只需利用现有的工具。NIO.2 很棒。


1
我认为任何未绑定到JVM <7的人都应该使用它。 - fedesilva

47
这应该可以工作(针对Scala 2.8):
val bis = new BufferedInputStream(new FileInputStream(fileName))
val bArray = Stream.continually(bis.read).takeWhile(-1 !=).map(_.toByte).toArray

我认为这是一个很好的示例,将Java API函数包装以获得Stream语义。非常感谢。 - qu1j0t3
3
如果您没有导入Java路径,那么 val bis = new java.io.BufferedInputStream(new java.io.FileInputStream(fileName)); 的意思是创建一个名为bis的变量,该变量将打开指定文件名的输入流并进行缓冲处理。 - BeniBela
1
使用这种方法,关闭文件是否也需要,还是隐含的? - Max
1
你需要自己关闭它。 - Tony K.
15
这种方法速度较慢,因为需要处理每个字节。理想情况下,I/O操作应该是以块为基础的。 - Dibbeke
我将其与缓冲方法进行了基准测试,在我的测试中它要慢大约500倍。(测试配置:计算一个14 MB文件的CRC32校验值,该文件从SSD中反复读取,位于系统文件缓存中;Intel Core i7第二代;16GB RAM)。 - morfizm

6

scala.io.Source存在问题,在读取二进制文件时不应使用它。

出错信息可以按照此处的说明进行复现:https://github.com/liufengyun/scala-bug

在文件data.bin中,包含十六进制数0xea,它的二进制表示为11101010,应该转换为十进制数234

main.scala文件中包含两种读取文件的方式:

import scala.io._
import java.io._

object Main {
  def main(args: Array[String]) {
    val ss = Source.fromFile("data.bin")
    println("Scala:" + ss.next.toInt)
    ss.close

    val bis = new BufferedInputStream(new FileInputStream("data.bin"))
    println("Java:" + bis.read)
    bis.close
  }
}

当我运行scala main.scala时,程序输出如下:
Scala:205
Java:234

Java库生成正确的输出,而Scala库则不是。


11
如果我将编码设置为 Source.fromFile("data.bin", "ISO8859-1"),它能够正常工作。 - fengyun liu
6
或许这很有帮助,但实际上,这并不是一个答案。在回答中引入一个新问题是没有建设性的,应该在其他地方进行讨论。 - Benjamin

5
val is = new FileInputStream(fileName)
val cnt = is.available
val bytes = Array.ofDim[Byte](cnt)
is.read(bytes)
is.close()

3
这不是一个有效的解决方案。根据InputStream.available的Java文档: 请注意,虽然某些InputStream的实现将返回流中的总字节数,但很多实现不会这样做。使用此方法的返回值分配一个旨在容纳此流中所有数据的缓冲区是错误的。 - m.bemowski

4
你可能还可以考虑使用 scalax.io
scalax.io.Resource.fromFile(fileName).byteArray

5
发现该代码库的最后操作是6年前,它仍然相关吗? - akauppi

2
你可以使用Apache Commons CompressIOUtils
import org.apache.commons.compress.utils.IOUtils

val file = new File("data.bin")
IOUtils.toByteArray(new FileInputStream(file))

1
我不得不导入import org.apache.commons.io.IOUtils而不是建议的import。 - niid

0
使用Scala Future和Java NIO2进行异步文件读取
  def readFile(path: Path)(implicit ec: ExecutionContext): Future[Array[Byte]] = {
    val p = Promise[Array[Byte]]()
    try {
      val channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)
      val buffer = ByteBuffer.allocate(channel.size().toInt);
      channel.read(buffer, 0L, buffer, onComplete(channel, p))
    }
    catch {
      case t: Exception => p.failure(t)
    }
    p.future
  }

  private def onComplete(channel: AsynchronousFileChannel, p: Promise[Array[Byte]]) = {
    new CompletionHandler[Integer, ByteBuffer]() {
      def completed(res: Integer, buffer: ByteBuffer): Unit = {
        p.complete(Try {
          buffer.array()
        })
      }

      def failed(t: Throwable, buffer: ByteBuffer): Unit = {
        p.failure(t)
      }
    }
  }

-2
我使用了以下代码来读取CSV文件。
import scala.io.StdIn.readLine
import scala.io.Source.fromFile

readFile("C:/users/xxxx/Downloads/", "39025968_ccccc_1009.csv")

def readFile(loc :String,filenm :String): Unit ={

  var flnm = fromFile(s"$loc$filenm") // Imported fromFile package

  println("Files testing")
  /*for (line <- flnm.getLines()) {
    printf("%4d %s\n", line.length, line)
  }*/
  flnm.getLines().foreach(println) // getLines() is imported from readLines.
  flnm.close() 
}

4
考虑到这个问题已经存在了九年,并且已经有很多答案被提交,指出你的新答案与之前的答案有何不同是很有帮助的。(而且包含已经注释掉的代码看起来很懒散。) - jwvh
是的,其他答案明确显示返回字节数组。这真的不太清楚。 - Alistair McIntyre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接