嘿,有一个关于比特掩码的问题。我想我现在知道它们是什么,在哪里可以使用。我想存储特定的权限,比如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;
}
}
编辑:我想用尽可能少的内存,因为我需要存储很多数据。
EDIT:我还需要将它存储在sql数据库中,但它不应该产生问题。
你知道这种答案迟早会出现,所以它来了:
尽管比特掩码的使用可以说是所有替代选项中速度最快、内存消耗最低的,但它也很容易出错,除非在一些非常边缘的情况下,否则大多不鼓励使用。这是一个经典的低级工具。如果做对了,奇迹就会产生,如果被滥用,可能会造成严重破坏。
因此,正确的方法是为此使用更高级别的抽象,即enums
和EnumSets
。速度和内存消耗是相当的,当然,虽然稍微差一点。不过,在一般情况下,它们是绝对足够的。根据你的具体情况和需求,有很多方法可以做到这一点。其中一种可能性可能是:
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;
}
}
仅此一点就可以让你做你做过的事情,但有两个区别:
- 我认为,现在它是打字安全的,而且更简单。无需重新设计车轮
- 它使用更多的内存。不多,因为这么小的
EnumSet
通常只是long
编辑以回答OP关于将EnumSet存储到数据库的评论:
是的,这可能是一个问题,因为存储int
要容易得多。如果你仍然考虑坚持使用EnumSet
,那么我脑海中有几种可能性:
看看SO。人们以前曾试图解决这个问题。
将值的名称保存在
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)); }
保存序号。这是非常危险的,因为通过更改
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]); }
以通常的方式串行化
EnumSet
,并直接或BASE64ed存储二进制数据。嗯。
---
编辑后的:
Ad。您关于为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;
}
}
然后,您将把索引保存到数据库中,并且只需要确保从中进行的解析是正确的。此外,在未来,只注释掉(而不是删除(任何不需要的枚举值会有所帮助,这样它们对您来说仍然可见,而且您不会占用它的索引。或者只需将其标记为@Deprecated
,就不必删除任何内容;(。
我认为您在中犯了一些错误
public void setPermissions(int permissions, int group)
{
this.permissions = permissions << group * PERMISSIONS;
}
这将在设置group
的权限时清除其他组的所有权限。如果您想保留其他组的权限,
// clear permissions for `group`
this.permissions &= ~(0x7 << group * PERMISSIONS);
// set permissions for `group`
this.permissions |= permissions << group * PERMISSIONS;
这使得其他goup的权限保持不变。
public void addPermissions(int permission, int group)
{
setPermissions(this.permissions | permission, group);
}
这将混合所有组的权限和您要为group
添加的权限。我想你想要的是
this.permissions |= permission << group * PERMISSIONS;
仅为CCD_ 18添加CCD_。
public boolean hasPermission(int permission, int group)
{
return (permissions & permission << group * PERMISSIONS) == permission;
}
如果group > 0
是按位的,则在最低的三个中没有位
return ((permissions >> group *PERMISSIONS) & permission) == permission;