2014-09-19 50 views
2

我在斯卡拉非常新,我對位操作功能感到困惑。我希望有人能夠指引我正確的方向?斯卡拉位域序列化

我有以下位字段定義的字節數組:

0-3 - magic number 
    4 - version 
5-7 - payload length in bytes 
8-X - payload, of variable length, as indicated in bits 5-7 

我想序列化這個來回的結構,例如:

MagicNumber: Integer 
Version: Integer 
Length: Integer 
payload: Array[Byte] 

你如何應對在這種情況下位最佳?我見過的大多數例子都處理更高級別的序列化,比如JSON。我想在這種情況下序列化和反序列化TCP二進制數據。

回答

6

您可以使用Scala Pickling或POF或谷歌的Protobuf,但如果你的格式是如此的限制,最簡單的方法就是寫你自己的序列:

case class Data(magicNumber: Int, version: Int, payload: Array[Byte]) 

def serialize(data: Stream[Data]): Stream[Byte] = 
    data.flatMap(x => 
    Array((x.magicNumber << 4 | x.version << 3 | x.payload.length).toByte) ++ x.payload) 

@scala.annotation.tailrec 
def deserialize(binary: Stream[Byte], acc: Stream[Data] = Stream[Data]()): Stream[Data] = 
    if(binary.nonEmpty) { 
    val magicNumber = binary.head >> 4 
    val version = (binary.head & 0x08) >>3 
    val size = binary.head & 0x07 
    val data = Data(magicNumber, version, ByteVector(binary.tail.take(size).toArray)) 
    deserialize(binary.drop(size + 1), acC++ Stream(data)) 
    } else acc 

或者你可以使用Scodec庫(此選項是更好因爲你將不得不自然而然值範圍檢查):

SBT:

libraryDependencies += "org.typelevel" %% "scodec-core" % "1.3.0" 

編解碼器:

case class Data(magicNumber: Int, version: Int, payload: ByteVector) 
    val codec = (uint(4) :: uint(1) :: variableSizeBytes(uint(3), bytes)).as[Data] 

用途:

val encoded = codec.encode(Data(2, 1, bin"01010101".bytes)).fold(sys.error, _.toByteArray) 
    val decoded = codec.decode(BitVector(encoded)).fold(sys.error, _._2) 
+0

謝謝,這有助於。我也試圖看看斯卡拉酸洗,但不幸的是它缺乏示例。我還不知道如何用酸洗/取出來做。現在,這對我來說已經足夠了! – 2014-09-19 15:25:08

+0

scodec比酸洗更加面向位,我已經用它的工作示例更新了答案。但它取決於像scalaz這樣的重型圖書館,不成形。 – dk14 2014-09-19 21:08:57

+0

我正在做對嗎?我似乎無法進行往返:val input2 = new Data(0,3,Array [Byte]('h','e','l','l','o')) val output2 = serialize(Stream(input2)) val output3 =反序列化(輸出2) – 2014-09-20 05:30:05

3

我想看看scodec。基於UDP example,它應該是類似的(未經測試):

import scodec.bits.{ BitVector, ByteVector } 
import scodec.codecs._ 

case class Datagram(
    magicNumber: Int, 
    version: Byte, 
    payload: ByteVector) 

object Datagram { 
    implicit val codec: Codec[Datagram] = { 
    ("magic_number" | int32) :: 
    ("version" | byte) :: 
    variableSizeBytes(int(3), 
     ("payload" | bytes)) 
    }.as[Datagram] 
}