使用bitset实现毫秒级查询(实例讲解)
前言
因为业务要求api的一次请求响应时间在10ms以内,所以传统的数据库查询操作直接被排除(网络io和磁盘io)。通过调研,最终使用了bieset,目前已经正常运行了很久
bitset介绍
看JDK中的解释简直一头雾水,用我自己的理解概括一下
1.bitset的内部实现是long数组
2.set中每一个位的默认值为false(0)
3.bitset长度按需增长
4.bitset非线程安全
bitset关键方法分析
/** *Setsthebitatthespecifiedindexto{@codetrue}. * *@parambitIndexabitindex *@throwsIndexOutOfBoundsExceptionifthespecifiedindexisnegative *@sinceJDK1.0 */ publicvoidset(intbitIndex){ if(bitIndex<0) thrownewIndexOutOfBoundsException("bitIndex<0:"+bitIndex); intwordIndex=wordIndex(bitIndex); expandTo(wordIndex); words[wordIndex]|=(1L<设置指定“位”为true,bitIndex参数为非负整数。假设我们执行以下代码,观察上面代码中worIndex,words[wordIndex]值的变化
BitSetbs=newBitSet() bs.set(0); bs.set(1); bs.set(2); bs.set(3); bs.set(4);
bitIndex wordIndex words[wordIndex] words[wordIndex]二进制表示 0 0 1 0001 1 0 3 0011 2 0 7 0111 3 0 15 1111 4 0 31 00011111 通过上表,我们可以很清晰的根据bitIndex和words[wordIndex]二进制值的对应关系,得到一个结论,即:bitset中每一个long可以表示64个非负整数在bitSet中存在与否。例如:0001可以表示整数0在bitset中存在,1111可以表示整数3,2,1,0在bitset中存在。
进入正题,实现bitset毫秒级查询
想象一个场景,我们有一张user表
CREATETABLE`user`( `id`int(11)NOTNULLAUTO_INCREMENT, `name`varchar(255)NOTNULL, `address`varchar(255)NOTNULLcomment'地址', `gender`varchar(10)NOTNULLcomment'性别', `age`varchar(10)NOTNULL, PRIMARYKEY(`uid`) )ENGINE=InnoDBAUTO_INCREMENT=0DEFAULTCHARSET=utf8;假设我们要查询“北京市18岁的女生”,那么对应的sql如下:
select*from`user`whereaddress='北京'andage='18'andgender='girl';如何使用bitset实现同样的查询呢?
1.将user表数据加载进内存中
2.为user表建立address,age,gender维度的bitset索引
3.根据索引查询数据
1.将user表数据加载进内存中
将user表从数据库读取出来存入List
User实体类:
publicclassUserimplementsCloneable{ privateStringname; privateStringaddress; privateStringgender; privateStringage; @Override publicStringtoString(){ return"User[name="+name+",address="+address+",gender="+gender+",age="+age+"]"; } @Override publicUserclone(){ Useruser=null; try{ user=(User)super.clone(); }catch(CloneNotSupportedExceptione){ e.printStackTrace(); } returnuser; } //省略getset方法。。。2.建立索引
创建bitset索引模型类
publicclassBitSetIndexModel{ privateStringtype;//索引类型:address,age,gender privateConcurrentHashMapmap;//索引类型和bitSet在bsList中下标的映射关系 privateList list;//索引类型的值集合,例如gender:girl,boy privateList bsList;//bitset集合 publicBitSetIndex(){ } publicBitSetIndexModel(Stringtype){ this.type=type; map=newConcurrentHashMap (); list=newArrayList (); bsList=newArrayList (); } publicStringgetType(){ returntype; } publicvoidsetType(Stringtype){ this.type=type; } publicMap getMap(){ returnmap; } publicvoidsetMap(ConcurrentHashMap map){ this.map=map; } publicList getList(){ returnlist; } publicvoidsetList(List list){ this.list=list; } publicList getBsList(){ returnbsList; } publicvoidsetBsList(List bsList){ this.bsList=bsList; } /** * *@paramstr *@parami */ publicvoidcreateIndex(Stringstr,inti){ BitSetbitset=null; //获取‘str'对应的bitset在bsList中的下标 Integerindex=this.getMap().get(str); if(index!=null){ bitset=this.getBsList().get(index); if(bitset==null){ bitset=newBitSet(); this.getBsList().add(index,bitset); } bitset.set(i,true);//将str对应的位置为true,true可省略 }else{ bitset=newBitSet(); List list=this.getList(); list.add(str); index=list.size()-1; bitset.set(i,true); this.getBsList().add(bitset); this.getMap().put(str,index); } } /** *从entity里拿出符合条件的bitset * *@paramstr *@return */ publicBitSetget(Stringstr){ BitSetbitset=null; str=str.toLowerCase(); Integerindex=this.getMap().get(str); if(index!=null){ bitset=this.getBsList().get(index); }else{ bitset=newBitSet(); } returnbitset; } /** *bitset的与运算 * *@paramstr *@parambitset *@return */ publicBitSetand(Stringstr,BitSetbitset){ if(str!=null){ str=str.toLowerCase(); if(bitset!=null){ bitset.and(get(str)); }else{ bitset=newBitSet(); bitset.or(get(str)); } } returnbitset; } /** *bitset的或运算 * *@paramstr *@parambitset *@return */ publicBitSetor(Stringstr,BitSetbitset){ if(str!=null){ str=str.toLowerCase(); if(bitset!=null){ bitset.or(get(str)); }else{ bitset=newBitSet(); bitset.or(get(str)); } } returnbitset; } /** *获取bitset值为true的即把bitset翻译为list的索引 * *@parambitset *@return */ publicstaticList getRealIndexs(BitSetbitset){ List indexs=newArrayList (); if(bitset!=null){ inti=bitset.nextSetBit(0); if(i!=-1){ indexs.add(i); for(i=bitset.nextSetBit(i+1);i>=0;i=bitset.nextSetBit(i+1)){ intendOfRun=bitset.nextClearBit(i); do{ indexs.add(i); }while(++i 为每一个user对象创建address,gender,age维度索引
publicclassUserIndexStore{ privatestaticfinalStringADDRESS="address"; privatestaticfinalStringGENDER="gender"; privatestaticfinalStringAGE="age"; privateBitSetIndexModeladdress; privateBitSetIndexModelgender; privateBitSetIndexModelage; privateConcurrentHashMapuserMap;//存储所有的user数据 publicstaticfinalUserIndexStoreINSTANCE=getInstance(); privateUserIndexStore(){ init(); } publicstaticUserIndexStoregetInstance(){ returnUserIndexStoreHolder.instance; } privatestaticclassUserIndexStoreHolder{ privatestaticUserIndexStoreinstance=newUserIndexStore(); } privatevoidinit(){ this.address=newBitSetIndexModel(ADDRESS); this.gender=newBitSetIndexModel(GENDER); this.age=newBitSetIndexModel(AGE); userMap=newConcurrentHashMap (); } /** *构建索引 *@paramusers */ publicvoidcreateIndex(List users){ if(users!=null&&users.size()>0){ for(intindex=0;index 3.测试bitset
publicclassBitSetTest{ publicstaticvoidmain(String[]args){ Listusers=buildData(); UserIndexStore.getInstance().createIndex(users); ExecutorServiceexecutorService=Executors.newFixedThreadPool(20); intnum=2000; longbegin1=System.currentTimeMillis(); for(inti=0;i indexs=BitSetIndexModel.getRealIndexs(UserIndexStore.getInstance().query("北京","girl","18")); for(Integerindex:indexs){ UserIndexStore.getInstance().findUser(index); } } }; executorService.execute(syncRunnable); } executorService.shutdown(); while(true){ try{ if(executorService.awaitTermination(1,TimeUnit.SECONDS)){ System.out.println("单次查询时间为:"+(System.currentTimeMillis()-begin1)/num+"ms"); break; } }catch(InterruptedExceptione){ e.printStackTrace(); } } } privatestaticList buildData(){ String[]addresss={"北京","上海","深圳"}; String[]ages={"16","17","18"}; List users=newArrayList<>(); for(inti=0;i<200000;i++){ Useruser=newUser(); user.setName("name"+i); intrand=ThreadLocalRandom.current().nextInt(3); user.setAddress(addresss[ThreadLocalRandom.current().nextInt(3)]); user.setGender((rand&1)==0?"girl":"boy"); user.setAge(ages[ThreadLocalRandom.current().nextInt(3)]); users.add(user); } returnusers; } } 测试结果(查询2w次):
数据量(users.size())|并发数|平均查询时间
---|---|---
20w|10|1ms
50w|20|3ms
100w|50|9ms测试机为thinkpadx240i58g内存
4.总结
==优点==:
通过测试发现随着数据量的增大和并发数的提高,平均耗时并没有明显升高,并且响应时间都在10ms以内
==缺点==:
1.不适合数据量太大的情况,因为需要把数据全部加载进内存
2.不适合复杂查询
3.不适合对name,id等唯一值做查询
后记
因为我们的查询业务比较简单,唯一的要求是速度,并且数据量也不大,每张表的数据量都不超过100w,所以使用这种方式比较合适。
在本篇文章中只谈到了如何创建索引,以及最基本的查询,在下一篇中我会继续说明如何更新索引,以及一些复杂查询,比如<,>,betweenand。
以上这篇使用bitset实现毫秒级查询(实例讲解)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。