1.BitMap
BitMap是一种排序算法,他的特点是速度快,节省容量。可以对整数型型数据进行排序,同时也可以应用于文本去重,排序前先要对文本进行MD5操作。
- 优点
- 节省空间,对于一个字节进行排序,只要一个字节的空间。
- 运行效率高
- 缺点:
- 重复数据只能记录一次。
假设要对30 亿个 数据 进行排序,每个整数如果占 4 个字节 那么 30亿的数据就要占用 11 个G,对于一些配置较差的机器来说要加载进内存就不够用了,BitMaps可以很好的解决这个问题。
如果要对 1 - 8 的数排序 要怎么办呢。
只要用一个 字节 表示 8个数 如上图 如果要记录 4 那么只要在第四位设置为1。
所以如果要对四排序那么久在第四位设置为1。一个字节能表示成 8 位,2 个字节 就能表示16 位的大小了。
bitsmap := make([]byte,2,2)
上面 开辟了2个字节 也就能表示16 个数字。
如果 要对 第4位 第12位 和 第14位 排序 该怎么做呢?
首先要确定 每个块的值域。 假设 第n个byte 他的值域是多少呢 ?
8
(
n
−
1
)
<
=
x
<
=
8
n
n
∈
(
1
,
无
穷
)
8(n-1) <= x <= 8n n∈(1,无穷)
8(n−1)<=x<=8nn∈(1,无穷) 假设 第一个字节 就是 1 ~8 的范围。 如果有2个字节就是
8 ~ 16 的范围
那么给定 一个数 7 怎么计算它属于哪个第几个字节呢?可以用求余操作。
7 % 8 = 商:0 余: 7 那么 前面就表示他在第几个字节 后面就是第几个字节的第几个位。
写成代码:
func setbit(bm []byte,i int){
i -= 1 //数组索引是从0 开始的 传入是从1开始的
p := i / 8 //计算商表示在第几个字节 等价也可以成 p := i >> 3
l := i % 8 //计算余数 表示位
bytem := bitsmap[p] //这就取出8位了
fmt.Printf("改变前状态:%08b 第 %d 位要置为1 \n", bytem,l + 1)
bytem = bytem ^ 1 << l //取异或操作
fmt.Printf("改变后 :%08b\n", bytem)
bm[p] = bytem //保存更改
}
bytem ^ 1 << l
这里要 ^ 是取 异或的操作 假设00000000 要设置第 1位为 1 但是又不修改其他位数呢
可以先 1 左移 << 1 1位 变成 10 然后用 异或操作 异或的定义 如果2个操作数不相同则为1.
这样 00000000 和 00000010 操作 变成了00000010 。
这里实现了 添加数。 接下来要实现 获取这个 数 是否在bitmaps中已进存在。
和上面是一样的。
这么理解吧 00000010 假设 要插入的数 已经 存在了 那么再一次 的进行 异或操作,
00000010 XOR 00000010 一旦异或 就会被置为 0 这样的话 就变小了。
所以 逻辑就是拿 原来的bitmasp 再 做一次 插入 如果不存在 置入1 一定会变大
如果原先 那一位 就存在的话 就一定会变小 比较一下
如果 原bitbyte > 插入一个数的bitbyte 就说明是已经存在了
func getbit(bm []byte,i int) bool{
i -= 1
p := i / 8
l := i % 8
bytem := bm[p] //这就取出8位了
return bytem > bytem ^ 1 << l //如果原来那一位为1 再一次异或 一定为 0 那么相比原来就变小了
}
测试代码:
bitsmap := make([]byte,2,2)
setbit(bitsmap,8)
setbit(bitsmap,16)
fmt.Println(getbit(bitsmap,8))
fmt.Println(getbit(bitsmap,2))
fmt.Println(iter(bitsmap))
接下来 再来实现以下把 所有的内容从大大小输出吧,这其实就很简单了,就遍历数组。就行了。
func iter(bm []byte) []int{
list := make([]int,0,len(bm))
//2个字节 总共可以表达 16的大小 遍历16位
for i:=1;i<=len(bm) * 8;i++ { //这里字节 要乘以 8=16bit
val := getbit(bm,i)
if val {
list = append(list, i)
}
}
return list
}
这里可以看到结果是正确的。
下面再实现 一个 删除存在的数字,其实把上面的方法组合下就可以了。
- 查找是否存在
- 存在 再执行一次Set 异或 就变为0 了
func UnSet(bm []byte,i int){
fmt.Println("删除数字:",i)
val := getbit(bm,i)//判断存在吗
if val {
setbit(bm,i)//再一次插入就是删除了 }
}
func main(){
bitsmap := make([]byte,2,2)
setbit(bitsmap,8)
setbit(bitsmap,16)
fmt.Println(getbit(bitsmap,8))
fmt.Println(getbit(bitsmap,2))
fmt.Println(iter(bitsmap))
UnSet(bitsmap,8) //删除
fmt.Println(iter(bitsmap))
}
可以正确删除,到这里BitMap核心的功能就实现了,实际上上面的是精简版,没有判断范围是否超出表示范围,而且 求 余 可以用 n & 8 也是一样的 。
其实很简单 就是用到了求余,求商和异或这三点知识。
上面使用了 byte 表示 每个字节只能 8个大小 ,但 用一个int64记录呢,是不是可以做到 一个int如果64个bit 可以记录 64位呢 ?原理其实是一样的 。
下面一个完整版
const (
//这里 ptrBits 可以知道int 在系统中表示的大小byte大小 (^uintptr(0) >> 63) 在 32位 为 0 在 64位 为 1
//ptrBits = 32 << uint( (^uintptr(0) >> 63)) //^uintptr(0) 表示 64位0 取反 也就是 2 << 63 -1 = 18446744073709551615
减1是因为 从0 开始
//byteModeMask = ptrBits - 1 //能记录的长度 上面 ptrBits 是 64 这里 从0 开始 所以减1
//byteShift =(1 << 7 + ptrBits) >> 6 // 8位 / 2 << 7 64 位就是 2 << 63
byteModeMask = 7 //2 ^3 -1
byteShift = 3 // 2 ^ 3
)
type BitSet interface {
Get(i int)bool //提取数据
Set(i int) //设置数据
Unset(i int) //取消设置
SetBool(i int,b bool)//设置
}
type Bytes [] byte //指针 可以指向 表示 2 << 63 的大小整数
func NewBytes(numBits int) Bytes{
// numBits 范围,byteModeMask 起始 如果 numBits = 0 byteModeMask >> byteShift 63 >> 6 = 0
return make(Bytes,(numBits+ byteModeMask) >> byteShift)
}
//提取数据
func (p Bytes)Get(i int)bool{
return p[uint(i) >> byteShift] & (1 << (uint(i) & byteModeMask)) != 0
}
//提取数据
func (p Bytes)Set(i int){
// (uint(i)&byteModeMask 10 % 63 等价于 i(10) & byteModeMask(63)
//uint(i) >> byteShift 这个等价于 10 / (1 << 63) 计算第几个uintptr
//|= 一位一位的取或运算
//所以下面代码 就是对 一个 64位0101的数 对指定为设置为1
//下面 用|= 和 ^= 是一样的 区别是 如果对同一个数添加2次 用^=2次 就会成为0
p[uint(i) >> byteShift] |= 1 << (uint(i)&byteModeMask)
}
func (p Bytes)UnSet(i int){
// 001000 001000 &^= 如果右侧是0,则左侧数保持不变 如果右侧是1,则左侧数一定清零 所以会被清零
p[uint(i) >> byteShift] &^= 1 << (uint(i)&byteModeMask)
}
func (p Bytes)SetBool(i int,b bool){
if b{
p.Set(i)
}
p.UnSet(i)
}
//增长数据
func (p * Bytes)Grow (numBits int){
ptrs := *p
//等价于 10 + 8 / 8
targetlen := (numBits + byteModeMask) >> byteShift
//计算出需要多少个容纳 更搭的数
missing := targetlen - len(ptrs)
if missing >0 && missing <= targetlen{
//添加一个新的 uint64
*p = append(ptrs,make(Bytes,missing)...)
}
}
func main(){
p :=NewBytes(10)
p.Set(11)
p.Set(14)
p.Grow(31)
fmt.Println(p.Get(11))
fmt.Println(p.Get(31))
}
位运算总结
在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方,右移一位相当于除2,右移n位相当于除以2的n次方。
<< 左移,相当于乘以2的n次方,例如:1<<6 相当于1×64=64,3<<4 相当于3×16=48
, >> 右移,相当于除以2的n次方,例如:64>>3 相当于64÷8=8
^ 异或,相当于求余数,例如:48^32 相当于 48%32=16
求余 : a % b = a &b