0%

BitField格式化

维基百科上对于BitField是这样解释的:

https://zh.wikipedia.org/wiki/%E4%BD%8D%E6%AE%B5

位段(或称“位域”,Bit field)为一种数据结构,可以把数据以的形式紧凑的储存,并允许程序员对此结构的位进行操作。这种数据结构的好处:

  • 可以使数据单元节省储存空间,当程序需要成千上万个数据单元时,这种方法就显得尤为重要。
  • 位段可以很方便的访问一个整数值的部分内容从而可以简化程序源代码。

FileCoin 有一个 具体的实现:[Go-bitfield][https://github.com/filecoin-project/go-bitfield]

Advanced RLE+ implementation

Features iterator based primitives that scale with number of runs instead of number of bits.

这个实现包含一个Run-length-encoder(RLE)。援引维基百科上面的解释:

游程编码(英语:run-length encoding,缩写RLE),又称行程长度编码变动长度编码法,是一种与资料性质无关的无损数据压缩技术,基于“使用变动长度的码来取代连续重复出现的原始资料”来实现压缩。

说明

举例来说,一组资料串”AAAABBBCCDEEEE”,由4个A、3个B、2个C、1个D、4个E组成,经过变动长度编码法可将资料压缩为4A3B2C1D4E(由14个单位转成10个单位)。

简言之,其优点在于将重复性高的资料量压缩成小单位;然而,其缺点在于─若该资料出现频率不高,可能导致压缩结果资料量比原始资料大,例如:原始资料”ABCDE”,压缩结果为”1A1B1C1D1E”(由5个单位转成10个单位)。

知道了以上大体的功能和说明,针对FileCoin其对BitField的实现版本,目标要将保存在BitField内的数据以JSON格式化形式表示出来。

虽然该库([Go-bitfield][https://github.com/filecoin-project/go-bitfield])默认实现了一个`MarshalJSON() ([]byte, error)`的签名方法,不过默认的格式化输出结果展示并不理想。其输出的结果可能是这样的:

1
[3395,1,11,1,10,1,4,1,3,1,10,1,26,1,6,1,3,1,4,1,10,1]

以上输出的JSON格式结果不具备可读性,简单把内部RLE格式的内容输出了。

FileCoin的Bitfiled实现有其实际用途的特点,主要运用在保存扇区号信息。因此它内部的RLE编解码器针对int64类型的数字进行操作,实现底层字节编码到实际数字映射之间的转化。本篇文章仅谈格式化输出,因此只关心这个解码器的输出结构。

通过调用Bitfiled.RunIterator() (rlepluslazy.RunIterator, error)方法,获得解码器,得到

1
2
3
4
type RunIterator interface {
NextRun() (Run, error)
HasNext() bool
}

以上就是一个典型的循环结构,返回的结构体

1
2
3
4
type Run struct {
Val bool
Len uint64
}

通过循环会得到大致如下的结果:

1
2
3
4
5
6
7
8
len: 42399, val: false
len: 1 , val: true
len: 4 , val: false
len: 1 , val: true
len: 15 , val: false
len: 1 , val: true
len: 2 , val: false
....

从观察得出:循环开始的第一个元素,42399是编码进这段Bitfiled内的第一个数据值,Run结构体的Len字段保存的值有三层含义:

1,循环开始的第一个元素是实际值的第一个值;

2,非第一个元素代表累加步进长度;

3,如果字段Val为true时且Len长度大于1,代表连续数值个数,并且代表累加步进长度。

以下为具体实现的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
last := uint64(0)
next := uint64(0)
for iter.HasNext() {
run, err := iter.NextRun()
if err != nil {
return nil, err
}

next += run.Len
if run.Val {
if run.Len > 1 {
sn = append(sn, fmt.Sprintf("%d-%d", last, last+run.Len-1))
} else {
sn = append(sn, strconv.FormatUint(last, 10))
}
} else {
last = next
}
}

完整代码如下:

1
2
# go.mod
require github.com/filecoin-project/specs-actors/v5 v5.0.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner"
"strconv"
"strings"
)

type ProveCommitAggregateParams = miner5.ProveCommitAggregateParams

// MarshalProveCommitAggregateParamsToJSON 将聚合参数转换成JSON格式
func MarshalProveCommitAggregateParamsToJSON(params *ProveCommitAggregateParams) ([]byte, error) {
return json.Marshal(&WrapperProveCommitAggregateParams{params})
}

type WrapperProveCommitAggregateParams struct {
*ProveCommitAggregateParams
}

func (params *WrapperProveCommitAggregateParams) MarshalJSON() ([]byte, error) {
buffer := bytes.NewBufferString("{\n")
count, err := params.SectorNumbers.Count()
if err != nil {
return nil, err
}

if count == 0 {
buffer.WriteString(" \"SectorNumbers\": \"\",\n")
} else {
sn, err := params.run()
if err != nil {
return nil, err
}

buffer.WriteString(fmt.Sprintf(" \"SectorNumbers\": \"%s\",\n", strings.Join(sn, ",")))
}

if params.AggregateProof == nil {
buffer.WriteString(" \"AggregateProof\": \"\"\n")
} else {
proof := base64.StdEncoding.EncodeToString(params.AggregateProof)
buffer.WriteString(fmt.Sprintf(" \"AggregateProof\": \"%s\"\n", proof))
}
buffer.WriteString("}")
return buffer.Bytes(), nil
}

func (params *WrapperProveCommitAggregateParams) run() (sn []string, err error) {
iter, err := params.SectorNumbers.RunIterator()
if err != nil {
return
}

last := uint64(0)
next := uint64(0)
for iter.HasNext() {
run, err := iter.NextRun()
if err != nil {
return nil, err
}

next += run.Len
if run.Val {
if run.Len > 1 {
sn = append(sn, fmt.Sprintf("%d-%d", last, last+run.Len-1))
} else {
sn = append(sn, strconv.FormatUint(last, 10))
}
} else {
last = next
}
}

return
}

格式化后输出看起来像这个样子。比较直观的展示保存在Bitfiled里面的数据,对于连续的数据之间用-号连接。

1
"154565,154571,154617,154881,154991,155263,155337,155689,155733,155753,155759,155805,155818,155847,155859-155860,155866,155869,155878-155879,155891-155892,155905,155916,155929,155940,155969-155970,155977,155982-155983,155988,155996-155997,156017,156021,156035,156038,156046,156059,156063,156067,156070,156112,156137,156155,156185,156187,156195,156205,156216,156293,156300,156302,156311,156321,156331,156336,156354,156369,156386,156397,156412,156499,156508,156511-156512,156533,156549-156550,156563,156570,156576,156586,156590,156598,156617,156723,156725,156748,156753-156754,156756,156761,156764,156766-156767,156772,156778,156782,156789,156791,156793,156807,156810-156812,156887,157013,157015"