所以我有一个关联,将一对Int
s与Vector[Long]
相关联,其大小可以达到10000,并且我有几十万到一百万的这样的数据。我想把它存储在一个文件中,以便以后在Scala中处理。
显然,将其存储在纯文本格式中会占用太多空间,所以我一直在试图通过编写Byte
流来弄清楚如何做到这一点。然而,我不太确定这是否会起作用,因为在我看来,Long
的byteValue()
返回仍然是4字节长的Byte
表示,因此我不会节省任何空间?我没有太多使用二进制格式的经验。
似乎Scala标准库有一个BytePickle
,这可能是我正在寻找的,但已经被弃用了?
一个任意的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版本中,让它成为一个普通类,去掉extends AnyVal
,并提供从ByteBuffer
到RichByteBuffer
的隐式转换):
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的数据时,这也是一个考虑因素(这听起来像是你已经给出了数十万个长度高达10,000的向量)。
如果你的问题实际上要小得多,那就不要太担心——把它打包成XML或使用JSON或一些自定义文本解决方案(或使用DataOutputStream
和DataInputStream
,它们的性能不太好,不会给你随机访问)。
如果你的问题实际上更大,你可以定义两个 long列表;首先是那些适合Int
的,然后是那些实际上需要一个完整的Long
的(带索引,这样你就知道它们在哪里)。数据压缩是一项非常具体的任务——假设您不只是想使用java.util.zip
——所以如果没有更多关于数据的知识,除了像我上面描述的那样将其存储为弱分层二进制文件之外,很难知道应该推荐什么。
参见Java的DataOutputStream
。它允许简单有效地将基本类型和Strings
写入字节流。特别是,您需要这样的内容:
val stream = new DataOutputStream(new FileOutputStream("your_file.bin"))
然后可以使用等效的DataInputStream
方法再次从该文件读取到变量。
我使用scala-io
, scala-arm
来编写Long
-s的二进制流。库本身应该是Scala的方式来做事情,但这些不在Scala master
分支中-也许有人知道为什么?我经常使用它们。
scala-io
:
git clone https://github.com/scala-incubator/scala-io.git
转到scala-io/package
并将Build.scala
, val scalaVersion
更改为您的
sbt package
scala-arm
:
git clone https://github.com/jsuereth/scala-arm.git
转到scala-arm/package
并将build.scala
, scalaVersion :=
更改为您的
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"
: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)