文章目录
Abstract
本文将介绍我如何将java中的BitSet实现(或者说叫翻译)为python版本, 保证了数据在java和python中的数据是互通和互操作的。最终的源代码在github javaCollectionInPython
背景和原因
事情是这样的,我们有一张event表格式它记录了不同aumation(1个automation可以理解为1组广告,1个template就理解为一个广告,1组广告中包含多个template),client_id是不同用户的id以及最后他们收到的时间。
id | automation_id | template_id | client_id | time |
---|---|---|---|---|
1 | 1 | 1 | 111 | 2022-09-01 10:10 |
2 | 1 | 1 | 111 | 2022-09-01 11:30 |
3 | 1 | 2 | 111 | 2022-09-01 11:00 |
4 | 2 | 3 | 111 | 2022-09-02 12:00 |
5 | 2 | 3 | 222 | 2022-09-02 12:03 |
我们可能会做如下的两类sql查询:
select automation_id, template_id, count(distinct(client_id)) from event where time > t1 and time < t2 group by automation_id,template_id
select automation_id, count(distinct(client_id)) from event where time > t1 and time < t2 group by automation_id
但是这个查询有个问题是数据库记录可能太多了,即便加上了合适的索引也还要20-30s才能查询回来。
所以想着就是能否预先归集一部分数据,因为过去已经产生的事件不会再改变。
所以想到了用java中的bitset来表示记录client_id这样也可以大幅缩减内存在用。然后将该bitset记录到数据库中新的聚合表中来缓存。 那么python中查询的时候就很方便直接查询该bitset然后进or
逻辑运算就可以了。
Java中的BitSet简单介绍
java中的bitset用一个long[] words
数组来存储。 可以大幅压缩空间占用:
hashmap — 记录一个数字要一个int 4字节
boolean[] — 记录一个数字要1个字节
biset —记录一个数字要1个bit。
但是这个相比数组而言,应该算是以时间换空间。
当需要设置时:
wordIndex=inputIndex / 64
不够则扩容
然后再设置这个long的某一位为1
需要查询时与之类似。bitset在java中确实很方便,但是我们的backend有部分是python写的 怎么办呢?
如何在python中解析bitset呢并查询元素个数呢?
java中的bitset.cardinality()
方法可以返回bitset中有多少个元素。我们需要找到一个方法来在python中使用该bitset。
搜索了下python中的实现,用的比较多的是bitarray
但是这个有问题:
- 结构和java的bitset无法互通。
- 这个bitarray要求2个bitset操作时长度必须一致。
所以就想着自己将java的bitset翻译到python中就可以了吧。
BitSet in Python
自己实现了一个python版本的bitset。该数据结构与java保持同样的数据结构,(底层的words结构一模一样)。我用了如下的方法:
第一步 初始的python代码
在这个网站上将java bitset源码转成的python
修改代码
当然上面的代码肯定无法编译运行,所以还需要进行很多修改:
- 我删除了一些我不需要的方法 比如stream相关的方法。
- python没有long类型的数组 所以我用的numpy中的array来存储。
- 用np.bitwise 操作来提高性能 优化for循环
- 可能遇到int数值超过c最大值的问题.
BitSet.valueOf()
构造函数使用的ByteBuffer
相关类在python中没有,只有自己把主要提取long的操作翻译处理。 这里我删除了对big-endian机器的支持。System.arrayCopy
在python中没有, 我用了np.append
操作来替换。- java的位运算操作不同。 比如 java的set方法中:
1L << 456
在java中和python结果不同。 - 一些保留关键字的替换。比如
or
方法被替换为or_
最终实现
最后我实现的代码在这里
pip install javaCollectionInPython
可以用如下的方式来使用:
from javaCollectionInPython.bitset import BitSet
bs = BitSet.valueOf([])
bs.set(123, True)
bs.set(456, True)
# how many bits set?
print(bs.cardinality())
# 可以与另外一个bitset进行逻辑运算
bs.or_(bs2)
更多的例子在这里
这里面包含了如何在java中压缩bitset并且在python中解压缩并还原bitset的完整代码。
总结
bitset算是设计很精巧实现巧妙地那一类集合。核心代码可能在 300-400行左右。