位运算在权限系统中的实战应用:如何用1个字段解决32种权限组合查询?

概述

本文从权限系统设计的困境引入,到最后给出通用的枚举管理方案,看吧,绝不会吃亏。

一、从两个低效方案说起:权限系统的设计困境

假设我们需要设计一个用户权限系统,支持 READ(读)、WRITE(写)、DELETE(删)等操作权限,且未来可能扩展新权限。用户权限需存储在 MySQL 中,要求能快速查询以下场景:

  1. 仅拥有 READ、WRITE、DELETE 权限的用户
  2. 同时拥有其中任意一种或多种权限的用户

以下是新手常见的两种低效实现方案:

❌ 方案一:字符串拼接存储

在字段中以逗号分隔存储权限,例如 "READ,WRITE",通过 LIKE 查询匹配权限:

SELECT * FROM users WHERE permissions LIKE '%READ%';  

致命缺陷

  • LIKE 查询无法利用索引,全表扫描效率极低
  • 字符串解析逻辑复杂,容易出错
❌ 方案二:多字段分列存储

为每个权限创建独立字段(如 read_flag, write_flag),通过索引加速查询:

SELECT * FROM users WHERE read_flag = 1 AND write_flag = 1;  

新问题诞生

  • 扩展性差:新增权限需改表结构、代码逻辑
  • 存储浪费:每权限一列,索引占用空间大
  • 组合查询复杂:多条件拼接难以维护

二、位运算的降维打击:1个字段存储32种权限

位标志(bit flags)是一种使用位操作来存储和管理多个布尔状态的技术。通过使用整数的不同位,每个位可以表示一个独立的布尔值(开/关,真/假)。这在需要高效存储和操作多个布尔标志时特别有用,如在权限管理、状态跟踪等应用中。

核心思想

用整型字段的二进制位表示权限状态

  • 每个权限对应一个二进制位(0-未授权,1-已授权)
  • 通过位运算实现权限的组合存储与快速查询
技术优势:
  1. 极致存储:1个 INT 字段支持32种权限
  2. 索引友好:直接对整数字段查询,索引命中率高
  3. 扩展灵活:新增权限无需修改表结构
  4. 运算高效:位运算性能远超字符串处理

示例应用

假设我们有一个应用程序,其中每个用户可以有不同的权限:读、写、执行和删除。我们可以将这些权限表示为位标志。

public class BitFlagsExample {
    // 定义标志
    private static final int READ_PERMISSION = 1 << 0;   // 0001
    private static final int WRITE_PERMISSION = 1 << 1;  // 0010
    private static final int EXECUTE_PERMISSION = 1 << 2;// 0100
    private static final int DELETE_PERMISSION = 1 << 3; // 1000

    public static void main(String[] args) {
        int userPermissions = 0;

        // 设置权限
        userPermissions |= READ_PERMISSION;
        userPermissions |= WRITE_PERMISSION;

        // 检查权限
        System.out.println("Read permission: " + ((userPermissions & READ_PERMISSION) != 0));
        System.out.println("Write permission: " + ((userPermissions & WRITE_PERMISSION) != 0));
        System.out.println("Execute permission: " + ((userPermissions & EXECUTE_PERMISSION) != 0));
        System.out.println("Delete permission: " + ((userPermissions & DELETE_PERMISSION) != 0));

        // 清除写权限
        userPermissions &= ~WRITE_PERMISSION;
        System.out.println("Write permission after removal: " + ((userPermissions & WRITE_PERMISSION) != 0));

        // 切换执行权限
        userPermissions ^= EXECUTE_PERMISSION;
        System.out.println("Execute permission after toggle: " + ((userPermissions & EXECUTE_PERMISSION) != 0));
    }
}

三、位标志操作手册:5种核心操作

1. 定义权限枚举

public enum Permission {  
    READ(1 << 0),   // 0001  
    WRITE(1 << 1),  // 0010  
    DELETE(1 << 2); // 0100  

    private final int bit;  
    Permission(int bit) { this.bit = bit; }  
    public int getBit() { return bit; }  
}  

2. 权限组合操作

flags 初始值为 0。

