Original address: Liang Guizhao's blog
Blog address: http://blog.720ui.com
Welcome to the official account: "Server Thinking". A group of people with the same frequency, grow together, improve together, and break the limitations of cognition.
How to elegantly use bit operations to achieve product requirements?
Before starting the text, let's talk about the system permissions design of Linux. In the Linux system, in order to ensure the security of files, the access rights of file owners, users in the same group, and other users are managed separately. Among them, the file owner is the user who created the file or directory. Users in the same group are all users in the group to which they belong. Other users refer to other users who are neither the owner of the file nor the users of the same group. Each file and directory has read, write, and execute permissions, which are independent of each other.
In the Linux system, the access authority of each file can be represented by 9 letters, and each 3 letters represent a type of user authority, representing the file creator, the same group user, and other users. Among them, r represents read permission, w represents write permission, and x represents execute permission. Modifying file permissions through functional mode consists of three parts, including objects, operations, and permissions.
Assuming that you need to increase the write permissions of users in the same group, let's look at an example.
chmod g+w /root/install.log
In addition, the visits of each type of user can also be represented numerically.
Then, the common Linux file permission operations can be summarized through the numerical pattern.
Assuming that we need to set the creator to be readable, writable and executable, readable by users in the same group, and readable by other users, we can write:
chmod 755 /root/install.log
In fact, file access permissions in Linux are a very classic use case for bit operations. Coincidentally, let's take a look at Java java.lang.reflect.Modifier
. Among them, the Modifier
class defines static constants in hexadecimal.
public static final int PUBLIC = 0x00000001;
public static final int PRIVATE = 0x00000002;
public static final int PROTECTED = 0x00000004;
public static final int STATIC = 0x00000008;
public static final int FINAL = 0x00000010;
public static final int SYNCHRONIZED = 0x00000020;
public static final int VOLATILE = 0x00000040;
public static final int TRANSIENT = 0x00000080;
public static final int NATIVE = 0x00000100;
public static final int INTERFACE = 0x00000200;
public static final int ABSTRACT = 0x00000400;
public static final int STRICT = 0x00000800;
...
Next, the Modifier
class provides many static methods, such as & PUBLIC
the hexadecimal value corresponding to the return value of the isPublic() method. If it is not 0, it means that it contains the public modifier.
public static boolean isPublic(int mod) {
return (mod & PUBLIC) != 0;
}
There is an important knowledge point here, using the & operation, the two bits are 1 at the same time, the result is 1, otherwise it is 0. That is 0&0=0; 0&1=0; 1&0=0; 1&1=1. For example: 3&1 is 0000 0011 & 0000 0001 = 00000001, the value is 1.
0000 0011
& 0000 0001
= 0000 0001
At the same time, the Modifier
class also uses the | operation to ensure that as long as one of the two objects participating in the operation is 1, its value is 1. That is, 0|0=0; 0|1=1; 1|0=1; 1|1=1. Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE | Modifier.ABSTRACT | Modifier.STATIC | Modifier.FINAL | Modifier.STRICT results in 3103, which is 110000011111.
private static final int CLASS_MODIFIERS =
Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE |
Modifier.ABSTRACT | Modifier.STATIC | Modifier.FINAL |
Modifier.STRICT;
0000 0000 0000 0001
| 0000 0000 0000 0010
| 0000 0000 0000 0100
| 0000 0000 0000 1000
| 0000 0000 0001 0000
| 0000 0100 0000 0000
| 0000 1000 0000 0000
= 0000 1100 0001 1111
Back to the book, we stand on the shoulders of our predecessors and design elegant multi-selection signs through bit operations, such as permission control or multi-state management through bit operations. The advantage of this is that it is easy to expand and avoid field expansion during database design. , to reduce disk storage space.
Suppose, we now have a business requirement: add a notification method to the task, the options include IM messages, system reminders, emails, and text messages. After selecting IM message, it supports IM instant sending; after selecting system reminder, it supports in-station message push; after selecting select mailbox, the follow-up reminder content of the task can be notified by sending an email to the relevant person's mailbox; after selecting SMS, the task Follow-up related reminders can be notified by sending a text message to the relevant person.
When we design database tables, usually, we combine multiple identification fields into one field, and save this field as a string. For example, if there is 1, it means IM is supported, and 2 means it supports system messages. 3 means support email, 4 means support SMS. At this point, if all are satisfied at the same time, its storage form is a comma-separated string: "1,2,3,4". The advantage of this design is that it not only eliminates the redundancy of the same fields, but also does not need to add new fields when adding new channel categories.
IM(1, "IM消息"),
SYSTEM(2, "系统提醒"),
MAIL(3, "邮箱"),
SMS(4, "短信");
But when querying data, we need to separate the strings. And string type fields are inferior to integer fields in terms of query efficiency and storage space. Therefore, we can solve this problem with "bits". We take different bits to represent different classes of identity fields, respectively.
Therefore, when a task supports IM, it saves 1 (0000 0001); when it supports system messages, it saves 2 (0000 0010); when it supports mailboxes, it saves 4 (0000 0100); when it supports SMS, it saves 8 (0000 1000). All four are supported, save 15 (0000 11111).
Rank | value | illustrate |
---|---|---|
00000001 | 1 | IM support |
00000010 | 2 | Support system messages |
00000011 | 3 | Support IM, system message |
00000100 | 4 | Support mailbox |
00000101 | 5 | Support email, IM |
00000110 | 6 | Support mailbox, system message |
00000111 | 7 | Support mailbox, IM, system message |
00001000 | 8 | SMS support |
... | ||
00001111 | 15 | Support mailbox, IM, system message, SMS |
Next, we implement additions, deletions and modifications by encapsulating common methods.
/**
* 判断
* @param mod 用户当前值
* @param value 需要判断值
* @return 是否存在
*/
public static boolean hasMark(long mod, long value) {
return (mod & value) == value;
}
/**
* 增加
* @param mod 已有值
* @param value 需要添加值
* @return 新的状态值
*/
public static long addMark(long mod, long value) {
if (hasMark(mod, value)) {
return mod;
}
return (mod | value);
}
/**
* 删除
* @param mod 已有值
* @param value 需要删除值
* @return 新值
*/
public static long removeMark(long mod, long value) {
if (!hasMark(mod, value)) {
return mod;
}
return mod ^ value;
}
To sum up, when we design the database, we combine multiple identification fields into one field and save this field as a string, which not only eliminates the redundancy of the same field, but also eliminates the need for new channel categories when adding new channel categories. Add new fields, but string type fields are inferior to integer fields in terms of query efficiency and storage space. Therefore, we can refer to using "bits" to solve this problem. We take different bits to represent different classes of identity fields, respectively.