【C# 数据结构】BitMap算法的c#实现
1.BitMap的应用
在网络同步中(尤其是帧同步),往往需要找一种数据在序列化之后,体积小,数据信息量可观的数据存储方式。
以帧同步为例,一个相当复杂的ACT游戏,采用同步交互量最省的帧同步方式进行网络同步,每关键帧只同步玩家的键位操作数据,此时如果使用BitMap算法进行数据结构的设计,只要1byte就可以存8个键位操作,1int则可以存32个键位操作,每一个关键帧的传输量很小。
在c#语言中,已经设计了BitArray这一数据结构,可直接投入使用。本文旨在详解BitMap算法,所以重新用最基础的byte[]数组以及简单的位运算,来重新实现一遍。
2.BitMap算法的原理介绍
首先,我们来看一个byte的结构组成
一个byte的有8位,每一位都可以用来存储0或1的值,默认每一位都是0。
我们把每一位的位置信息,作为计数,如0就把第0位的值存为1,1就放在第1位的值存为1,2就放在第2位的值存为1,想要表达的数据超过了8位,就再建立一个byte,继续存放。
Magic! 如果现在有10亿的数据,原本按32位int存一个数组,需要 4字节*10亿 = 3.7G左右的空间,如果转换成上述的bitmap算法,从4字节存一个数,变成了1字节存8个数,则只需要3.7G / 32 的空间了。
3.具体实现与源码解析
按照上述分析,我们主要实现目标其实就是对众多byte组成的这一数据结构,可以进行便利的增删改查。
其中要点可以分为以下三点
1.能够快速的根据存入的数据,定位到其在bitmap中的索引位置。
2.定位到位置后,可以方便的修改其值。(0或1)
3.对于多个byte进行恰当的存储。
首先针对第3点,我们可以很快联系到申明一个byte数组,这样数据连续存储,也方便整体遍历。
再者,针对第1点,也不难想到,既然bitmap本质意义是一个byte数组,那么对于存储其中的数据,我们需要定位到其存储在数组的第几号元素,以及在该元素也就是单个byte中,存储于第几位上。
一个byte有8位,首位存0,直接上超级简单的数学算法
对于数据N
在数组中的序号(N) = N / 8 = N >> 3
定位到当前序号的byte后
在byte中的位置(N) = N % 8 = N & 0X07
解决了定位,我们再来看第2点,修改当前位置的值
对于当前byte中,第P位的值
设定一个同为8位byte类型的模板值,由1左移P位得到
byte M = (byte)(1 << P)
1.如果存入,则需将第P位设为1
byte = byte | M
2.如果清除,则需将P位设为0
2.1 将模板值M取反,Z = ~M
2.2 byte = byte & Z
同理,我们还可以扩展出算法,对当前的bitmap是否存储了某个值进行判断
byte isContain = byte & M
如果 isContain 不为0,则存在数据N,反之则不存在
所有源码如下
public class CustomBitMap { public byte[] Bits { get; private set; } /// <summary> /// 数组中的第几个byte /// </summary> public byte Index { get; private set; } /// <summary> /// 在单个byte中 第几位 /// </summary> public byte Position { get; private set; } public CustomBitMap(int length = 8) { Bits = new byte[length]; Index = 0; Position = 0; } /// <summary> /// byte[] 中 所在的num位置变为1 其他位置不变 /// </summary> /// <param name="num"></param> public void Add(int num) { //num / 8 得到位于数组的第几个byte int arrayIndex = num >> 3; // num % 8 得到位于单个byte中 第几位 int position = num & 0x07; byte arg = (byte) (1 << position); Bits[arrayIndex] |= arg; } public void Clear(int num) { //num / 8 得到位于数组的第几个byte int arrayIndex = num >> 3; // num % 8 得到位于单个byte中 第几位 int position = num & 0x07; //将1 左移并取反 byte arg = (byte) ~(1 << position); //随后&上即清除 Bits[arrayIndex] &= arg; } public bool Contains(int num) { //num / 8 得到位于数组的第几个byte int arrayIndex = num >> 3; // num % 8 得到位于单个byte中 第几位 int position = num & 0x07; //将1 左移并取反 byte arg = (byte)(1 << position); return (Bits[arrayIndex] & arg) != 0; } }