Java——位运算符

news2024/9/22 9:55:52

Java——位运算符

  • 起因
  • 位运算符
    • 1.Java中`^ =`运算符的目的
    • 2.Java中`& 0xff`运算符的目的
    • 3.Java中`<< 8`运算符的目的

起因

写这篇文章的起因是在某个地方需要将字节数组byte[]转16进制数字int。见上一篇文章: 进制转换的一些内容,我写出来的方法长这样。

byte[] bytes = new byte[]{35,35};
//方法1:10进制byte转16进制字符串,16进制字符串转16进制数字int
public int bytesToInt(byte[] bytes) {
    StringBuilder stringBuilder = new StringBuilder("");
    for (int i = 0; i < bytes.length; i++) {
        int b = bytes[i] & 0xff;
        String str = Integer.toHexString(b);
        if (str.length() < 2) {
            stringBuilder.append(0);
        }
        stringBuilder.append(str);
    }
    return Integer.parseInt(stringBuilder.toString(),16);
}

当然,功能是实现了,但是直到我看见大佬写的方法,长这样:

byte[] bytes = new byte[]{35,35};
//方法2:位运算直接转(数组长度必须小于等于4)
public int bytesToInt(byte[] bytes) {
    ByteBuffer buffer = ByteBuffer.wrap(bytes);
    int value = 0;
    for (int i = 0; i < bytes.length; i++) {
        value = (value << 8) | (buffer.get() & 0xFF);
    }
    return value;
}

大佬的方法如此简洁,但运行之后能产生同样的效果。对位运算并不熟悉的我看得有点懵逼,这就忍不住去复习了一遍位运算的内容:

位运算符

符号描述运算规则
&两个位都为1时,结果才为1
|两个位都为0时,结果才为0
^异或两个位相同为0,相异为1
~取反0变1,1变0
<<左移各二进位全部左移若干位,高位丢弃,低位补0
>>右移各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)

重点看大佬代码中用到的<< 8& 0xFF分别是什么意思,为什么简单两个运算符就能达成我代码中又是创建变量又是计算好几行的效果。

1.Java中^ =运算符的目的

用于删除byte数组中的重复项

例如:一个byte数组[1, 1, 2, 2, 3, 3, 4]

进行遍历^ =运算,结果是4,也就是把前面两两相等的数字都清除掉了,留下没有可以配对两两抵消的数字4。

byte[] bytes = new byte[]{1, 1, 2, 2, 3, 3, 4};
byte checkCode = 0x00;
for (byte bt : bytes) {
    checkCode ^= bt;
}
System.out.println(checkCode);//4

2.Java中& 0xff运算符的目的

&:表示按位与,只有两个位同时为1,才能得到1,例如:

//0b开头用来表示这是一个2进制数字
int a = 0b0000 0000 1111 1111 0000 0000 1000 0001;
int b = 0b0000 1111 0000 1111 0000 0000 1000 0001;
//上下相与,同时为1才得1,否则就为0
a & b = 0b0000 0000 0000 1111 0000 0000 1000 0001;

0xff转为2进制是:0000 0000 0000 0000 0000 0000 1111 1111

所以:& 0xff是一种二进制的位运算,表示只取后8位的数字。

当一个byte会转换成int时,由于int是32位,而byte只有8位这时会按照符号进行补位,例如:

byte b = -127;//1000 0001
int a =  b;//1111 1111 1111 1111 1111 1111 1000 0001
// 0xff:0000 0000 0000 0000 0000 0000 1111 1111,上下相与,只保留后8位,也就是1000 0001,前面都置0
System.out.println(a);//-127
a =  b&0xff;//0000 0000 0000 0000 0000 0000 1000 0001
System.out.println(a);//129

可以看到:byte会自动补位,把前24位都补为1。

所以:当byte转为int类型数据的时候,就需要&0xff计算,只保留后8位数字,前面24位都置0

3.Java中<< 8运算符的目的

<<:将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。

在上面的byte[]数组转String的例子中:假如我们有一个byte数组[35, 36, 37, 38, 39, 40]。现在需要将这四位转成16进制的int(或者long)并且拼接到一起。例如[35,36]转换之后是[23,24],那么需要输出的结果是2324。

