有效地在scala中读取二进制文件的方法

时间:2015-10-23 14:13:58

标签: scala

我正在尝试读取一个二进制文件(16 MB),其中我只有16位编码的整数。所以为此,我使用了1 MB的块,它给了我一个字节数组。为了我自己的需要,我将这个字节数组转换为一个短数组,并使用以下函数进行转换,但是使用缓冲区读取此文件并将其转换为短数组需要5秒钟,这是否比我的解决方案更快?

 def convert(in: Array[Byte]): Array[Short] = in.grouped(2).map {
    case Array(one) => (one << 8 | (0 toByte)).toShort
    case Array(hi, lo) => (hi << 8 | lo).toShort
  } .toArray

  val startTime = System.nanoTime()

val file = new RandomAccessFile("foo","r")
val defaultBlockSize = 1 * 1024 * 1024
    val byteBuffer = new Array[Byte](defaultBlockSize)
    val chunkNums = (file.length / defaultBlockSize).toInt
    for (i <- 1 to chunkNums) {
      val seek = (i - 1) * defaultBlockSize
      file.seek(seek)
      file.read(byteBuffer)
      val s = convert(byteBuffer)
      println(byteBuffer size)
    }

val stopTime = System.nanoTime()
  println("Perf of = " + ((stopTime - startTime) / 1000000000.0) + " for a duration of " + duration + " s")

2 个答案:

答案 0 :(得分:2)

16 MB,除非您在功能手机或其他设备上运行,否则很容易适合内存。没有必要把它搞砸并使逻辑变得更难。

使用java.nio.files.Files.readAllBytes

立即吞下整个文件
val buffer = java.nio.files.Files.readAllBytes(myfile.toPath)

假设您没有使用Java 1.6。 (如果您坚持使用Java 1.6,请使用myfile.size预先分配缓冲区大小,并在read上使用FileInputStream一次性完成所有操作。这并不困难,只是别忘了关闭它!)

然后,如果你不想自己转换,你可以

val bb = java.nio.ByteBuffer.wrap(buffer)
bb.order(java.nio.ByteOrder.nativeOrder)
val shorts = new Array[Short](buffer.length/2)
bb.asShortBuffer.get(shorts)

你已经完成了。

请注意,这是所有Java的东西;这里没有Scala特有的保存语法。

如果你想知道为什么这样比你的代码快得多,那是因为grouped(2)将字节装箱并将它们放在一个数组中。对于你想要的每一个短片,这都是三个分配!您可以通过直接索引数组来自己完成,而且速度很快,但是为什么当ByteBuffer和朋友完全按照您的需要做什么时,您会想要这样做?

如果您真的真的关心最后一个(奇数)字节,那么您可以使用(buffer.length + 1)/2作为shorts的大小,并使用{ {1}}获取最后一个字节。

答案 1 :(得分:0)

出现了一些问题:

如果byteBuffer的大小始终为1024 * 1024,那么case Array(one)中的convert将永远不会被使用,因此不需要进行模式匹配。

此外,您可以避免使用尾递归函数的for循环。在val byteBuffer = ...行之后,您可以用以下内容替换chunkNums和for循环:

@scala.annotation.tailrec
def readAndConvert(b: List[Array[Short]], file : RandomAccessFile) : List[Array[Short]] = {
  if(file.read(byteBuffer) < 0)
    b
  else {
    file.skipBytes(1024*1024)
    readAndConvert(b.+:(convert(byteBuffer)), file)
  }
}

val sValues = readAndConvert(List.empty[Array[Short]], file)

注意:因为列表预先加载比附加上面的循环快得多,所以从文件中的阅读顺序开始按相反的顺序获取转换后的值。