最近在做一个商业智能软件,也就是我们大家通常说的BI。找了许多资料,关于数据挖掘里面数据立方体的算法到时介绍的很多,但是个人却始终觉得没有什么实际的意义。而我所说的位图索引,也是书中概念的一部分。我把它拿来用来了,但是不是书中所说的一般的方式。个人也觉得挺幸运的,侥幸的知道了位图索引的概念,然后又侥幸的通过位图索引解决了大数据量处理的问题。可能前人已经想到了类似的方法,但是以下说诉,都是个人的思想。
先介绍下位图索引的概念。如果有个二维的销售表,包含以下几个字段:省份、销售员、产品类型、产品。简单的假设表的值如下:
省份 | 销售员 | 产品类型 | 产品 |
---|---|---|---|
江苏 | jack | 手机 | iphone |
江苏 | jane | 家电 | 电视机 |
浙江 | apple | 手机 | n97 |
浙江 | tom | 家电 | 电冰箱 |
江苏 | Jerry | 家电 | 电磁炉 |
针对省份里面的“江苏”,因为表里面第0、1、4行是江苏,那么可以就在该行用1表示。具体结果如下:
行号 | 江苏 | 浙江 |
0 | 1 | 0 |
1 | 1 | 0 |
2 | 0 | 1 |
3 | 0 | 1 |
4 | 1 | 0 |
现在来介绍下分组的算法:
假设我们要对省份、产品类型两个字段进行分组:
省份里面有两个值:江苏和浙江。先把江苏的索引给取出来,是11001。然后取产品类型的位图索引:手机-10100、家电01011。
我们把江苏-11001和手机-10100比较,同时有1的位置就是既有江苏又有手机的位置,也就是对11001、10100求与运算,得到结果10000,10000不全为0,说明存在这个分组,而且位置是第0行。再将江苏-11001和家电-01011求与得到01001,存在这个分组,而且位置是第1、4行。如果全部为0,说明不存在这个组合的分组。
这样一层次一层次的遍历下去,就会得到一个树形的数据结构。也就是分组的结果。如下:
root---江苏(11001)---手机(10000)
---家电(01001)
---浙江(00110)---手机(00100)
---家电(00010)
这样分组的好出有一下几点:
1.速度快。主要运算基本都是“与”运算
上诉的分组过程中,字段不同值之间的分组结果其实是不相干的。(我想先算江苏或者先算浙江都一样,而且两者互相直接没有任何关系,因为它们的位图索引其实是互斥的)
不相干带来几个好处:
2.传统的分组算法,必须将关系表遍历几遍,得到最终准确结果,才能准确展示。而位图索引的不相干性,表明了分组完全可以部分计算。比如说:我只计算江苏的分组,一层一层的与下去,当结果数根节点的个数打到了当前页里面的条数,那么就可以break了(后者你后台继续算)。
所以,部分计算、部分展示的特性是其强大的优点。
3.多线程计算。不相干代表了计算结果互不影响,那么多线程计算就是必须添加进去了。可以多线程计算分组、或者汇总
4.过滤(条件分组)很方便。拿过滤条件(过滤条件也可以转换成位图),与树节点中的每个节点求与操作,如果全0,从树中舍弃该节点及其子节点。比如说求条件为“销售员=jack”下的省份、产品类型分组,该条件的位图为:10000,然后去上面的树进行与操作。实际的过滤条件可能较为复杂,需要进过各种“与”、“或”等操作得到。
5.汇总值、平均值等计算方法。只要取根节点里面所存位图的,然后取位图中值为1的位置相应的汇总字段,求汇总即可。也可以加入多线程。
6.排序非常简单。只要对一个节点的子节点进行排序就行。简单的迭代遍历
7.省去许多重复计算。比如存在了“省份、销售员、产品类型”的结果,求“省份、销售员”就直接从前者的树里面截取就得到了。
实际操作中存在几个难点:
位图索引的保存、压缩问题。
我是用了几种方法:
1.用long数组保存。一个long值就是64个位置,那么每个位置都与实际的位置想对应。为何不用int或者short之类的呢。因为long是64位,假如640000行数据,我只要10000个long,意味着位图索引与的时候只要遍历10000次。而用int,就是20000次了。
2.long数组能快速的取,快速的遍历,但是在大数据量的时候,所占的内存空间也很大。比如说1000W行的数据,那么就是1000W/64个long值,大约是1M的内存空间。假如该字段里面的值不是很多,我们还可以接受这种数据结构。假如省份有10000个,那么这样子就不行了,会内存溢出。当然可以将索引保存到本地文件,用的时候再读取。但是这毕竟不是最终解决之道。
3.我用了其他两中方式来保存上述状况,但是这两种还有差别。
第一种:一个有顺序的“1”的位置列表。假如1000W行数据中,省份=“江苏”的位置只有1,10000行这两个位置,那么就是保存的1和10000。这种情况在数字字段里面较多,因为一些随机数字重复出现的概率很低。但是这样数据结构必须用的时候才能生成。原因很简单:1000W个不同的数字,就说明有1000W个不同的位图索引,每个里面的都有一个位置,而这个位置又是一个整数,也就是说明有1000W个整数在内存中。显然不可取。所以呢,快速的生成这种数据结构是个值得研究的,而且这种数据结构与上面的long数组做“与”操作的算法,也必须完美。
第二种:因为long[]数组中肯定存在许多的0,这些0毫无意义,其实只要我们知道这些数据那些位置是0就好。这种就是对0的压缩。
本人上述的两种方式,都能快速知道随机某个位置是否是0(都是是通过二分法),只是比数组的直接索引慢了一点。运算时间还可以接受。
个人认为一个“与”、“或”运算的事件最好不能超过100us。一般客户等个1s就算长的,1s除以100us等于1W,说明最多可以有1W次与操作,也就是1W个树节点,而1W个节点是大数据量展示一页显示差不多的量。
位图运算的难点根本在于位图的数据结构和计算方法。
本来想用B+树实现压缩的,后来发现效率有点低(虽然压缩度最大),就放弃了。可能是我掌握的不太好的原因吧,有机会肯定要重新试一下。
说了这么多废话的,本来想放源码的,但是因为商业机密(即使这些代码都是本人写的),不得不、、、
感兴趣的、有不明白的可以私信M我