BitSet在搜索里面压缩参数的使用

目录

背景

原理

案例

计算流程

1、压缩过程——categoryIdList转换为categoryIdBitSet

2、解析过程——判断文档的categoryId记录是否落在bitset中

脚本案例——解析es 打印出来的 bitset


背景

电商平台按照单位区分可以搜索的采购品类,还有对应的入驻供应商。平台需求是控制客户只能查看允许采购的商品品类,还有只能查看入驻供应商的商品。但每个单位配置的品类和入驻供应商成千上万,直接传输的话,无论传输速度还是解析速度慢,因此需要对参数进行压缩。

原理

使用BitSet的方法对参数进行压缩。业务上是只需要判断CategoryId是否存在,不需要理会具体的值,因此可以以0,1判断此值是否存在。

计算机存储时:

int 占 32位,1000个int,就要有1000个32位的存储;

如果直接用位数存储,判断1000个int是否存在,直接用对应的位存储,就只需要1000个位,比原本1000个32位少了很多。

java里面的BitSet压缩是按照64位压缩的,以long型的存储大小为准。因为java里面,int位数限制的缘故,1<<128和1<<64(此类的1<<n = 1<<(n+64))得到都是相同的结果,都是1(java位运算结果使用int接收的),因此以 一个“64个二进制位” 为BitSet里面的一个元素,其中位数置为1或0表示categoryId是否存在。

上述做法既可以表示一个categoryId是否存在,又可以简化代码编写。(注意同样的操作在python是不适用的!python已经帮忙封装好了,1<<128和1<<64是不一样的结果,64位是JDK默认的设置);

JAVA

ed654fa0e3bdd87c491f90849ed67d1b.png

Python

综上,在JAVA中,用BitSet来压缩判断categoryId是否存在,能够直接将数据压缩64倍;

案例

入参是:约1500个CategoryId的列表,如

[1,2,3,7,10,500,......10067]

初始化的BitSet是全0的:

[000000000000...000,

000000000000...000

...

000000000000...000]

即将要生成的BitSet类似于:

[101010101001...001, 

000001100011....100, 

101010010010....001,

...

000001100011....100]

单个元素的位数 = 64

BitSet总长度 = Max(categoryIdList) / 64 + 1(也即向上取整)

元素中标识1的表示此位置有Id,

如第一个元素:

101010101001...001

(63->...)

表示 63,61,59,57,55,52,......,0 这些categoryId是存在于上传的列表中的;

第二个元素:

000001100011....100

(127->...)

表示64+index的元素122,121,117,116,......,66这些categoryId是存在于上传的列表中的;

计算流程

1、压缩过程——categoryIdList转换为categoryIdBitSet

private final static int ADDRESS_BITS_PER_WORD = 6;

/**
 * Sets the bit at the specified index to {@code true}.
 *
 * @param bitIndex a bit index
 * @throws IndexOutOfBoundsException if the specified index is negative
 * @since JDK1.0
 */
public void set(int bitIndex) {
    if (bitIndex < 0)
        throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

    int wordIndex = wordIndex(bitIndex);
    expandTo(wordIndex);

    words[wordIndex] |= (1L << bitIndex); // Restores invariants

    checkInvariants();
}

/**
	* Given a bit index, return word index containing it.
	*/
private static int wordIndex(int bitIndex) {
		return bitIndex >> ADDRESS_BITS_PER_WORD;
}

解析:

wordIndex:categoryId>>6,先将category压缩在 Max(categoryId)/64 个格子范围里面,也即BitSet中第几个元素(0-base的index);

words[wordIndex] |= (1L << bitIndex):拆分成

(1)res = 1L<<bitIndex 和 

(2)words[wordIndex] | res

(1)结合前述,1<< n 和 1<< (n+64)是同一个结果,因此此式可理解为,在bitset的第wordIndex个元素中,第n位的值是存在的;

(2)或运算,表示原本有值我们就还是保持原本的1;原本无值,此时我们将对应位从0变为1;

2、解析过程——判断文档的categoryId记录是否落在bitset中

int category_id =(int)doc['category_id'].value; 
int index_1 = category_id>>>6; 
if(index_1>=params.categoryIds.length) { 
    return -100000;
 } 
if(((params.categoryIds[index_1]>>>(category_id & 63))&1)==0){
      return -100000;
 }

行1:获取文档中的categoryId值;

行2:同前述压缩过程中,判断此categoryId是落在bitset的第几个元素;

行3-5:如果长度超出了范围,说明此文档中的categoryId铁定不在我们输入的列表中,直接返回(-10000是用来做业务算分的,表示分值很低,当做是false就行);

行6-8:重点介绍

一个大前提,我们要判断的是,文档条目的categoryId是否落在bitset某个元素的某一位;

某个元素可以用category_id/64向下取整来获得。某一位,压缩的时候是1左移了传入的category_id除以64后的余数位(同样是因为Java里面 1<<n = 1<< (n+64))

(1)category_id & 63:其实就是相当于取category_id除以64的余数;

(2)params.categoryIds[index_1]:取bitset中,这个category_id可能存在的元素;

(3) (2)的结果 >>> (1)的结果,判断位数从右往左数,获取第 余数 个位置的结果;

(4)(3)结果与1的位运算,如果结果是1,表示此值存在需要筛选的列表,如果结果是0,表示值不存在。

解析的步骤都需要结合压缩步骤进行理解;

脚本案例——解析es 打印出来的 bitset

(java自动将二进制数转成了long型)

/**
 * 输入一个字符串的列表(因为这个是展示到es dsl里面的脚本,用这种格式传入的时候复制粘贴更简洁)
 * @param bitSetList
 * @return
 */
public List<Integer> parseBitSetToIntList(List<String> bitSetList) {
    Integer maxLen = 64;
    List<Integer> resList = this.newList();
    for (int index = 0; index < bitSetList.size(); index++) {
        Long input = Long.valueOf(bitSetList.get(index));
        int start = maxLen * index;
        String binarySimple = Long.toBinaryString(input);
        // 补齐到64位
        int binStrLen = binarySimple.length();
        int lackZero = maxLen - binStrLen;
        StringBuilder zeroStringBuilder = new StringBuilder();
        for (int i = 0; i < lackZero; i++) {
            zeroStringBuilder.append("0");
        }
        zeroStringBuilder.append(binarySimple);
        String binString = zeroStringBuilder.toString();

        // 解析
        for (int i = 0; i < maxLen; i++) {
            char c = binString.charAt(i);
            if (Objects.equals(c, '1')) {
                resList.add(start + 63 - i);
            }
        }
    }
    return resList;
}

/**
 * 生成新泛型列表
 * @param t
 * @param <T>
 * @return
 */
private <T> List<T> newList(T... t) {
    List<T> ll = new ArrayList<>();
    for (int i = 0; i < t.length; i++) {
        ll.add(t[i]);
    }
    return ll;
}

public static void main(String[] args) {
    someImpl some = new someImpl();
    String[] m = new String[]{"-1152918206071963648","10466113745846340","0","4037776047994822400","351843720888321","0","-67556055994990592","4095","-128","-72057594037927937","-8070450532382146561","140737488355327","384","0","0","0","0","0","0","0","0","422212465065984","-16","-1","-1","-565201754310819841","-1","-1","-1","-1","-1","-1","-1","9223372036854775807","-1","-1","-1","-1","536870911","0"};
    List<String> inputList = some.newList(m);
    List<Integer> resList = some.parseBitSetToIntList(inputList);
    System.out.println(resList);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值