在Scala中的文件中有效地存储Long序列

ade*_*rtc 3 byte scala binaryfiles bytearray binary-data

所以我有一个关联将一对Ints与一个Vector[Long]最大为10000 的s 相关联,并且我有几十万到一百万这样的数据.我想将它存储在一个文件中,以便以后在Scala中处理.

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

似乎Scala标准库有BytePickle可能是我想要的,但后来被弃用了?

Rex*_*err 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>
Run Code Online (Sandbox Code Playgroud)

这是一种特别方便的阅读和写作格式,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)
...
Run Code Online (Sandbox Code Playgroud)

并在回来的路上

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!
...
Run Code Online (Sandbox Code Playgroud)

还有一些在方便的方法ByteBuffer是不存在的,但你可以implicits添加他们(这里斯卡拉2.10; 2.9使它成为一个普通类,删除extends 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 }
}
Run Code Online (Sandbox Code Playgroud)

无论如何,这样做的原因是你最终会得到不错的性能,当你有数十亿字节的数据时这也是一个考虑因素(听起来你已经给出了数十万个长度达到数千的向量)万).

如果您的问题实际上要小得多,那么不要太担心它 - 将它打包成XML或使用JSON或一些自定义文本解决方案(或使用DataOutputStreamDataInputStream,它们表现不佳并且不会随机给你访问).

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


Rui*_*ves 5

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

val stream = new DataOutputStream(new FileOutputStream("your_file.bin"))
Run Code Online (Sandbox Code Playgroud)

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