一个任意的
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)
...
在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的数据(根据您提供的长度为一万的数百万个向量),这也是需要考虑的。
如果您的问题实际上要小得多,那么不要太担心-将其打包成XML或使用JSON或一些自定义文本解决方案(或使用DataOutputStream和DataInputStream,它们的性能不佳,并且不会给您提供随机访问)。
如果您的问题实际上更大,则可以定义两个longs列表;首先是适合于Int的那些,然后是实际上需要完整Long的那些(具有索引,以便知道它们在哪里)。数据压缩是一个非常特定于情况的任务-假设您不只想使用java.util.zip-因此,如果没有更多关于数据外观的知识,很难知道除了按照我上面描述的方式将其存储为弱层次结构二进制文件之外还有什么建议。