“高端”的位运算

封面:“高端”的位运算.png

王有志,一个分享硬核Java技术的互金摸鱼侠
加入Java人的提桶跑路群:共同富裕的Java人

原计划迭代作为预备知识的收尾,不过在解2的幂和4的幂时,想到关于数字2的问题可以通过位运算去解决,因此补充了关于位运算的内容。

“征服”面试官

当我还在校园的时候,听到过一个故事:某位学长去面试腾讯时,要求优化冒泡排序,学长“苦思冥想”后使用位运算交换变量,成功“征服”面试官拿下Offer。
故事我们可以当做段子来看,不过提到的位运算交换变量却值得我们去探究。先来看下“普通”程序员是如何交换变量的:

int a = 3, b = 5;
int temp = a;
a = b;
b = temp;

那么“高端”程序员是如何使用位运算交换变量的呢?

int a = 3, b = 5;
a = a ^ b;
b = a ^ b;
a = a ^ b;

同样是4行代码,使用位运算无需引入临时变量,因此在空间复杂度上更优,并且位运算更靠近计算机,因此在运算效率上更有优势。

位运算

计算机中,数以二进制的形式存储在内存中,而位运算就是对内存中的二进制数进行操作。维基百科中是这样定义的:

位操作是程序设计中对位数组或二进制数的一元和二元操作。在许多古老的微处理器上,位运算比加减运算略快,通常位运算比乘除法运算要快很多。在现代架构中,位运算的运算速度通常与加法运算相同(仍然快于乘法运算),但是通常功耗较小,因为资源使用减少。

既然位运算是操作二进制数的,那么我们有必要先了解计算机中二进制数是如何表示的。

码,反码和补码

计算机中有3种有符号数的表示方法:原码,反码和补码。
原码,反码和补码的共同点是:最高位为符号位,0代表正数,1代表负数。但它们在数值位上有着不同的表示方法。
我们通过十进制数字-11,来展示这3种表示方法的二进制数(使用8位)。

原码表示

使用原码表示时,除了符号位外,数值位和我们通过“除二倒取余法”计算后的数字相同,-11的原码为:1000 1011。
图1:原码表示.png

反码表示

反码是原码和补码之间转换的过渡码,原码转换为反码的规则为:

  • 正数时,反码和原码相同
  • 负数时,符号位保持不变,数值位时原码按位取反

因此,-11的反码表示为:1111 0100。

补码表示

通过反码,我们可以计算出数字的补码,转换规则为:

  • 正数时,补码和反码相同;
  • 负数时,符号位保持不变,数值位时反码数值位加1。

因此,-11的补码表示为:1111 0101。
补码是计算机系统中普遍使用的表示方式,补码相较于原码和反码有两个特点:

  • 符号位可以参与运算
  • 加法和减法可以统一处理

到此为止,我们已经了解了计算机中3种二进制数的表示方式,下面简单的做个总结:当数字为正数时,原码,反码和补码相同
当数字为负数时,原码,反码和补码遵循以下转换规则:
图2:原码,反码和补码的转换.png

位运算符

Java中提供了7种位运算操作符:

符号描述运算规则优先级示例
~取反操作一个数,0变为1,1变为01~ a
<<带符号左移操作两个数,数值位向左移动,高位丢弃,低位补02a << 1
>>带符号右移操作两个数,数值位向右移动,低位丢弃,高位补符号位2a >> 1
>>>无符号右移操作两个数,数值位向右移动,低位丢弃,高位补02a >>> 1
&按位与操作两个数,同为1时,结果为1,否则为03a & b
^按位异或操作两个数,相同为0,不同为14a ^ b
|按位或操作两个数,同为0时,结果为0,否则为15

用一段程序来展示位运算符的基础操作:

int number = -11;

// 输出Java中的二进制表示(补码),11111111111111111111111111110101
System.out.println("原值,二进制:" + Integer.toBinaryString(number));

// 取反,0变为1,1变为0
System.out.println("取反,十进制" + ~number);
System.out.println("取反,二进制:" + Integer.toBinaryString(~number));