操作类型 代码示例 二进制效果
添加权限 flags |= READ.getBit() 0000 → 0001
移除权限 flags &= ~WRITE.getBit() 0011 → 0001
检查权限 (flags & DELETE.getBit()) != 0 0100 → true/false
切换权限 flags ^= EXECUTE.getBit() 0000 ↔ 1000
以下是代码示例
  1. 定义标志

    • 每个位可以用来表示一个标志。通常使用位移操作来定义标志。
    • 例如:
      final int FLAG_A = 1 << 0;  // 0001
      final int FLAG_B = 1 << 1;  // 0010
      final int FLAG_C = 1 << 2;  // 0100
      final int FLAG_D = 1 << 3;  // 1000
      
  2. 设置标志(添加权限)

    • 使用按位或操作符 | 来设置标志。
    • 例如,要设置 FLAG_A 和 FLAG_C:
      int flags = 0;
      flags |= FLAG_A;  // 设置 FLAG_A
      flags |= FLAG_C;  // 设置 FLAG_C
      
  3. 清除标志(移除权限)

    • 使用按位与操作符 & 和按位取反操作符 ~ 来清除标志。
    • 例如,要清除 FLAG_A:
      flags &= ~FLAG_A;  // 清除 FLAG_A
      
  4. 检查标志(检查权限)

    • 使用按位与操作符 & 来检查标志是否被设置。
    • 例如,检查 FLAG_B 是否被设置:
      boolean isFlagBSet = (flags & FLAG_B) != 0;
      
  5. 切换标志(切换权限)

    • 使用按位异或操作符 ^ 来切换标志。
    • 例如,切换 FLAG_D:
      flags ^= FLAG_D;
      

四、实战:数据库查询优化技巧

场景1:查询精确权限或权限组合

-- 查询同时拥有 READ + WRITE 权限的用户  
SELECT * FROM users WHERE permissions = (1 | 2); -- 十进制值 3  

场景2:查询包含任意权限

-- 查询拥有 READ 或 WRITE 权限的用户,直观上看这样写 sql 就可以,但这样不走索引,建议在 Java 层面就做好转换,使用 IN 语句来进行查询,这样就可以走索引。
SELECT * FROM users WHERE permissions & 3 != 0;  
// 生成所有包含 READ 和 WRITE 的权限组合  ,getAllCombinationsWithFlags方法在下文中有封装
List<Integer> combinations = flagManager.getAllCombinationsWithFlags(READ, WRITE);  
String sql = "SELECT * FROM users WHERE permissions IN (" + StringUtils.join(combinations, ",") + ")";  

五、高级封装:通用权限管理工具

功能亮点

  • 自动解析权限组合
  • 支持AND/OR条件生成
  • 无缝对接数据库查询
核心接口 BitFlag
public interface BitFlag {  
    int getBit();  // 返回权限位值  
}  
通用管理器 GenericBitFlagManager
    // 方法包括:  
    // - 添加/移除权限  
    // - 检查权限  
    // - 生成权限组合
  
import java.util.ArrayList;  
import java.util.List;  
  
// 通用的位标志管理类  
public class GenericBitFlagManager<T extends Enum<T> & BitFlag> {  
  
    private final Class<T> enumType;  
  
    /**  
     * 构造函数,初始化枚举类型  
     * @param enumType 实现了BitFlag接口的枚举类型  
     */  
    public GenericBitFlagManager(Class<T> enumType) {  
        this.enumType = enumType;  
    }  
  
    /**  
     * 获取具有指定标志的所有枚举常量  
     * @param flagValue 标志值  
     * @return  
     */    public List<T> getFlags(int flagValue) {  
        List<T> flags = new ArrayList<>();  
        for (T flag : enumType.getEnumConstants()) {  
            if ((flagValue & flag.getBit()) != 0) {  
                flags.add(flag);  
            }  
        }  
        return flags;  
    }  
    /**  
     * 检查是否具有指定的标志  
     * @param flagValue 标志值  
     * @param flag 标志枚举  
     * @return  
     */    public boolean hasFlag(int flagValue, T flag) {  
        return (flagValue & flag.getBit()) != 0;  
    }  
  
    /**  
     * 增加指定的标志  
     * @param flagValue 要增加的标志值  
     * @param flag 要增加的标志  
     * @return  
     */    public int addFlag(int flagValue, T flag) {  
        return flagValue | flag.getBit();  
    }  
  
    /**  
     * 增加指定的标志  
     * @param flagValue 当前的标志值  
     * @param flags 要增加的标志,可变参数  
     * @return 新的标志值,包含所有增加的标志  
     */  
    public int addFlags(int flagValue, T... flags) {  
        for (T flag : flags) {  
            flagValue |= flag.getBit();  
        }  
        return flagValue;  
    }  
  
