java 位掩码的正确用法?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14281827/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 15:44:49  来源:igfitidea点击:

Correct usage of bitmask?

javabit-manipulationbitwise-operatorsbitbitmask

提问by maxammann

heyhey, just have a question about bitmasks. I think I know now what they are and where they can be used. I want to store specific permissions like BUILD, BREAK and INTERACT and maybe more for specific groups. The code below should do this but Im not very sure whether this is the corrent "style".

嘿嘿,只是有一个关于位掩码的问题。我想我现在知道它们是什么以及它们可以在哪里使用。我想存储特定的权限,如 BUILD、BREAK 和 INTERACT,可能还有更多特定组的权限。下面的代码应该这样做,但我不太确定这是否是正确的“风格”。

The idea is to use the first 3 bits here to store the permissions of the first group, then use the next three bits for the second group and so on. So my question is now whether this is a good way or not or what would be better?

思路是这里使用前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;
    }
}

EDIT: I want to use as little memory as possible because I'll need to store much data.

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

EDIT: I also need to store it in a sql database, but it should not make probs.

编辑:我还需要将它存储在 sql 数据库中,但它不应该产生问题。

回答by Petr Jane?ek

You knew this kind of answer had to appear sooner or later, so here it goes:

你知道这种答案迟早会出现,所以它是这样的:

Although the usage of bitmasks is arguably the fastest and has the lowest memory consuption of all the alternative options, it's also very error prone to get right and it's mostly discouraged to use except in some very edge cases. It's a classic low-level tool. If done right, works wonders, if misused, can wreak havoc.

虽然位掩码的使用可以说是最快的,并且在所有替代选项中内存消耗最低,但它也很容易出错,并且除了在一些非常边缘的情况下,大多数情况下不鼓励使用。这是一个经典的低级工具。如果做得好,会产生奇迹,如果滥用,可能会造成严重破坏。

Therefore, the correctapproach is to use higher-level abstractions for this, namely enumsand EnumSets. The speed and memory consuption are comparable, although slightly worse, of course. In a general case, they're absolutely sufficient, though. There are many ways of how to do that based on your exact context and needs. One of the possibilities could be this:

因此,正确的方法是为此使用更高级别的抽象,即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;
    }
}

This alone enables you to do exactly the things you did, but with two differences:

仅此一项就可以让您完全按照您所做的去做,但有两个不同之处:

  1. Now it's type-safe and more foolproof, I think. No reinventing of the wheel needed.
  2. It uses more memory. Not by much, because EnumSetthis small is usually just a long.
  1. 我认为,现在它是类型安全的,而且更加万无一失。无需重新发明轮子。
  2. 它使用更多的内存。不是很多,因为EnumSet这个小通常只是一个long.




EDITto answer OP's comment on storing the EnumSet to a database:

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

Yes, this can be a concern since storing an intis infinitely easier. If you still considered sticking to using an EnumSet, then there are several possibilities from the top of my head:

是的,这可能是一个问题,因为存储一个int非常容易。如果您仍然考虑坚持使用EnumSet,那么我的脑海中有几种可能性:

  1. Look at SO.People have tried to tackle the problem before.

  2. Save the names of the values in the EnumSet:

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

    You can then easily rebuild the values:

    for (String word : stringsFromDtb) {
        p.alliance.add(Permission.valueOf(word));
    }
    
  3. Save the ordinals. This is VERY dangerous as you can easily break it with changing the Permissionenum. Also, any random number could be fed in to break this.

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

    You can then easily rebuild the values:

    for (int ordinal : ordinalsFromDtb) {
        p.alliance.add(Permission.values()[ordinal]);
    }
    
  4. Serialize the EnumSetthe usual way and store the binary data directly or BASE64ed. Ehm.

  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);
    }
    

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

    for (String word : stringsFromDtb) {
        p.alliance.add(Permission.valueOf(word));
    }
    
  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());
    }
    

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

    for (int ordinal : ordinalsFromDtb) {
        p.alliance.add(Permission.values()[ordinal]);
    }
    
  4. EnumSet用通常的方式序列化,直接或BASE64ed存储二进制数据。嗯。

---

---

EDITafter EDIT:

编辑编辑

Ad. your remarks of making indexes for your enumvalues so that when you changed or reordered them in the future, it would still work. There is an easy way to do this with enums! It's basically the middle way between bitfields and enums, it retains the type safety and all enumfeatures and still has the advantages of bitfields.

广告。您对为您的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;
    }
}

You'll then save the indexes into your database and will only have to make sure the parsing from it is right. Also, in the future, it helps to only comment out (not delete) any unneeded enum values so they still remain visible for you and you won't take up it's index. Or just mark it as @Deprecatedand you don't have to delete anything ;).

然后您将索引保存到您的数据库中,并且只需要确保从中进行的解析是正确的。此外,在将来,它有助于仅注释(而不是删除)任何不需要的枚举值,以便它们仍然对您可见,并且您不会占用它的索引。或者只是将其标记为@Deprecated,您不必删除任何内容;)。

回答by Daniel Fischer

There are a few points where I think you made an error

有几点我认为你犯了错误

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

This clears all permissions for other groups when setting the permissions for group. If you want to retain the permissions for other groups,

这会在为 设置权限时清除其他组的所有权限group。如果要保留其他组的权限,

// clear permissions for `group`
this.permissions &= ~(0x7 << group * PERMISSIONS);
// set permissions for `group`
this.permissions |= permissions << group * PERMISSIONS;

that leaves the permissions for the other goups untouched.

这使得其他组的权限保持不变。

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

This mixes the permissions for all groups with the permissions you want to add for group. I think what you want there is

这会将所有组的权限与您要添加的权限混合在一起group。我想你想要的是

this.permissions |= permission << group * PERMISSIONS;

to just add permissionfor groupwithout changing anything else.

只需添加permissiongroup无需更改任何其他内容。

public boolean hasPermission(int permission, int group)
{
    return (permissions & permission << group * PERMISSIONS) == permission;
}

You are shifting in the wrong direction here, if group > 0, the bitwise and will have no bits among the lowest three,

您在这里向错误的方向移动,如果group > 0,按位并且在最低的三个中没有位,

return ((permissions >> group *PERMISSIONS) & permission) == permission;