// 按位异或,相同为0,不同为1
System.out.println("按位异或" + (number ^ number));

// 按位与,同为1时,结果为1,否则为0
System.out.println("按位与:" + (number & 1));

// 按位或,同为0时,结果为0,否则为1
System.out.println("按位或:" + (number | ~number));

// 左移,数值位向左移动,高位丢弃,低位补0
System.out.println("左移,十进制:" + (number << 1));
System.out.println("左移,二进制:" + Integer.toBinaryString(number << 1));

// 右移,数值位向右移动,低位丢弃,高位补符号位
System.out.println("右移,十进制:" + (number >> 2));
System.out.println("右移,二进制:" + Integer.toBinaryString(number >> 2));

// 无符号右移,数值位向右移动,低位丢弃,高位补0
System.out.println("无符号右移,十进制:" + (number >>> 1));
System.out.println("无符号右移,二进制:" + Integer.toBinaryString(number >>> 1));

位运算技巧

到目前为止,我们已经了解了二进制数和位运算符,不过这些操作看起平平无奇,好像并没有什么用?那么接下来就介绍一些基础的位运算技巧。

2的幂

还记得2的幂吗?当时使用递归求解,效率上还是有些差强人意的,那么有没有更高效的方法呢?
有一个二进制数字:1101 0101。根据常规的二进制转换为十进制的方法,可以得到如下等式: 1 × 2 7 + 1 × 2 6 + 0 × 2 5 + 1 × 2 4 + 0 × 2 3 + 1 × 2 2 + 0 × 2 1 + 1 × 2 0 = 213 1\times2^7+1\times2^6+0\times2^5+1\times2^4+0\times2^3+1\times2^2+0\times2^1+1\times2^0=213 1×27+1×26+0×25+1×24+0×23+1×22+0×21+1×20=213
显而易见,如果是2的幂,二进制数字中有且仅有1个1。例如:1000 0000。那么判断是否为2的幂就变成了证明二进制数字仅有1个1,或者说是,证明二进制数字中1后面所有位都为0。
如果数字n是2的幂,那么n-1一定是比n少一位,且二进制位都为1的数字。可以利用0&1=0的特性判断是否符合我们的预期,代码如下:

if((n & (n - 1)) == 0) {
	return true;
} else {
	return false;
}

奇偶性

此外,在二进制转换为十进制的过程中,还可以得到另外一点信息,即如果二进制的最低位为1,则数字为奇数
那么在判断一个数字的奇偶性时,我们只需要得知二进制数字的最后一位是否为1即可,代码如下:

if ((number & 1) == 0) {
	System.out.println("偶数");
} else {
	System.out.println("奇数");
}

快速乘/除2

还是在二进制转换为十进制的过程中,我们可以看到,如果想要乘/除2,只需要向左/右移动一位即可,不过对于奇数来说是除2后向下取整
今天介绍的只是位运算中的基础技巧,算是为大家抛砖引玉。实际上位运算的技巧远不止这些,或者说是二进制数的使用技巧远不止这些。
离我们最近的有Java中ThreadPoolExecutor处理线程状态时使用的技巧,或者叫做位掩码。另外,相信有的小伙伴在面试中被问到过布隆过滤器,这也是一种二进制的进阶用法。
更多的技巧,也可以参考位运算的简单应用和位运算的进阶介绍。

结语

今天的内容到这里就结束了,我们来回顾下都聊了哪些内容:
首先是简单介绍了计算机中的原码,反码和补码,接着是Java中7种位运算操作符,不过并不是所有语言都提供了无符号右移(>>>),最后介绍了一些简单位运算的技巧,但位运算的用法远不止这些,包括听起来很高端的布隆过滤器,也使用了位运算,这也是为什么我说位运算“高端”。
最后补充一篇关于为什么要使用位运算的问答《What are the advantages of using bitwise operations?》,虽然已经过去了11年,但依旧可以作为参考。

练习

