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

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

前言


提及位运算,相信对绝大多数Java程序员是感觉既陌生又熟悉的。陌生是因为你大概率没有去真实的使用过,熟悉是有时在看些开源框架(或者JDK源码)时会时长看到有使用的地方(譬如Jackson/Fastjson这些JSON库都大量的使用了位运算)。


当然,不能“流行”起来是有原因的:不好理解,不符合人类的思维,阅读性差…位运算它在low-level的语言里使用得比较多,但是对于Java这种高级语言它就很少被提及了。虽然我们使用得很少但Java也是支持的,毕竟很多时候使用位运算才是最佳实践。

位运算在日常开发中使用得较少,但是巧妙的使用位运算可以大量减少运行开销,优化算法:一条语句可能对代码没什么影响,但是在高重复,大数据量的情况下将会节省很多开销


正文


在了解什么是位运算之前,有必要先简单科普下二进制的概念。


二进制

二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”。0、1是基本算符。因为它只使用0、1两个数字符号,非常简单方便,易于用电子方式实现(比如半导体)


比如请计算如下的计算结果(二进制):


求 1011(2进制)+ 11(2进制) 的和?
结果为:1110(二进制数)

了解了什么是二进制后,其实八进制、十进制与十六进制都是差不多的,它们之间区别在于数运算时是逢几进一位,借一当作几


进制转换

关于进制转换这个知识点就老生常谈了,由于现存有非常多的文章讲解它,因为我就无需重复造轮子了,此处我推荐百度经验的一篇文章供以你学习参考:二进制、八进制、十进制、十六进制之间的转换


image.png


二进制与编码


计算机能识别的只有1和0,也就是二进制,而1和0可以表达出全世界的所有文字和语言符号。

那如何表达文字和符号呢?这就涉及到字符编码了。字符编码强行将每一个字符对应一个十进制数字(请注意字符和数字的区别,比如’0’字符对应的十进制数字是48),再将十进制数字转换成计算机理解的二进制,而计算机读到这些1和0之后就会显示出对应的文字或符号。关于编码的进化史,有兴趣的小伙伴可以点击 这里 参考,此处我简要给出几点总结:


  • 一般对英文字符而言,一个字节表示一个字符,但是对汉字而言,由于低位的编码已经被使用(早期计算机并不支持中文,因此为了扩展支持,唯一的办法就是采用更多的字节数)只好向高位扩展。
  • 字符集编码的范围 utf-8>gbk>iso-8859-1(latin1)>ascll。ascll编码是美国标准信息交换码的英文缩写,包含了常用的字符,如阿拉伯数字,英文字母和一些打印符号共255个。
  • unicode编码包含很多种格式,utf-8是其中最常用的一种。utf-8名称的来自于该编码使用8位一个字节表示一个字符。对于一个汉字而言,它需要3个字节表示一个汉字,但大中华地区人民表示不服,搞一套gbk编码格式,用两个字节表示一个汉字。


Java中的二进制

熟悉Java的同学应该知道在Java7之前是不支持前置直接表示二进制数的,但从7版本之后就可以了:


  • 二进制:前置0b/0B
  • 八进制:前置0
  • 十进制:默认的,无需前置
  • 十六进制:前置0x/0X


public static void main(String[] args) {
    //二进制
    int i = 0B101;
    System.out.println(i); //5
    //八进制
    i = 0101;
    System.out.println(i); //65
    //十进制
    i = 101;
    System.out.println(i); //101
    //十六进制
    i = 0x101;
    System.out.println(i); //257
}


说明:1、System.out.println()会先自动转为10进制后再输出的;2、Long类型也是有类似的静态方法API的;3、Byte、Short等类型是木有此API的


Java中便捷的进制转换API


JDK自1.0开始便提供了非常便捷的进制转换的API,这在我们有需要时非常有用。


