聊聊Java中的位运算:与、或、非、异或、左移、右移、无符号右移【小家Java】(中)

简介: 聊聊Java中的位运算:与、或、非、异或、左移、右移、无符号右移【小家Java】(中)

~:按位非


操作规则:全部的0置为1,1置为0。


public static void main(String[] args) {
    // 2 -> 10(其实是00000000000000000000000000000010  共32位)
    // 非后结果:     11111111111111111111111111111101 共32位
    System.out.println(Integer.toBinaryString(~2));
}


可以看到取非的结果像是“面目全非”的赶脚,因此使用时需要谨慎。


^:按位异或


操作规则:操作数不同时(1遇上0,0遇上1)对应的输出结果才为1,否则为0。(相同为0,不同为1


public static void main(String[] args) {
    // 2 -> 10
    // 3 -> 11
    // 异或后结果:01(二进制数)
    System.out.println(Integer.toBinaryString(2 ^ 3));
}


<<:按位左移


操作规则:把一个数的全部位数都向左移动若干位。


public static void main(String[] args) {
    // 2 -> 10
    // 左移3位结果:10000(二进制数)
    System.out.println(Integer.toBinaryString(2 << 3));
}


左移用得非常多,也非常好理解。x左移多少位,效果同十进制里直接乘以2的多少次方就行了,但是需要注意值溢出的情况~


>>:按位右移


操作规则:把一个数的全部位数都向右移动若干位。


public static void main(String[] args) {
    // 2 -> 10
    // 右移3位结果:0(二进制数)
    // 位数不够全被移没了,所以最终打印0
    System.out.println(Integer.toBinaryString(2 >> 3));
    // 100 -> 1100100
    // 右移3位结果:1100
    System.out.println(Integer.toBinaryString(100 >> 3));
}


右移用得也很多,操作其实就是吧右边的N位直接砍掉即可


>>>:无符号右移(注意:没有无符号左移)


注意:并没有<<<这个符号的哟~~~


正数做>>>运算的时候和>>是一样的。区别在于负数运算


复合运算


这里指的复合运算指的就是和=号一起来使用,类似于+= -=等。本来这属于常识不用单独解释,但因有好几个小伙伴问过了,所以在此处顺带的介绍下吧:



public static void main(String[] args) {
    // 2 -> 10
    // 3 -> 11
    // 与后结果:10(二进制数)
    int i = 2;
    i &= 3; // 此效果同 i = i & 3
    System.out.println(Integer.toBinaryString(i)); //打印:10
}


位运算的使用场景


位运算不仅有高效的特点,还有一个非常非常的大特点:计算的可逆性。通过这个特点我们可以用来达到隐蔽数据的效果(后面有示例),并且还保证了效率。


在JDK的原码中。有很多初始值都是通过位运算计算的。位运算有很多优良特性,能够在线性增长的数据中起到作用。且对于一些运算,位运算是最直接、最简便的方法。


判断一个数的奇偶性

在十进制数中可以通过和2取余来可以达到效果,对于位运算有一个更为高效的方式:


public static void main(String[] args) {
    System.out.println(isEvenNum(1)); //false
    System.out.println(isEvenNum(2)); //true
    System.out.println(isEvenNum(3)); //false
    System.out.println(isEvenNum(4)); //true
    System.out.println(isEvenNum(5)); //false
}
private static boolean isEvenNum(int n) {
    // 1 -> 1(二进制表示。所以它的前31位都是0哦~~~)
    return (n & 1) != 1;
}


为何&1能判断基偶性?因为在二进制下偶数的末位肯定是0,so奇数的最低位肯定是1。

而二进制的1它的前31位均为0,所以在和其它数字的前31位与运算后肯定所有位数都是0(无论是1&0还是0&0结果都是0),那么唯一区别就是看最低位和1进行与运算的结果喽:结果为1表示奇数,反则结果为0表示偶数


不借助第三方变量方式交换两个数的值


这是个经典面试题,题目本来很简单,但是加上了不借助第三方变量这个条件后就会难倒一大片了。其实它会有两种方案,这里我都展示出来:


方式一:传统方式


public static void main(String[] args) {
    int a = 3, b = 5;
    System.out.println(a + "-------" + b);
    a = a + b;
    b = a - b;
    a = a - b;
    System.out.println(a + "-------" + b);
}


使用这种方式的好处是容易理解,坏处是:a+b,可能会超出int型的最大范围,造成精度丢失导致错误,所以生产环境强烈建议采用下面的方式二。


方式二:位运算方式

public static void main(String[] args) {
    // 这里使用最大值演示,以证明这样方式是不会溢出的
    int a = Integer.MAX_VALUE, b = Integer.MAX_VALUE - 10;
    System.out.println(a + "-------" + b); // 2147483647-------2147483637
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    System.out.println(a + "-------" + b); // 2147483637-------2147483647
}


它的根本原理就是利用了位运算的可逆性,使用异或运算来操作。


移位运算用在数据库字段上


业务系统中数据库设计的尴尬现象:通常 我们的数据表中 可能会包含各种状态属性, 例如 blog表中,我们需要有字段表示其是否公开,是否有设置密码,是否被管理员封锁,是否被置顶等等。 也会遇到在后期运维中,策划要求增加新的功能而造成你需要增加新的字段,这样会造成后期的维护困难,数据库增大,索引增大的情况, 这时使用位运算就可以巧妙的解决。


说明:1、MySql是支持这些位运算符的;2、这种方式不一定适合所有场景,因为它会导致索引失效(不过状态值一般也不需要索引),所以具体问题需具体分析


其实移位运算玩法比较像Linux里的权限控制:权限分为 r 读, w 写, x 执行,其中 它们的权值分别为4,2,1, 所以 如果用户要想拥有这三个权限 就必须 chomd 7 , 即 7=4+2+1 表明 这个用户具有rwx权限,如果只想这个用户具有r,x权限 那么就 chomd 5即可。


流水号生成器(订单号生成器)


生成订单流水号,当然这其实这并不是一个很难的功能,最直接的方式就是日期+主机Id+随机字符串来拼接一个流水号,但是今天有个我认为比较优雅方式来实现。什么叫优雅:可以参考淘宝、京东的订单号,看似有规律,其实没规律:


  • 不想把相关信息直接暴露出去。
  • 通过流水号可以快速得到相关业务信息,快速定位问题(这点非常重要)。
  • 使用AtomicInteger可提高并发量,降低了冲突




相关文章
|
5月前
|
编解码 算法 Java
Java中的位运算详解
Java中的位运算详解
|
2月前
|
IDE Java 编译器
Java“找不到符号” 错误怎么查找解决
“找不到符号”是Java编程中常见的编译错误,通常表明代码试图访问未声明或不可见的符号(如类、方法或变量)。解决此问题需检查拼写、导入包是否正确及作用域是否合适。确保使用正确的类路径和库,可有效避免此类错误。若问题依旧,查阅官方文档或使用调试工具定位错误亦为良策。
2340 10
|
3月前
|
IDE Java 编译器
lombok编译遇到“找不到符号的问题”
【9月更文挑战第18天】当使用 Lombok 遇到 “找不到符号” 的问题时,可能是由于 Lombok 未正确安装、编译器不支持、IDE 配置不当或项目构建工具配置错误。解决方法包括确认 Lombok 安装、编译器支持,配置 IDE 和检查构建工具配置。通过这些步骤通常可解决问题,若问题仍存在,建议检查项目配置和依赖,或查看日志获取更多信息。
1513 2
|
4月前
|
Java
java:找不到符号
这篇文章讨论了Java编程中常见的错误信息 "找不到符号:类 entity",并提供了解决这个问题的一些方法和建议。
|
4月前
|
Java
成功解决:java: 找不到符号 符号: 方法 getSort() 位置: 类型为com.atguigu.gulimall.product.entity.CategoryEntity的变量 menu1
这篇文章讨论了Java中遇到的一个常见错误:"java: 找不到符号 符号: 方法 getSort() 位置: 类型为com.atguigu.gulimall.product.entity.CategoryEntity的变量 menu1",即在尝试调用一个不存在的方法时出现的问题,并提供了相应的解决方法。
|
6月前
|
Java
JAVA工具类匹配重复或者连续的字符和符号
JAVA工具类匹配重复或者连续的字符和符号
|
6月前
|
Java
Java中的左移运算符及其在实现加法效果上的应用
Java中的左移运算符及其在实现加法效果上的应用
|
1天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
3天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
3天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。