How to elegantly use bit operations to achieve product requirements?

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.

image.png

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.

image.png

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.

image.png

Then, the common Linux file permission operations can be summarized through the numerical pattern.

image.png

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 Modifierclass 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 Modifierclass 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.

image.png

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.

image.png

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.

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324207398&siteId=291194637