@Test
void demo11() {
    byte[] bytes = new byte[]{35, 36, 37, 38, 39, 40};
    ByteBuffer buffer = ByteBuffer.wrap(bytes);
    int value = 0;
    for (int i = 0; i < bytes.length; i++) {
        value = (value << 8) | (buffer.get() & 0xFF);
        log.info("第 {} 次计算", i);
        log.info("10进制value {}", value);
        log.info("16进制value {}", Long.toHexString(value));
    }
}

@Test
void demo12(){
    byte[] bytes = new byte[]{35, 36, 37, 38, 39, 40};
    StringBuilder stringBuilder = new StringBuilder("");
    for (int i = 0; i < bytes.length; i++) {
        int b = bytes[i] & 0xff;
        String str = Integer.toHexString(b);
        if (str.length() < 2) {
            stringBuilder.append(0);
        }
        stringBuilder.append(str);
        log.info("第 {} 次计算", i);
        log.info("10进制value {}", Long.parseLong(stringBuilder.toString(),16));
        log.info("16进制value {}", stringBuilder);

    }
}

在上面的例子demo11中,就利用了位运算来完成这一工作,避免了创建字符串对象并反复追加字符串的工作。

在这里,每次取得的byte都执行<<8 | 下一个byte的低8位,换成二进制语言来解释,如下表所示:

原始int value=0;

原始byte数组:

byte(10进制)353637383940
转换成2进制0010 00110010 01000010 01010010 01100010 01110010 1000
转换成16进制232425262728

2进制计算过程:

计算过程计算结果value十进制十六进制
value = (value << 8) | (buffer.get() & 0xFF);
即:value左移8位,与 byte的低8位进行或运算
第1次执行0 | 0010 00110010 00113523
第2次执行0010 0011 0000 0000 | 0010 01000010 0011 0010 010089962324
第3次执行0010 0011 0010 0100 0000 0000 | 0010 01010010 0011 0010 0100 0010 01012303013232425
第4次执行0010 0011 0010 0100 0010 0101 0000 0000 | 0010 01100010 0011 0010 0100 0010 0101 0010 011058957136623242526
第5次执行0010 0100 0010 0101 0010 0110 0000 0000 | 0010 01110010 0100 0010 0101 0010 0110 0010 011160641437524252627
第6次执行0010 0101 0010 0110 0010 0111 0000 0000 | 0010 10000010 0101 0010 0110 0010 0111 0010 100062325738425262728

可以看到结果与通过String变量每次获取一个byte的16进制之后再拼接起来的结果一致,但存在一个问题:

  • Java中的int类型存储的二进制数字上限是32位,也就是左移8位这个操作只能执行3次,第四次执行的话就会覆盖第一次取得的8位2进制数字。

所以采用位运算来将byte转换成10进制的int,局限性在于只能转换length<=4的byte[]数组,length一旦超过4,就只能取到byte[]数组最后的4位转换之后的结果。

如果把value的类型改成long,long类型最多可以存储64位的数字,那么位运算的大小限制就扩大了一倍,也就是,左移8位这个操作可以执行7次,即,length的大小最多为8。

而字符串拼接法在这一点上,只需要引入BigInteger,转换对于数据的长度是没有限制的。

综上所述:

在byte[]数组转10进制数字的时候,有两种方法:分别是位运算转换法字符串拼接转换法

  1. 位运算转换:性能更优,但对于数组长度有限制,数组长度必须<=8,如果长度超过8,就只转换最后8位。
  2. 字符串拼接转换:性能稍劣,但对于数组长度无限制。

因此在需要转换的byte[]数组长度小于等于8的情况下,用位运算更佳;长度大于8,则必须用字符串拼接法转换。

//样例数据
private static byte[] bytes8 = new byte[]{18,52,86,120,-112,18,52,86};//8位
private static byte[] bytes15 = new byte[]{18,52,86,120,-112,18,52,86,120,-112,18,52,86,120,-112};//15位

/**
 * 位运算转换法
 */
@Test
void demo1() {
    ByteBuffer buffer = ByteBuffer.wrap(bytes8);
    long value = 0;
    for (int i = 0; i < bytes.length; i++) {
        value = (value << 8) | (buffer.get() & 0xFF);
        log.info("第 {} 次计算", i);
        log.info("10进制value {}", value);
        log.info("16进制value {}", Long.toHexString(value));
    }
}

/**
 * 字符串拼接转换法
 */