public static void main(String[] args) {
    int i = 192;
    System.out.println("---------------------------------");
    System.out.println("十进制转二进制:" + Integer.toBinaryString(i)); //11000000
    System.out.println("十进制转八进制:" + Integer.toOctalString(i)); //300
    System.out.println("十进制转十六进制:" + Integer.toHexString(i)); //c0
    System.out.println("---------------------------------");
    // 统一利用的为Integer的valueOf()方法,parseInt方法也是ok的
    System.out.println("二进制转十进制:" + Integer.valueOf("11000000", 2).toString()); //192
    System.out.println("八进制转十进制:" + Integer.valueOf("300", 8).toString()); //192
    System.out.println("十六进制转十进制:" + Integer.valueOf("c0", 16).toString()); //192
    System.out.println("---------------------------------");
}


如何证明Long是64位的?

其实最简单的方式便是:我们看看Long类型的最大值,用2进制表示转换成字符串看看长度就行了


public static void main(String[] args) {
    long l = 100L;
    //如果不是最大值 前面都是0  输出的时候就不会有那么长了(所以下面使用最大/最小值示例)
    System.out.println(Long.toBinaryString(l)); //1100100
    System.out.println(Long.toBinaryString(l).length()); //7
    System.out.println("---------------------------------------");
    l = Long.MAX_VALUE; // 2的63次方 - 1
    //正数长度为63为(首位为符号位,0代表正数,省略了所以长度是63)
    //111111111111111111111111111111111111111111111111111111111111111
    System.out.println(Long.toBinaryString(l));
    System.out.println(Long.toBinaryString(l).length()); //63
    System.out.println("---------------------------------------");
    l = Long.MIN_VALUE; // -2的63次方
    //负数长度为64位(首位为符号位,1代表负数)
    //1000000000000000000000000000000000000000000000000000000000000000
    System.out.println(Long.toBinaryString(l));
    System.out.println(Long.toBinaryString(l).length()); //64
}


提示:1、在计算机中,负数以其正值的补码形式表达,方法为其绝对值求反加1;2、用同样方法可以看出Integer类型是占用32位(4个字节)

Java中的位运算

Java语言支持的位运算符还是非常多的,列出如下:


  • &:按位与。
  • |:按位或。
  • ~:按位非。
  • ^:按位异或。
  • <<:左位移运算符。
  • >>:右位移运算符。
  • >>>:无符号右移运算符。


除~以 外,其余均为二元运算符,操作的数据只能是整型(长短均可)/字符型。


&:按位与


操作规则:仅当两个操作数都为1时,输出结果才为1,否则为0(相同为1,不同为0)


说明:1、本示例(下同)中所有的字面值使用的都是十进制表示的,理解的时候请用二进制思维去理解;2、关于负数之间的位运算本文章统一不做讲述


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

|:按位或


操作规则:仅当两个操作数都为0时,输出的结果才为0。(仅需一个是1便是1


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




相关文章
|
5月前
|
编解码 算法 Java
Java中的位运算详解
Java中的位运算详解
|
2月前
|
IDE Java 编译器
Java“找不到符号” 错误怎么查找解决
“找不到符号”是Java编程中常见的编译错误,通常表明代码试图访问未声明或不可见的符号(如类、方法或变量)。解决此问题需检查拼写、导入包是否正确及作用域是否合适。确保使用正确的类路径和库,可有效避免此类错误。若问题依旧,查阅官方文档或使用调试工具定位错误亦为良策。
1781 10
|
3月前
|
IDE Java 编译器
lombok编译遇到“找不到符号的问题”
【9月更文挑战第18天】当使用 Lombok 遇到 “找不到符号” 的问题时,可能是由于 Lombok 未正确安装、编译器不支持、IDE 配置不当或项目构建工具配置错误。解决方法包括确认 Lombok 安装、编译器支持,配置 IDE 和检查构建工具配置。通过这些步骤通常可解决问题,若问题仍存在,建议检查项目配置和依赖,或查看日志获取更多信息。
968 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中的左移运算符及其在实现加法效果上的应用
|
18天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
9天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
3天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####