因为后面很少会再出现位运算的内容了,因此这次的题目会比较多。
简单难度:

  • 67.二进制求和
  • 136.只出现一次的数字
  • 190.颠倒二进制位
  • 191.位1的个数
  • 231.2的幂
  • 342.4的幂
  • 693.交替位二进制数
  • 剑指 Offer 65.不用加减乘除做加法

中等难度:

  • 36.有效的数独
  • 89.格雷编码
  • 137.只出现一次的数字 II
  • 201.数字范围按位与

如果本文对你有帮助的话,还请多多点赞支持。如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核Java技术的金融摸鱼侠王有志,我们下次再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/306179.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于ssm的生鲜在线销售系统的设计与实现论文

摘 要 使用旧方法对生鲜在线销售系统的信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在生鲜在线销售系统的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。这次开发的生…

Python(33):数据断言(查询数据库数据和插入数据对比)

Python(33):数据断言(查询数据库数据和插入数据对比) 前言&#xff1a; 需求&#xff1a;需要针对查询数据库数据和插入的数据进行对比&#xff0c;用Python语言进行编写 数据库查询的结果可参考&#xff1a;https://blog.csdn.net/fen_fen/article/details/135462484 1、查…

共享文件访问权限被拒绝

winr 打开命令行输入gpedit.msc打开组编辑窗口 这样操作之后就远程电脑一般就可以访问共享文件夹了

STM32CubeMX配置STM32G031多通道UART+DMA收发数据(HAL库开发)

时钟配置HSI主频配置64M 配置好串口&#xff0c;选择异步模式 配置DMA TX,RX,选择循环模式。 NVIC中勾选使能中断 勾选生成独立的.c和h文件 配置好需要的开发环境并获取代码 串口重定向勾选Use Micro LIB main.c文件修改 增加头文件和串口重定向 #include <string.h&g…

纯血鸿蒙「扩圈」100天,酝酿已久的突围

坦白讲&#xff0c;去年参加华为开发者大会看到HarmonyOS NEXT&#xff08;仅运行鸿蒙原生应用&#xff0c;所以也称作「纯血鸿蒙」&#xff09;的时候&#xff0c;小雷也没料想到鸿蒙原生应用生态的发展速度会如此之快。 9月25日&#xff0c;华为正式对外宣布启动HarmonyOS NE…

LabVIEW在微生物检测中的应用

随着对食品安全关注的增加&#xff0c;食品检测的准确性变得越来越重要。其中&#xff0c;微生物计数作为食品合格的关键指标&#xff0c;对其检测技术的准确性和实时性要求极高。传统的微生物检测面临着菌落识别困难、设备实时性差和自动化程度不高等问题&#xff0c;尤其在疫…

深入了解鸿鹄电子招投标系统:Java版企业电子招标采购系统的核心功能

随着市场竞争的加剧和企业规模的扩大&#xff0c;招采管理逐渐成为企业核心竞争力的重要组成部分。为了提高招采工作的效率和质量&#xff0c;我们提出了一种基于电子化平台的解决方案。该方案旨在通过电子化招投标&#xff0c;使得招标采购的质量更高、速度更快&#xff0c;同…

【HarmonyOS4.0】第三篇-类web开发模式

【HarmonyOS4.0】第三篇-类web开发模式 一、鸿蒙介绍 课程核心 为什么我们需要学习鸿蒙&#xff1f; 哪些人适合直接转鸿蒙&#xff1f; 鸿蒙系统优势是什么&#xff1f; 课程内容 (1)为什么要学习鸿蒙 从行情出发&#xff1a; 美国商务部长访问中国&#xff0c;2023年…

uniapp实现清除缓存

一、页面加载时计算缓存大小&#xff08;H5不支持&#xff09; data() {return {// 缓存大小展示到页面上fileSizeString: 0KB} }// 获取缓存大小formatSize() {let that this;// #ifndef H5plus.cache.calculate(function(size) {let sizeCache parseInt(size);if (sizeCac…

linux 使用log4cpp记录项目日志