@Test
void demo2() {
    StringBuilder stringBuilder = new StringBuilder("");
    for (int i = 0; i < bytes8.length; i++) {
        int b = bytes[i] & 0xff;
        String str = Integer.toHexString(b);
        if (str.length() < 2) {
            stringBuilder.append(0);
        }
        stringBuilder.append(str);
        log.info("第 {} 次计算", i);
        BigInteger bigInteger = new BigInteger(stringBuilder.toString(), 16);
        log.info("10进制value {}", bigInteger.toString(10));
        log.info("16进制value {}", stringBuilder);
    }
}

  • bytes8运行结果:位运算转换法字符串拼接转换法两种方法运行结果一致

在这里插入图片描述

  • bytes15运行结果:

位运算:

在这里插入图片描述

字符串拼接:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2. RNN 情感评论鉴定

目录1. 加载购物评论数据集2. 构建 RNN 神经网络&#xff08;DNN、CNN、RNN、GNN&#xff09;3. 多循环神经网络原理分析4. LSTM 原理剖析5. LSTM 与 Bi LSTM1. 加载购物评论数据集 file --> setting --> plugins --> Installed --> 搜索【csv Plugin】即可。 …

【前端】浏览器的渲染流程(完整)

本文主要包含以下内容&#xff1a;浏览器渲染整体流程解析 HTML样式计算布局分层生成绘制指令分块光栅化绘制常见面试题浏览器渲染整体流程浏览器&#xff0c;作为用户浏览网页最基本的一个入口&#xff0c;我们似乎认为在地址栏输入 URL 后网页自动就出来了。殊不知在用户输入…

RocketMQ之(一)RocketMQ入门

一、RocketMQ入门一、RocketMQ 介绍1.1 RocketMQ 是什么&#xff1f;1.2 RocketMQ 应用场景01、应用解耦02、流量削峰03、数据分发1.3 RocketMQ 核心组成01、NameServer02、Broker03、Producer04、Consumer1.6 运转流程1.5 RocketMQ 架构01、NameServer 集群02、Broker 集群03、…

NetApp Cloud Volumes ONTAP 将数据复制到云或从云中复制

NetApp Cloud Volumes ONTAP 将数据复制到云或从云中复制&#xff0c;为开发运营和基于云的灾难恢复提供支持。 无论应用位于何处&#xff0c;都可以使用企业级存储,让云存储基础架构更经济、更智能、更合规且更安全。 为什么选择 NetApp Cloud Volumes ONTAP NetApp Cloud …

RocketMQ 第二章

RocketMQ 第二章 7、SpringBoot整合RocketMQ SpringBoot 提供了快捷操作 RocketMQ 的 RocketMQTemplate 对象。 7.1、引入依赖 注意依赖的版本需要和 RocketMQ 的版本相同。 <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rock…

本地部署element-plus文档

由于一直使用的前端组件element-plus&#xff0c;所以需要经常看文档&#xff0c;但无奈官网实在不给力&#xff0c;经常报503或者404&#xff0c;大大影响效率和心情&#xff0c;忍无可忍就本地化部署一套解决此问题。 百度了一下大多数都是使用 vscode的live server, 或者放…

JAVA保姆式JDBC数据库免费教程之02-连接池技术

连接池 连接池概念 ​ 概念&#xff1a;其实就是一个容器(集合)&#xff0c;存放数据库连接的容器。 当系统初始化好后&#xff0c;容器被创建&#xff0c;容器中会申请一些连接对象&#xff0c;当用户来访问数据库时&#xff0c;从容器中获取连接对象&#xff0c;用户访问完…

【MySQL】MySQL 架构

一、MySQL 架构 C/S 架构&#xff0c;即客户端/服务器架构。服务器程序直接和我们存储的数据打交道&#xff0c;多个客户端连接这个服务器程序。客户端发送请求&#xff0c;服务器响应请求。 MySQL 数据库实例 &#xff1a;即 MySQL 服务器的进程 &#xff08;我们使用任务管理…

Vue组件间通信的四种方式(函数回调,自定义事件,事件总线,消息订阅与发布)

目录 概述 props配置项-回调函数实现 自定义事件实现 事件总线实现 消息订阅与发布实现 概述 在组件化编程中&#xff0c;组件间的通信是重要的&#xff0c;我们可以有四种方式实现组件间的通信。 分别是&#xff1a;函数回调&#xff0c;自定义事件&#xff0c;事件总…

