Proper use of bitmask?

heyhey, just a question about bitmasks. I think now I know what it is and where they can be used. I want to store certain permissions, such as BUILD, BREAK and INTERACT, and possibly more for certain groups. The code below should do this, but I'm not very sure if this is the root "style".

The idea is to use the first 3 bits to store the permissions of the first group, and then use the next three bits for the second group and so on. So now my question is: is it good or not, or which is better?

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 need to store a lot of data.

EDIT: I also need to save it in the sql database, but it should not create a problem.

+7
source share
2 answers

You knew that such an answer should have appeared sooner or later, so here it is:

Although using bitmasks is arguably the fastest and has the lowest amount of memory for all alternatives, it is also very error prone and this is generally not recommended, except in some cases with a very edge. This is a classic low level tool. If everything is done correctly, it works wonders; if they are used incorrectly, it can cause damage.

Therefore, the right approach is to use higher-level abstractions for this, namely enums and EnumSets . Of course, speed and memory are the same, although, of course, a little worse. In general, they are absolutely sufficient. There are many ways to do this based on your exact context and needs. One possibility might be:

 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 in itself allows you to do exactly what you did, but with two differences:

  • Now it's safe and safer, I think. No need to reinvent the wheel.
  • It uses more memory. Not much, because EnumSet is small, usually just long .


EDIT to respond to OP comment about storing EnumSet in the database:

Yes, this can be a problem, since saving int infinitely simpler. If you still think that you are sticking to using EnumSet , then I have several options:

  • Look at SO. People tried to solve the problem before.

  • Save the value names in EnumSet :

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

    Then you can easily restore the values:

     for (String word : stringsFromDtb) { p.alliance.add(Permission.valueOf(word)); } 
  • Save the ordinals. This is VERY dangerous since you can easily break it with a change in the Permission enumeration. In addition, any random number can be cast to break it.

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

    Then you can easily restore the values:

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

---

EDIT after EDIT :

Ad. Your comments on creating indexes for your enum values ​​so that if you change or change them in the future, it will still work. There is an easy way to do this with enums ! This is basically the middle path between enums and enums , it retains type safety and all the functions of enum and still has the advantages of bitfields.

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

Then you save the indexes in your database and just make sure that the parsing of them is right. In addition, in the future, it only helps to comment (not delete) any unnecessary enumeration values ​​so that they still remain visible to you, and you will not use this index. Or just mark it as @Deprecated and you don't need to delete anything;).

+11
source

There are a few points when I think you made a mistake

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

This frees all permissions for other groups when setting permissions for group . If you want to keep permissions for other groups,

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

which leaves the rights for other goups intact.

 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 you want there

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

just add permission for group without changing anything.

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

Here you are moving in the wrong direction if group > 0 is bitwise and will not have bits among the lowest three,

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

All Articles