3

因此,我有一个关联,它将一对Ints 与Vector[Long]最大大小为 10000 的 a 相关联,并且我拥有从几十万到一百万的此类数据。我想将其存储在一个文件中,以便以后在 Scala 中处理。

Byte显然,以纯文本格式存储它会占用太多空间,所以我一直试图通过编写流来弄清楚如何做到这一点。但是我不太确定这是否可行,因为在我看来byteValue()aLong返回的Byte表示仍然是 4 个字节长,因此我不会节省任何空间?我没有太多使用二进制格式的经验。

似乎 Scala 标准库有一个BytePickle可能是我一直在寻找的,但后来被弃用了?

4

3 回答 3

7

任意Long长度大约为 19.5 个 ASCII 数字,但只有 8 个字节长,因此如果以二进制形式编写,您将节省约 2 倍的成本。现在,可能大多数值实际上并未占用全部 8 个字节,在这种情况下,您可以自己定义一些压缩方案。

无论如何,您最好使用java.nio.ByteBuffer和朋友来编写块数据。二进制数据在块中读取效率最高,您可能希望文件可以随机访问,在这种情况下,您希望数据看起来像这样:

<some unique binary header that lets you check the file type>
<int saying how many records you have>
<offset of the first record>
<offset of the second record>
...
<offset of the last record>
<int><int><length of vector><long><long>...<long>
<int><int><length of vector><long><long>...<long>
...
<int><int><length of vector><long><long>...<long>

这是一种特别方便的阅读和写作格式,ByteBuffer因为您提前知道所有内容将有多大。这样你就可以

val fos = new FileOutputStream(myFileName)
val fc = fos.getChannel // java.nio.channel.FileChannel
val header = ByteBuffer.allocate(28)
header.put("This is my cool header!!".getBytes)
header.putInt(data.length)
fc.write(header)
val offsets = ByteBuffer.allocate(8*data.length)
data.foldLeft(28L+8*data.length){ (n,d) =>
  offsets.putLong(n)
  n = n + 12 + d.vector.length*8
}
fc.write(offsets)
...

在回来的路上

val fis = new FileInputStream(myFileName)
val fc = fis.getChannel
val header = ByteBuffer.allocate(28)
fc.read(header)
val hbytes = new Array[Byte](24)
header.get(hbytes)
if (new String(hbytes) != "This is my cool header!!") ???
val nrec = header.getInt
val offsets = ByteBuffer.allocate(8*nrec)
fc.read(offsets)
val offsetArray = offsets.getLongs(nrec)  // See below!
...

有一些方便的方法ByteBuffer不存在,但是您可以使用隐式添加它们(这里对于 Scala 2.10;使用 2.9 使其成为普通类,删除,并提供从toextends AnyVal的隐式转换):ByteBufferRichByteBuffer

implicit class RichByteBuffer(val b: java.nio.ByteBuffer) extends AnyVal {
  def getBytes(n: Int) = { val a = new Array[Byte](n); b.get(a); a }
  def getShorts(n: Int) = { val a = new Array[Short](n); var i=0; while (i<n) { a(i)=b.getShort(); i+=1 } ; a }
  def getInts(n: Int) = { val a = new Array[Int](n); var i=0; while (i<n) { a(i)=b.getInt(); i+=1 } ; a }
  def getLongs(n: Int) = { val a = new Array[Long](n); var i=0; while (i<n) { a(i)=b.getLong(); i+=1 } ; a }
  def getFloats(n: Int) = { val a = new Array[Float](n); var i=0; while (i<n) { a(i)=b.getFloat(); i+=1 } ; a }
  def getDoubles(n: Int) = { val a = new Array[Double](n); var i=0; while (i<n) { a(i)=b.getDouble(); i+=1 } ; a }
}

无论如何,这样做的原因是您最终会获得不错的性能,当您拥有数十 GB 的数据时,这也是一个考虑因素(听起来您已经给出了数十万个长度为万)。

如果您的问题实际上要小得多,那么不要太担心它——将它打包成 XML 或使用 JSON 或一些自定义文本解决方案(或使用DataOutputStreamand DataInputStream,它的性能不太好,不会给你随机使用权)。

如果您的问题实际上更大,您可以定义两个长列表;首先,那些将适合的Int,比如说,然后是那些实际上需要一个完整的Long(带有索引,所以你知道它们在哪里)。数据压缩是一个非常特定于案例的任务——假设你不只是想使用——java.util.zip所以如果没有更多关于数据是什么样子的知识,除了将它存储为弱层次结构之外,很难知道推荐什么如上所述的二进制文件。

于 2013-01-04T00:03:21.560 回答
5

请参阅 Java 的DataOutputStream. 它允许简单有效地写入原始类型和Strings字节流。特别是,你想要这样的东西:

val stream = new DataOutputStream(new FileOutputStream("your_file.bin"))

然后,您可以使用等效DataInputStream方法再次从该文件读取变量。

于 2013-01-03T23:31:08.170 回答
2

我使用scala-io,scala-arm来编写Long-s 的二进制流。库本身应该是一种 Scala 的做事方式,但这些不在 Scalamaster分支中——也许有人知道为什么?我不时使用它们。

1)克隆scala-io

git clone https://github.com/scala-incubator/scala-io.git

scala-io/package换成你Build.scalaval scalaVersion

sbt package

2)克隆scala-arm

git clone https://github.com/jsuereth/scala-arm.git

scala-arm/package换成你build.scalascalaVersion :=

sbt package

3)在不太远的地方复制:

scala-io/core/target/scala-xxx/scala-io-core_xxx-0.5.0-SNAPSHOT.jar

scala-io/file/target/scala-xxx/scala-io-file_xxx-0.5.0-SNAPSHOT.jar

scala-arm/target/scala-xxx/scala-arm_xxx-1.3-SNAPSHOT.jar

4)启动REPL: scala -classpath "/opt/scala-io/scala-io-core_2.10-0.5.0-SNAPSHOT.jar: /opt/scala-io/scala-io-file_2.10-0.5.0-SNAPSHOT.jar: /opt/scala-arm/scala-arm_2.10-1.3-SNAPSHOT.jar"

5):paste实际代码:

import scalax.io._

// create data stream
val EOData = Vector(0xffffffffffffffffL)
val data = List(
  (0, Vector(0L,1L,2L,3L))
  ,(1, Vector(4L,5L))
  ,(2, Vector(6L,7L,8L))
  ,(3, Vector(9L))  
)
var it = Iterator[Long]()
for (rec <- data) {
  it = it ++ Vector(rec._1).iterator.map(_.toLong)
  it = it ++ rec._2.iterator
  it = it ++ EOData.iterator
}

// write data at once
val out: Output = Resource.fromFile("/tmp/data")
out.write(it)(OutputConverter.TraversableLongConverter)  
于 2013-01-04T00:38:47.897 回答