可调恒流驱动LED电路分析

https://www.icxbk.com/article/detail?aid884 常规使用的pwm调亮度不仅会导致频闪&#xff0c;而且在长时间使用的时候&#xff0c;有损坏led的风险&#xff0c;所以这次设计了一个恒流调亮度电路&#xff0c;其电路图如下所示 电路原理的解读&#xff1a; 左侧的电位计起着…

【JavaScript】js实现深拷贝的方法

前言 在js中我们想要实现深拷贝&#xff0c;首先要了解深浅拷贝的区别。 浅拷贝&#xff1a;只是拷贝数据的内存地址&#xff0c;而不是在内存中重新创建一个一模一样的对象&#xff08;数组&#xff09; 深拷贝&#xff1a;在内存中开辟一个新的存储空间&#xff0c;完完全全…

Java语言常用哪些运算符?

之前有个大家讨论过java的数据类型&#xff0c;总体来说类型和其他几种语言也相差无几&#xff0c;我为什么会这样说&#xff1f;我们应该都要知道Python可还有个复数类型。 这里主要给大家讲解Java运算符的分类和使用。 一、运算符分类 说到运算符&#xff0c;我们可以先了…

硬件系统工程师宝典(9)-----如何正确使用去耦电容

各位同学大家好&#xff0c;欢迎继续做客电子工程学习圈&#xff0c;今天我们继续来讲这本书&#xff0c;硬件系统工程师宝典。上篇我们说到在电源完整性分析时&#xff0c;明确噪声来源可以有效的避免、解决噪声问题。今天我们来看看电源完整性分析中重要的一环&#xff0c;去…

【自动化测试】web自动化测试验证码如何测?如何处理验证码问题?解决方案......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 在对安全性有要求的…

线程池ThreadPoolExecutor源码剖析

一、Java构建线程的方式 继承Thread &#xff08;也实现了Runnable&#xff09; 实现Runnable 实现Callable &#xff08;与Runnable区别…&#xff09; 线程池方式 &#xff08;Java提供了构建线程池的方式&#xff09;[可以实现Runnable 和 Callable 功能] Java提供了Exe…

使用Vue3实现一个可复制的表格

前言 表格是前端非常常用的一个控件&#xff0c;但是每次都使用v-for指令手动绘制tr/th/td这些元素是非常麻烦的。同时&#xff0c;基础的 table 样式通常也是不满足需求的&#xff0c;因此一个好的表格封装就显得比较重要了。 最基础的表格封装 最基础基础的表格封装所要做…

【并发编程十七】c++实现一个线程池

【并发编程十七】c实现一个线程池一、线程池原理二、实现重点三、个人理解四、实验简介&#xff1a; 大多数系统上&#xff0c;若因某些任务可以与其他任务并行处理&#xff0c;就分别给他们配备专属的线程&#xff0c;则这种做法不切实际。但是只要有可能&#xff0c;我们还是…

C语言进阶——动态内存管理(上)

&#x1f307;个人主页&#xff1a;_麦麦_ &#x1f4da;今日名言&#xff1a;“你若爱&#xff0c;生活哪里都可爱。你若恨&#xff0c;生活哪里都可恨。你若感恩&#xff0c;处处可感恩。你若成长&#xff0c;事事可成长。不是世界选择了你&#xff0c;是你选择了这个世界。既…

mdio协议

1. 简介 MDIO接口中有特定的术语定义总线上的各种设备&#xff0c;驱动MDIO总线的设备被定义为站管理实体&#xff08;STA&#xff09;&#xff0c;而被MDC管理的目标设备称为可被MDIO管理的设备&#xff08;MMD&#xff09;。 STA初始化MDIO所有的通信&#xff0c;同时负责驱动…

【数据结构与算法】哈希表1:字母异位词 两数交集 快乐数 两数之和

文章目录今日任务1.哈希表理论基础&#xff08;1&#xff09;哈希表&#xff08;2&#xff09;哈希函数&#xff08;3&#xff09;哈希碰撞&#xff08;4&#xff09;链地址法&#xff08;拉链法&#xff09;&#xff08;5&#xff09;线性探测法&#xff08;6&#xff09;常见…