为什么要用log4cpp记录项目日志 在通常情况下&#xff0c;Linux/UNIX 每个程序在开始运行的时刻&#xff0c;都会打开 3 个已经打开的 stream. 分别用来输入&#xff0c;输出&#xff0c;打印错误信息。通常他们会被连接到用户终端。这 3 个句柄的类型为指向 FILE 的指针。可以…

Spring循环引用和三级缓存

前言 Spring 解决 Bean 之间的循环引用关系用到了三级缓存&#xff0c;那么问题来了。三级缓存是怎么用的&#xff1f;每一层的作用是什么&#xff1f;非得用三级吗&#xff1f;两级缓存行不行&#xff1f; 理解循环引用 所谓的“循环引用”是指 Bean 之间的依赖关系形成了一…

【算法刷题】Day28

文章目录 1. 买卖股票的最佳时机 III题干&#xff1a;算法原理&#xff1a;1. 状态表示&#xff1a;2. 状态转移方程3. 初始化4. 填表顺序5. 返回值 代码&#xff1a; 2. Z 字形变换题干&#xff1a;算法原理&#xff1a;1. 模拟2. 找规律 代码&#xff1a; 1. 买卖股票的最佳时…

PostGIS学习教程二十:3-D

PostGIS学习教程二十&#xff1a;3-D 注意&#xff1a;本文介绍许多PostGIS2.0及更高版本才支持的功能。 文章目录 PostGIS学习教程二十&#xff1a;3-D一、3-D几何图形二、3-D函数三、N-D索引 一、3-D几何图形 到目前为止&#xff0c;我们一直在处理2-D几何图形&#xff08;…

firewalld防火墙命令行工具

firewall-cmd命令 &#xff08;1&#xff09;启动、停止、查看firewalld服务 在安装CentOS 7系统时&#xff0c;会自动安装firewalld 和图形化工具firewall-config.执行以下命令可 以启动 firewalld 并设置为开机自启动状态。 [rootllcgc ~]# systemctl start firewalld.serv…

【SpringCloud】之入门级及nacos的集成使用

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是君易--鑨&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《SpringCloud开发之入门级及nacos》。&#x1f3…

数据库内核那些事|细说PolarDB优化器查询变换:IN-List变换

导读 数据库的查询优化器是整个系统的"大脑"&#xff0c;一条SQL语句执行是否高效在不同的优化决策下可能会产生几个数量级的性能差异&#xff0c;因此优化器也是数据库系统中最为核心的组件和竞争力之一。阿里云瑶池旗下的云原生数据库PolarDB MySQL版作为领先的云…

36-javascript输出方式,弹框:普通,confirm弹框,prompt弹框,控制台输出:普通,warm,error

1.页面打印 <body><p>你真是一个小机灵鬼</p><script>// 页面打印document.write("打印内容");</script> </body> 2.覆盖文档 <body><p>你真是一个小机灵鬼</p><script>// 覆盖文档window.onload f…

模型容器与AlexNet构建

一、模型容器——Containers nn.Sequential 是 nn.module的容器&#xff0c;用于按顺序包装一组网络层 Sequential 容器 nn.Sequential 是 nn.module的容器&#xff0c;用于按顺序包装一组网络层 • 顺序性&#xff1a;各网络层之间严格按照顺序构建 • 自带forward()&#xf…

HACKTHEBOX通关笔记——Poison(退役)

调试网络连通性 拿到IP我们还是做一下nmap扫描&#xff0c;快速速率扫描结合-A详细扫描&#xff0c;事半功倍 nmap --rate-min 5000 -p- 10.129.58.204 -vnmap -A -p 22,80 10.129.58.204 -v 发现http是一个可以读取文件的页面 这台主机似乎没办法做目录扫描&#xff0c;一扫…

电脑找不到ffmpeg.dll的解决方法有哪些,分享5种可靠的方法

在计算机编程和多媒体处理领域&#xff0c;ffmpeg.dll是一个非常重要的动态链接库文件。它是由FFmpeg项目开发和维护的&#xff0c;FFmpeg是一个开源的音视频处理框架&#xff0c;提供了一套完整的音视频编解码、转码、流化、滤镜等功能。ffmpeg.dll是FFmpeg库的一部分&#xf…