位摆弄看起来像魔术,它允许以非常有效的方式来做很多事情。
在这篇文章中,我将分享一些真实的示例,其中可以使用位操作获得良好的性能。

按位操作训练营
位运算符包括。 – AND(&) –或(|) –不(〜) – XOR(^) –班次(<<,>>)
Wikipedia具有Bitwise_operation的高级概述。 在准备这篇文章的同时,我写了学习测试,它可用于learningtest github项目。 学习测试是开始深潜之前探索任何事物的好方法。 我计划稍后再写有关学习测试的详细信息。
在这些示例中,我将使用以下位技巧作为解决更复杂问题的基础。
- countBits –二进制中1位的计数
- bitParity –检查位添加到二进制代码
- 设置/清除/切换 –操作单个位
- pow2 –找到2的下一个幂并将其用作遮罩。
这些功能的代码可在github上的@ Bits.java中获得 ,而单元测试可在@ BitsTest.java上获得
让我们现在来看一些现实世界中的问题。
客户每日主动跟踪
电子商务公司会保留重要的指标,例如客户活跃的天数或从事某些业务。 对于建立可用于改善客户参与度的模型而言,此指标非常重要。 这种类型的指标对于欺诈或与风险相关的用例也很有用。
投资银行也将此类指标用于股票/货币来建立交易模型等。
使用简单的位操作技巧,可以将30天的数据打包为4个字节,因此,要存储全年的信息,只需要48个字节即可。
程式码片段
public class CustomerActivity {
private final int[] months = new int[12];
public void record(LocalDate day) {
int monthOffSet = day.getMonthValue() - 1;
int monthValue = months[monthOffSet];
// Set bit for day in 32 bit int and then OR(|) with month value to merge value
months[monthOffSet] = monthValue | 1 << (day.getDayOfMonth() - 1);
}
public int daysActive(Month month) {
int monthValue = months[month.ordinal()];
return countBits(monthValue);
}
public boolean wasActive(LocalDate day) {
int monthOffSet = day.getMonthValue() - 1;
int monthValue = months[monthOffSet];
// Set bit for day in 32 bit int and then AND(|) with month value to check if bit was set
return (monthValue & 1 << (day.getDayOfMonth() - 1)) > 0;
}
}
除了紧凑的存储,此模式还具有良好的数据局部性,因为处理器可以使用单个加载操作读取全部内容。
传输错误
这是位操作的另一个亮点。
假设您正在构建分布式存储块管理软件或正在构建某种文件传输服务,则该服务所需的事情之一就是确保正确完成传输,并且在传输过程中不会丢失任何数据。 这可以使用位奇偶校验(奇数或偶数)技术来完成,它涉及将“ 1”位数保持为奇数或偶数。
/*
Used for verification for data transferred over network or data saved on disk. Parity bits is used in many hardware for deducting errors.
Caution: This is simple technique and comes with some limitation of deduction of error with odd or even.
Hadoop name nodes performs some checks like this to check data integrity.
*/
public class Transmission {
public static byte transmit(byte data) {
return Bits.oddParity(data); // Add 1 bit to keep odd parity if required.
}
public static boolean verify(byte data) {
return (Bits.countBits(data) & 1) == 1; // Checks if parity is Odd on receiver end.
}
}
进行此类验证的另一种方法是Hamming_distance 。 汉明距离的整数值的代码段。
/*
Using bits count to find distance between 2 integer. Some of application are error deduction while data transfer
*/
public class HammingDistance {
public static int weight(int value) {
return Bits.countBits(value);
}
public static int distance(int value1, int value2) {
return Bits.countBits(value1 ^ value2);
}
public static int distance(String value1, String value2) {
throw new IllegalArgumentException("Not implemented");
}
}
保持数据完整性而无额外开销的非常有用的方法。
锁具
现在开始进行并发。 锁通常不利于性能,但有些时候我们不得不使用它。 许多锁的实现非常繁重,并且也很难在程序之间共享。在此示例中,我们将尝试构建锁,这将是内存有效的锁,可以使用单个Integer管理32个锁。
程式码片段
/*
This is using single Int to manage 32 locks in thread safe way.
This has less memory usage as compared to JDK lock which uses one Int(32 Bytes) to manage single lock.
*/
public class Locks {
public static final int INT_BYTES = 32;
private AtomicInteger lock = new AtomicInteger(0);
public boolean lock(int index) {
int value = lock.get();
if (Bits.isSet(value, index)) {
return false;
}
int newLock = Bits.set(value, index);
return lock.compareAndSet(value, newLock);
}
public boolean release(int index) {
int value = lock.get();
int newLock = Bits.clear(value, index);
return lock.compareAndSet(value, newLock);
}
}
本示例将单个位设置技巧与AtomicInteger结合使用以使该代码具有线程安全性。
这是非常轻巧的锁。 由于此示例与并发相关,因此由于虚假共享而将导致一些问题,可以通过使用“ 多核可伸缩计数器”中提到的一些技术来解决。
容错磁盘
让我们进入一些严肃的东西。 假设我们有2个磁盘,并且我们想保留数据副本,以便在其中一个磁盘发生故障的情况下恢复数据,天真的方法是保留每个磁盘的备份副本,因此,如果您有1 TB,则再增加1个需要TB。 如果您使用这种方法,那么像Amazon这样的云提供商将非常高兴。
只需使用XOR(^)运算符,我们就可以将单个磁盘对的备份保持在单个磁盘上,我们可以获得50%的收益。 节省50%的存储费用。
代码段测试可还原逻辑。
public void restoreDisk() {
RaidDisk disk1 = new RaidDisk(2);
disk1.set(0, MoreInts.toByte("01101101"));
disk1.set(1, MoreInts.toByte("00101101"));
RaidDisk disk2 = new RaidDisk(1);
disk2.set(0, MoreInts.toByte("11010100"));
RaidDisk raidDisk = disk1.xor(disk2); // This xor allow to keep data for both disk in RaidDisk
RaidDisk newDisk1 = raidDisk.xor(disk2); // If we loose disk1 then disk1 can be restore using raidDisk ^ disk2
RaidDisk newDisk2 = raidDisk.xor(disk1);
assertEquals(disk1.toBinary(), newDisk1.toBinary());
assertEquals(disk2.toBinary(), newDisk2.toBinary());
}
可用的磁盘代码@ RaidDisk.java
环形缓冲区
环形缓冲区是进行异步处理时非常流行的数据结构,在写入慢速设备之前先缓冲事件。 环形缓冲区是有界缓冲区,它有助于在关键执行路径中使用零分配缓冲区,非常适合低延迟编程。
常见的操作之一是在缓冲区中查找用于写/读的插槽,这是通过使用Mod(%)运算符完成的,mod或除法运算符的性能不好,因为它会暂停执行,因为CPU仅具有1个或2个端口用于处理除法但是它有许多用于按位操作的端口。
在此示例中,我们将使用按位运算符查找mod,并且只有在mod number为powof2时才有可能。 我认为这是每个人都应该知道的技巧之一。
n&(n-1)
如果n是2的幂,则可以使用'x&(n-1)'在单个指令中查找mod。 这是如此流行,以至于在许多地方都使用了它,JDK hashmap也使用它来查找map中的slot。
public class RingBuffer<T> {
public RingBuffer(int size) {
this.capacity = Bits.powOf2(size);
this.mask = capacity - 1;
buffer = new Object[this.capacity];
}
private int offset(int index) {return index & mask;
//return index % capacity;
}
public boolean write(T value) {
if (buffer[offset(write)] != null)
return false;
buffer[offset(write++)] = value;
return true;
}
public T read() {
if (read == write)
return null;
T value = (T) buffer[offset(read)];
buffer[offset(read++)] = null;
return value;
}
}
结论
我只是在高层次上分享了简单的位操作技术可以实现的功能。
比特操作使解决问题的方法具有许多创新性。 在程序员工具包中拥有额外的工具总是一件好事,许多事情对于每种编程语言都是永恒的。
在后期使用的所有代码都可以@ 位回购。