    /**  
     * 移除指定的标志  
     * @param flagValue 要移除的标志值  
     * @param flag 要移除的标志  
     * @return  
     */    public int removeFlag(int flagValue, T flag) {  
        return flagValue & ~flag.getBit();  
    }  
    /**  
     * 获取同时具有所有指定标志的所有可能组合  
     * @param requiredFlags 所需的标志  
     * @return  
     */    public List<Integer> getAllCombinationsWithFlags(T... requiredFlags) {  
        List<Integer> result = new ArrayList<>();  
        int requiredFlagBits = 0;  
  
        // 计算所需标志的组合  
        for (T flag : requiredFlags) {  
            requiredFlagBits |= flag.getBit();  
        }  
  
        // 获取所有可能的组合  
        int maxCombination = (1 << enumType.getEnumConstants().length) - 1;  
  
        // 遍历从 0 到 maxCombination 的所有整数值  
        for (int i = 0; i <= maxCombination; i++) {  
            if ((i & requiredFlagBits) == requiredFlagBits) {  
                result.add(i);  
            }  
        }  
  
        return result;  
    }  
    /**  
     * 获取包含指定标志中任意一个的所有可能组合  
     * @param requiredFlags 所需的标志  
     * @return  
     */    public List<Integer> getAllCombinationsWithAnyFlags(T... requiredFlags) {  
        List<Integer> result = new ArrayList<>();  
        int requiredFlagBits = 0;  
  
        // 计算所需标志的组合  
        for (T flag : requiredFlags) {  
            requiredFlagBits |= flag.getBit();  
        }  
  
        // 获取所有可能的组合  
        int maxCombination = (1 << enumType.getEnumConstants().length) - 1;  
  
        // 遍历从 0 到 maxCombination 的所有整数值  
        for (int i = 0; i <= maxCombination; i++) {  
            if ((i & requiredFlagBits) != 0) {  
                result.add(i);  
            }  
        }  
  
        return result;  
    }  
}

使用示例

第一步:定义枚举类

枚举类有两个要求:

  • 需继承BitFlag接口
  • 需使用位运算定义枚举。

示例:

import com.gis.common.untils.BitFlag;  
  
public enum Permission implements BitFlag {  
    READ(1 << 0),    // 0001  
    WRITE(1 << 1),   // 0010  
    EXECUTE(1 << 2), // 0100  
    DELETE(1 << 3);  // 1000  
  
    private final int bit;  
  
    Permission(int bit) {  
        this.bit = bit;  
    }  
  
    public int getBit() {  
        return bit;  
    }  
}
第二步:使用GenericBitFlagManager进行管理
  
import com.gis.common.untils.GenericBitFlagManager;  
  
import java.util.List;  
  
public class GenericBitFlagManagerTest {  
  
  
    public static void main(String[] args) {  
  
        GenericBitFlagManager<Permission> permissionManger = new GenericBitFlagManager<>(Permission.class);  
  
        int userPermission = 0;  
  
        userPermission = permissionManger.addFlag(userPermission, Permission.READ);  
        userPermission = permissionManger.addFlag(userPermission, Permission.WRITE);  
  
        // 检查权限  
        System.out.println("Has READ permission: " + permissionManger.hasFlag(userPermission, Permission.READ));  
        System.out.println("Has EXECUTE permission: " + permissionManger.hasFlag(userPermission, Permission.EXECUTE));  
  
        // 获取用户权限列表  
        List<Permission> flags = permissionManger.getFlags(userPermission);  
        System.out.println("current User permissions:" + flags);  
  
        // 删除用户的读权限  
        permissionManger.removeFlag(userPermission, Permission.READ);  
        System.out.println("Has READ permission: " + permissionManger.hasFlag(userPermission, Permission.READ));  
  
        // 查询同时拥有某几个权限的用户(查数据库)  
        List<Integer> allCombinationsWithFlags = permissionManger.getAllCombinationsWithFlags(Permission.READ, Permission.WRITE);  
        for (Integer permissionFlag : allCombinationsWithFlags) {  
            List<Permission> permissions = permissionManger.getFlags(permissionFlag);  
            System.out.println(permissionFlag + ":具有如下权限:" + permissions);  
        }  
        System.out.println("==========================================");  
        // 查询拥有以下几个权限之一的用户(查数据库)  
        List<Integer> allCombinationsWithAnyFlags = permissionManger.getAllCombinationsWithAnyFlags(Permission.READ, Permission.WRITE);  
        for (Integer permissionFlag : allCombinationsWithAnyFlags) {  
            List<Permission> permissions = permissionManger.getFlags(permissionFlag);  
            System.out.println(permissionFlag + ":具有如下权限:" + permissions);  
        }  
    }  
}

六、方案对比:为什么位运算是最优解?

维度 字符串方案 多字段方案 位运算方案
存储空间 极高 极低
查询性能 一般 优秀
扩展成本 零成本
代码可维护性 一般 还算可以

位运算除了代码不太直观、不太容易理解之外,其他方面都优于其他的方案。但通过函数封装,用户不用关心底层细节,使用起来也是十分方便的。


七、写在最后

通过位运算方案,我们成功实现了:
✅ 存储空间减少
✅ 查询性能提升
✅ 系统扩展零成本

以上,祝你今天愉快。

你可能感兴趣的:(Java实战,java,数据库,位运算)