正确使用位掩码?

max*_*ann 8 java bit-manipulation bitmask bit bitwise-operators

heyhey,只是对bitmasks有疑问.我想我现在知道它们是什么以及它们可以在哪里使用.我想存储特定权限,如BUILD,BREAK和INTERACT,以及更多特定群组.下面的代码应该这样做但我不太确定这是否是正确的"风格".

这个想法是在这里使用前3位来存储第一组的权限,然后使用接下来的三位用于第二组,依此类推.所以现在我的问题是这是一种好方法还是更好的方法?

public class Test {
    private int permissions = 0;

    /**
     * The amount of permissions, currently: {@link #BREAK}, {@link #BUILD}, {@link #INTERACT}
     */
    private static final int PERMISSIONS = 3;
    /**
     * The different permissions
     */
    public static final int BUILD = 1, BREAK = 2, INTERACT = 4;
    /**
     * The different groups
     */
    public static final int ALLIANCE = 0, OUTSIDERS = 1;

    public void setPermissions(int permissions, int group)
    {
        this.permissions = permissions << group * PERMISSIONS;
    }

    public void addPermissions(int permission, int group)
    {
        setPermissions(this.permissions | permission, group);
    }

    public boolean hasPermission(int permission, int group)
    {
        return (permissions & permission << group * PERMISSIONS) == permission;
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:我想尽可能少使用内存,因为我需要存储大量数据.

编辑:我还需要将它存储在一个SQL数据库中,但它不应该生成probs.

Pet*_*ček 12

你知道这种答案迟早会出现,所以在这里:

尽管位掩码的使用可以说是最快的并且具有所有替代选项中最低的内存消耗,但它也非常容易出错并且除了在一些非常边缘的情况下使用它之外最不鼓励使用.这是一款经典的低级工具.如果做得好,作品奇迹,如果误用,可能会造成严重破坏.

因此,正确的方法是使用更高级别的抽象,即enumsEnumSets.速度和记忆的消耗是可比较的,当然稍微差一点.但在一般情况下,它们绝对足够了.根据您的确切背景和需求,有很多方法可以做到这一点.其中一种可能性是:

public enum Permission {
    BUILD, BREAK, INTERACT;
}

public class Permissions {
    private final Set<Permission> alliance = EnumSet.noneOf(Permission.class);
    private final Set<Permission> outsiders = EnumSet.noneOf(Permission.class);

    public Set<Permission> alliance() {
        return alliance;
    }

    public Set<Permission> outsiders() {
        return outsiders;
    }
}
Run Code Online (Sandbox Code Playgroud)

仅此一项就可以让您完成您所做的事情,但有两点不同:

  1. 我认为现在它是类型安全的,更加万无一失.不需要重新发明轮子.
  2. 它使用更多的内存.不是很多,因为EnumSet这个小通常只是一个long.


编辑以回答OP关于将EnumSet存储到数据库的注释:

是的,这可能是一个问题,因为存储int更容易.如果你仍然认为坚持使用EnumSet,那么从我的头脑中有几种可能性:

  1. 看看SO.以前人们试图解决这个问题.

  2. 保存值的名称EnumSet:

    Permissions p = new Permissions();
    p.alliance().addAll(EnumSet.of(Permission.BREAK, Permission.BUILD));
    for (Permission permission : p.alliance()) {
        System.out.println(permission);
    }
    
    Run Code Online (Sandbox Code Playgroud)

    然后,您可以轻松地重建值:

    for (String word : stringsFromDtb) {
        p.alliance.add(Permission.valueOf(word));
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 保存序数.这非常危险,因为您可以通过更改Permission枚举轻松打破它.此外,任何随机数都可以输入以打破这一点.

    Permissions p = new Permissions();
    p.alliance().addAll(EnumSet.of(Permission.BREAK, Permission.BUILD));
    for (Permission permission : p.alliance()) {
        System.out.println(permission.ordinal());
    }
    
    Run Code Online (Sandbox Code Playgroud)

    然后,您可以轻松地重建值:

    for (int ordinal : ordinalsFromDtb) {
        p.alliance.add(Permission.values()[ordinal]);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 序列化EnumSet通常的方式并直接存储二进制数据或BASE64ed.埃姆.

---

编辑编辑:

广告.您为您的enum值制作索引的评论,以便在将来更改或重新排序时,它仍然有效.有一个简单的方法来做到这一点enums!它基本上是位域之间的中间路径enums,它保留了类型安全性和所有enum功能,并且仍然具有位域的优点.

public enum Permission {
    /* I like to have binary literals in place of bit fields,
     * but any notation will work */
    BUILD   (0b0001),
    BREAK   (0b0010),
    INTERACT(0b0100);

    private final int index;

    private Permission(int index) {
        this.index = index;
    }

    public int index() {
        return index;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您将索引保存到数据库中,只需要确保从中解析是正确的.此外,将来,它只会注释掉(不删除)任何不需要的枚举值,这样它们仍然可以为您显示,并且您不会占用它的索引.或者只是将其标记为@Deprecated,您不必删除任何内容;).