亲宝软件园·资讯

展开

编程思想:巧用位运算重构代码

木宛城主 人气:2
### 开篇 > 在一门编程语言中,往往会提供大量的运算符。按功能来分的话,有算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符等。这些对于大家来说都不陌生。但是,本期的主角『位运算』符相对而言是比较少去使用的。因为位运算符主要针对两个二进制数进行位运算。 巧用位运算能极大的精简代码和提高程序效率。所以,在一些优秀的开源代码中,经常能出现位运算。所以,把位运算这种思想迁移到业务代码里,有时候往往能起到柳暗花明般的重构。 ### 位运算 在 JAVA 语言中,定义了诸多的**位运算**符,如下所示: | **运算符** | **描述** | | ---------- | -------- | | & | 与 | | \| | 或 | | ~ | 非 | | ^ | 异或 | | << | 左移 | | >> | 右移 | **&(与)** | **十进制** | **二进制** | | ----------- | ---------- | | 3 | 0 0 1 1 | | 5 | 0 1 0 1 | | & 后结果:1 | 0 0 0 1 | 即:对应位都为 1 时,才为 1,否则全为 0。 **|(或)** | **十进制** | **二进制** | | ------------- | ---------- | | 3 | 0 0 1 1 | | 5 | 0 1 0 1 | | \| 后结果 :7 | 0 1 1 1 | 即:对应位只要有 1 时,即为 1,否则全为 0。 **~(非)** | **十进制** | **二进制** | | ------------ | ---------- | | 3 | 0 0 1 1 | | ~ 后结果:12 | 1 1 0 0 | 即:对应位取反。 异或 **^** | **十进制** | **二进制** | | ----------- | ---------- | | 3 | 0 0 1 1 | | 5 | 0 1 0 1 | | ^ 后结果:6 | 0 1 1 0 | 即:只要对应为不同即为 1。 ### 使用位运算重构项目 当前我们需要设计一个权限模块,可动态的为用户指定某个文件的操作权限。并且,用户对一个文件的操作权限分为:读(R),写(W),执行(X)。 这是一个很简单的需求,为了描述这种关系,我们会在数据库表关系设计时,定义如下的结构: **数据表:`user_file_permission`** | 字段 | 类型 | 备注 | | ---------- | ---- | ---------- | | userId | int | 用户 | | fileId | int | 文件 | | readable | bit | 是否可读 | | writable | bit | 是否可写 | | executable | bit | 是否可执行 | **映射的模型:`UserFilePermission`** ```java public class UserFilePermission { /** * 用户 */ private User user; /** * 文件 */ private File file; /** * 读操作 */ private Boolean readable; /** * 写操作 */ private Boolean writable; /** * 执行操作 */ private Boolean executable; } ``` 这是常见的实现方式。但考虑下,业务需求千变万化,倘若需要再新增一个下载(D) 操作,是不是需要去额外扩展一个字段。所以,对于长期来讲,有值得重构的空间。 故缺点很明显: - 难扩展 - 繁琐,比如判断是否包含读和执行的操作权限,需要这样写`if(xx.IsReadable() && xx.IsExecutable())`,但随着权限操作越来越多时,`if`代码块也越来越大。 **位运算重构** 了解 `Linux` 的同学一定知道利用 `chmod` 来控制文件如何被他人调用。比如针对一个文件,可分别给 User、Group、Other 设置访问的权限。同时权限操作分为:r(读),w(写),x(执行)。很巧,和我们的需求一样。那我们来看下`Linux` 是如何实现权限控制的。 核心是定义一个整数来代表操作权限,即:r=4,w=2,x=1 - 若要 rwx 权限,则:4+2+1=7; - 若要 rw- 权限,则:4+2=6; - 若要 r-x 权限,则:4+1=5。 所以使用 chmod 也可以用数字来表示权限,如下即给 User、Group、Other 三个维度的对象都设置了代表可读、可写、可执行的权限,代号:7。 ```shell chmod 777 file ``` 你可能会想,为什么 r=4,w=2,x=1?聪明的你,肯定想到了——二进制。 | 权限操作 | 二进制 | 十进制 | | -------- | ------ | ------ | | r | 0100 | 4 | | w | 0010 | 2 | | x | 0001 | 1 | 所以借由这个思想,我们对代码进行重构,去掉了`readable`,`writable`,`executable` 这三个字段,而统一由一个 `permissoin` 字段来表示,如下所示: ```java public class UserFilePermission { /** * 可执行(x):0001 */ public static final int OP_EXECUTABLE = 1; /** * 可写(w):左移一位:0010 */ public static final int OP_WRITABLE = 1 << 1; /** * 可读(r):左移二位:0100 */ public static final int OP_READABLE = 1 << 2; /** * 用户 */ private User user; /** * 文件 */ private File file; /** * 权限 */ private int permission; } ``` 其中 `permission` 的可选项如下表格所示: | permission | r | w | x | 描述 | | ---------- | ---- | ---- | ---- | ------------------ | | 1(0001) | 0 | 0 | 1 | 可执行 | | 2(0010) | 0 | 1 | 0 | 可写 | | 4(0100) | 1 | 0 | 0 | 可读 | | 3(0011) | 0 | 1 | 1 | 可写、可执行 | | 7(0111) | 1 | 1 | 1 | 可读、可写、可执行 | | 0(0000) | 0 | 0 | 0 | 禁止 | 同时,操作权限不是一尘不变的,我们往往需要对其新增、删除、查询。通过位运算,可以非常方便实现。 **为当前权限新增一个操作:** ```java public void addOp(int op) { permission |= op; } ``` **为当前权限删除一个操作:** ```java public void removeOp(int op) { permission &= ~op; } ``` **判断当前权限是否包含指定的操作权限:** ```java public boolean containsOp(int op) { return (permission & op) == op; } ``` **判断当前权限是否不包含指定的操作权限:** ```java public boolean notContainsOp(int op) { return (permission & op) == 0; } ``` 当然,这样的重构唯一的缺点就是可读性变差。当然,如果团队对位运算达成共识之后,大家都有一定的了解。相反,可读性还是可以的。同时,位运算的计算非常快,也在一定程度上提升了执行效率。 ### 位运算在 Netty 中的体现 我们可以在诸多优秀的开源代码看到位运算的身影。比如 `JDK` 中有非常多的案例。在此,抛砖引玉,谈谈在 `Netty` 的体现。 `Netty` 的内部提供了 `Skip` 的注解,用来表明一个 `ChannelHandler` 的某个方法不需要被执行,即跳过。我们来看下`Netty` 是如何实现的。 ```java final class ChannelHandlerMask { // Using to mask which methods must be called for a ChannelHandler. static final int MASK_EXCEPTION_CAUGHT = 1; static final int MASK_CHANNEL_REGISTERED = 1 << 1; static final int MASK_CHANNEL_UNREGISTERED = 1 << 2; static final int MASK_CHANNEL_ACTIVE = 1 << 3; static final int MASK_CHANNEL_INACTIVE = 1 << 4; static final int MASK_CHANNEL_READ = 1 << 5; static final int MASK_CHANNEL_READ_COMPLETE = 1 << 6; static final int MASK_USER_EVENT_TRIGGERED = 1 << 7; static final int MASK_CHANNEL_WRITABILITY_CHANGED = 1 << 8; static final int MASK_BIND = 1 << 9; static final int MASK_CONNECT = 1 << 10; static final int MASK_DISCONNECT = 1 << 11; static final int MASK_CLOSE = 1 << 12; static final int MASK_DEREGISTER = 1 << 13; static final int MASK_READ = 1 << 14; static final int MASK_WRITE = 1 << 15; static final int MASK_FLUSH = 1 << 16; private static final int MASK_ALL_INBOUND = MASK_EXCEPTION_CAUGHT | MASK_CHANNEL_REGISTERED | MASK_CHANNEL_UNREGISTERED | MASK_CHANNEL_ACTIVE | MASK_CHANNEL_INACTIVE | MASK_CHANNEL_READ | MASK_CHANNEL_READ_COMPLETE | MASK_USER_EVENT_TRIGGERED | MASK_CHANNEL_WRITABILITY_CHANGED; private static final int MASK_ALL_OUTBOUND = MASK_EXCEPTION_CAUGHT | MASK_BIND | MASK_CONNECT | MASK_DISCONNECT | MASK_CLOSE | MASK_DEREGISTER | MASK_READ | MASK_WRITE | MASK_FLUSH; /** * Calculate the {@code executionMask}. */ private static int mask0(Class<? extends ChannelHandler> handlerType) { int mask = MASK_EXCEPTION_CAUGHT; try { if (ChannelInboundHandler.class.isAssignableFrom(handlerType)) { mask |= MASK_ALL_INBOUND; if (isSkippable(handlerType, "channelRegistered", ChannelHandlerContext.class)) { mask &= ~MASK_CHANNEL_REGISTERED; } if (isSkippable(handlerType, "channelUnregistered", ChannelHandlerContext.class)) { mask &= ~MASK_CHANNEL_UNREGISTERED; } if (isSkippable(handlerType, "channelActive", ChannelHandlerContext.class)) { mask &= ~MASK_CHANNEL_ACTIVE; } if (isSkippable(handlerType, "channelInactive", ChannelHandlerContext.class)) { mask &= ~MASK_CHANNEL_INACTIVE; } if (isSkippable(handlerType, "channelRead", ChannelHandlerContext.class, Object.class)) { mask &= ~MASK_CHANNEL_READ; } if (isSkippable(handlerType, "channelReadComplete", ChannelHandlerContext.class)) { mask &= ~MASK_CHANNEL_READ_COMPLETE; } if (isSkippable(handlerType, "channelWritabilityChanged", ChannelHandlerContext.class)) { mask &= ~MASK_CHANNEL_WRITABILITY_CHANGED; } if (isSkippable(handlerType, "userEventTriggered", ChannelHandlerContext.class, Object.class)) { mask &= ~MASK_USER_EVENT_TRIGGERED; } } ... if (isSkippable(handlerType, "exceptionCaught", ChannelHandlerContext.class, Throwable.class)) { mask &= ~MASK_EXCEPTION_CAUGHT; } } catch (Exception e) { // Should never reach here. PlatformDependent.throwException(e); } return mask; } } ``` 上述代码将主干代码剥离出后,其实核心逻辑很简单: ```java // 添加了所有 mask |= MASK_ALL_INBOUND; // 如果该 Handler 的 xx 方法标注了 @Skip 注解,则将他剔除 if (isSkippable(handlerType, "xx", ChannelHandlerContext.class)) { mask &= ~xx; } ``` 因为 `Netty` 的 `pipeline` 是个职责链,它需要判断当前的 `method`是否被允许执行。使用 `(ctx.executionMask & mask) == 0` 来表示当前是否被禁止调用。如果是的话,则忽略,继续迭代,直到找到允许被调用的 `handler`。 如下所示: ```java private AbstractChannelHandlerContext findContextInbound(int mask) { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.next; } while ((ctx.executionMask & mask) == 0); return ctx; } ``` ### 小结 本文为大家展示了如何使用二进制以及位运算来重构代码。显而易见,代码量及其精简。同时这种思想也大量出现在开源代码中,值得学习。

加载全部内容

相关教程
猜你喜欢
用户评论