Java:使用位操作实现单表多对多存储

需求:建立Course表(等)与Teacher表的关联

分析: 当两个实体之间存在着多对多的映射关系时,往往需要一张中间表来存放。但是,多一张表就会多一个实体类、Mapper、Dao等等,就需要更多的代码去维护。因此我们需要尽量减少数据库的复杂度。在这个例子中,我们就可以避免使用中间表,原因在于:

  1. 老师的数量有限,(和需求方沟通后确定)不会多于30位老师使用本系统。
  2. 除了Course外还有诸如Project、Module等等实体都要与Teacher表关联,而且关系都是多对多,这样增加的就不是一张中间表而是很多中间表,这势必会造成更大的维护困难。

在这种情况下,可以使用位操作的办法来实现多对多映射,避免中间表。

以Course表为例,用一个Long型的字段来存放teacher:
在这里插入图片描述
Integer是4个字节32位,其实已经满足需求,但是保险起见还是使用了Long来存放这个teacher的信息,这样的话老师用户最多可以有64人。

存储的原理是,第n位表示编号为n的老师和这门课的关系,1代表要上这门课,0代表不上。假设已经有4位老师:(忽略0号的管理员)
在这里插入图片描述
那么0001就可以表示1号老师上这门课,1010表示2号和4号老师上这门课,etc.

这样一来Long 64位就可以表示64位老师和这门课的关系。

实现: 创建config工具类,里面使用静态方法来实现我们的转换。

1. 把id转换成Long(编码)

Java里需要用到字节数组。创建一个长度为8的字节数组(一个字节8比特,所以一共是64位)byte[] bytes = new byte[8],Java会自动帮我们把所有位初始化为0。如果想要把某个字节的某一位设成1,可以让字节与一个整数做或运算,这个整数由1左位移得到。比如,把byte的(从0开始)第四位设成1,我们需要:

byte |= 1<<4;

1:0000 0001,左位移4位后:0001 0000,然后做或运算即可把byte的第4位标记成1。

用这个原理可以得到一个标识好位数的字节数组,代码:

	static byte[] getByteList(Integer[] pos) //把长整型转换成字节数组
    {
        byte[] bytes = new byte[8]; //64位数拆成8字节的字节数组
        for(int p : pos)
        {
            if(p<1||p>64) continue;
            int x=(p-1)/8,y=(p-1)%8;
            bytes[x] |= 1 << y; //将字节数组的第x位赋2的y次方
        }
        return bytes;
    }

接下来还要把字节数组转成Long型长整数,以便存放数据库。对于原理部分可以参考https://blog.csdn.net/u013556056/article/details/81019509(转的是Integer,原理都差不多),而对于Long的转换要稍微有点区别,如果直接套用int的方法改成long,会出现这个问题:
在这里插入图片描述
当左位移大于32时,IDEA提示左位移32相当于移0,移40相当于移8等等。

这是因为byte类型的移位会被自动强转成int型,int一共只有32位,那么左移超过32位其实就相当于没移位。因此要在long型上移位,只能先转成long型再移位。注意long型在&0xff时也会有变化。代码:(为什么要&0xff原文有,一句话总结就是:byte强转成int或long会补位,但补的都是1不是0,所以要清零)

    static long convertByteToLong(byte[] bytes) //把字节数组拼在一起成为一个长整形long
    {
        long l0 = bytes[0] & 0x00000000000000ffL;
        long l1 = ((long)bytes[1]<<8 ) & 0x000000000000ff00L;
        long l2 = ((long)bytes[2]<<16) & 0x0000000000ff0000L;
        long l3 = ((long)bytes[3]<<24) & 0x00000000ff000000L;
        long l4 = ((long)bytes[4]<<32) & 0x000000ff00000000L;
        long l5 = ((long)bytes[5]<<40) & 0x0000ff0000000000L;
        long l6 = ((long)bytes[6]<<48) & 0x00ff000000000000L;
        long l7 = ((long)bytes[7]<<56) & 0xff00000000000000L;
        return l0 | l1 | l2 | l3 | l4 | l5 | l6 | l7; //把8个长整型数的各个字节位取出来拼在一起
    }

这样就得到了Long型的数据,相当于编码,储存在数据库里。

2. Long转id(解码)

从数据库取出Long型数据还要解码成老师的id,其步骤和上面是相反的。

先要把Long型转成字节数组。强转byte其实就是取出long的前8位。代码:

    static byte[] convertLongToByte(long num) //把长整型拆成长度为8的字节数组
    {
        byte[] bytes = new byte[8];
        bytes[0] = (byte)num;
        bytes[1] = (byte)(num>>8);
        bytes[2] = (byte)(num>>16);
        bytes[3] = (byte)(num>>24);
        bytes[4] = (byte)(num>>32);
        bytes[5] = (byte)(num>>40);
        bytes[6] = (byte)(num>>48);
        bytes[7] = (byte)(num>>56);
        return bytes;
    }

得到字节数组,要还原出id的List,只需要for循环遍历就行。代码:

    static List<Integer> getIndexList(byte[] bytes) //提取字节数组中标志位1的下标
    {
        List<Integer> indexList = new ArrayList<>();
        for(int i=0;i<8;i++)
        {
            int b = bytes[i];
            for(int j=0;j<8;j++)
            {
                if((b&0x01)==1) indexList.add(i*8+j+1);
                b = b>>1;
            }
        }
        return indexList;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值