跳到主要内容

Java中的进制与位运算

参考课程:https://www.bilibili.com/video/BV17t411q7nz

一 大端序与小端序

1.1 什么是字节序?

  • 字节序,又称端序尾序(英语中用单词:Endianness 表示),在计算机领域中,指电脑内存中或在数字通信链路中,占用多个字节的数据的字节排列顺序。
  • 在几乎所有的平台上,多字节对象都被存储为连续的字节序列。例如在 Go 语言中,一个类型为int的变量x地址为0x100,那么其指针&x的值为0x100。且x的四个字节将被存储在内存的0x100, 0x101, 0x102, 0x103位置。
  • 大端序(Big-Endian)将数据的低位字节存放在内存的高位地址高位字节存放在低位地址。这种排列方式与数据用字节表示时的书写顺序一致,符合人类的阅读习惯。
  • 小端序(Little-Endian),将一个多位数的低位放在较小的地址处,高位放在较大的地址处,则称小端序。小端序与人类的阅读习惯相反,但更符合计算机读取内存的方式,因为CPU读取内存中的数据时,是从低地址向高地址方向进行读取的。
  • 高字节与低字节: 在编程语言中,字符一般是占16位,8位为一字节,所以有高位字节和低位字节。一个16进制数有两个字节组成,例如:A9。高字节就是指16进制数的前8位(权重高的8位),如上例中的A。低字节就是指16进制数的后8位(权重低的8位),如上例中的9。

  • 很多人会问,为什么会有字节序,统一用大端序不行吗?答案是,计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。在计算机内部,小端序被广泛应用于现代 CPU 内部存储数据;而在其他场景,比如网络传输和文件存储则使用大端序

1.2 Java中高低位字节的运用

1、不同端模式的处理器进行数据传递时必须要考虑端模式的不同(因主机CPU不同,不同的机器会出现高、低两种字节序问题)。 2、在网络上传输数据时,由于数据传输的两端对应不同的硬件平台,采用的存储字节顺序可能不一致。所以在TCP/IP协议规定了在网络上必须采用网络字节顺序,也就是大端模式。对于char型数据只占一个字节,无所谓大端和小端。而对于非char类型数据,必须在数据发送到网络上之前将其转换成大端模式。接收网络数据时按符合接受主机的环境接收。

        int value = 515;
int big = (value & 0xFF00) >> 8;
int little = value & 0xFF;

System.out.println(big);
System.out.println(little);
  • 十六进制文字0xFF是一个相等的int(255)。
  • 0xFF是16进制的表达方式,F是15;十进制为:255,二进制为:1111 1111
  • &运算符:如果2个bit都是1,则得1,否则得0

1.2.1 原码

  • 原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。
  • 比如 00000000 00000000 00000000 00000101 是 5的 原码。

1.2.2 反码

  • 反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码
  • 取反操作指:原为1,得0;原为0,得1。(1变0;0变1)
  • 比如:将00000000 00000000 00000000 00000101每一位取反,得11111111 11111111 11111111 11111010。
  • 反码是相互的,所以也可称:11111111 11111111 11111111 11111010 和 00000000 00000000 00000000 00000101 互为反码。

1.2.3 补码

  • 补码:反码加1称为补码。
  • 也就是说,要得到一个数的补码,先得到反码,然后将反码加上1,所得数称为补码。
  • 比如:00000000 00000000 00000000 00000101 的反码是:11111111 11111111 11111111 11111010。
  • 那么,补码为:11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011

1.2.4 解释

  • 首先我们要都知道, &表示按位与,只有两个位同时为1,才能得到1, 0x代表16进制数,0xff表示的数二进制1111 1111 占一个字节.和其进行&操作的数,最低8位,不会发生变化
  • 例如:java socket通信中基于长度的成帧方法中,如果发送的信息长度小于65535字节,长度信息的字节

定义为两个字节长度。这时候将两个字节长的长度信息,以Big-Endian的方式写到内存中

out.write((message.length>>8)&0xff);//取高八位写入地址
out.write(message.length&0xff);//取低八位写入高地址中
  • 例如,有个数字 0x1234,如果只想将低8位写入到内存中 0x1234&0xff,0x1234 表示为二进制 0001001000110100,0xff 表示为二进制 11111111,两个数做与操作,显然将0xff补充到16位,就是高位补0,此时0xff 为 0000000011111111,与操作 1&0 =0 1&1 =1 这样 0x1234只能保留低八位的数 0000000000110100 也就是 0x34。
  • 我们只关心二进制的机器数而不关注十进制的值,那么byte &0xff只是对其最低8位的复制,通常配合逻辑或 ‘’|’'使用,达到字节的拼接,但不保证其十进制真值不变
public static void main(String[] args) {
byte b = -127;//10000001
int a = b;
System.out.println(a);
a = b&0xff;
System.out.println(a);
}//输出结果-127,129
  • 乍一看,b是8位的二进制数,在与上0xff(也就是 11111111),不就是其本身吗,输出在控制台结果为什么是129呢?
  • 首先计算机内的存储都是按照补码存储的,-127补码表示为 1000 0001,int a = b;将byte 类型提升为int时候,b的补码提升为 32位,补码的高位补1,也就是1111 1111 1111 1111 1111 1111 1000 0001
  • 负数的补码转为原码,符号位不变,其他位取反,在加1,正数的补码,反码都是本身结果是 1000 0000 0000 0000 0000 0000 0111 1111表示为十进制 也是 -127
  • 也就是 当 byte -> int 能保证十进制数不变,但是有些时候比如文件流转为byte数组时候,我们不是关心的是十进制数有没有变,而是补码有没有变,这时候需要&上0xff
  • 本例子中,将byte转为int 高24位必将补1,此时补码显然发生变化,在与上0xff,将高24重新置0,这样能保证补码的一致性,当然由于符号位发生变化,表示的十进制数就会变了。

二 进制转换

2.1 进制的概念?

  • 指进位计数制,是人为定义的一种带进位的计数方法。
  • 即对于任何一种确定的进制,每一位置上的数运算时都是逢该确定的值进一位。
  • 例如,二进制就是逢二进一,四进制是逢四进一,十进制是逢十进一,十六进制是逢十六进一等。
  • 其中,十进制是人们在日常生活中最常用的进制,二进制是计算机中最常用的进制。

2.2 为啥有二进制,八进制,十进制,十六进制?

  • 有二进制的原因是因为计算机最底层的电子元器件只有两种状态高电平和低电平(有电和没电)。
  • 任何数据在计算机中都是以二进制的形式存在的,二进制早期由电信号开关演变而来。
  • 一个整数在内存中一样也是二进制的,但是使用一大串的1或者0组成的数值进行使用很麻烦。
  • 有八进制、十六进制的原因是因为二进制表示同样的数值太长不方便阅读和记忆,而八进制和十六进制较短,方便阅读和记忆。,

2.3 计算机中的单位转换?

  • 计算机存储单位一般用bit、B、KB、MB、GB、TB、PB、EB、ZB、YB、BB、NB、DB……来表示,它们之间的关系是:
  • 位 bit (比特)(Binary Digits):存放一位二进制数,即 0 或 1,最小的存储单位。[英文缩写:b(固定小写)]
  • 字节byte:8个二进制位为一个字节(B),最常用的单位。
  • 1 Byte(B) = 8 bit
  • 1 Kilo Byte(KB) = 1024B
  • 1 Mega Byte(MB) = 1024 KB
  • 1 Giga Byte (GB)= 1024 MB
  • 1 Tera Byte(TB)= 1024 GB
  • 1 Peta Byte(PB) = 1024 TB

2.4不同进制的组成规则?

二进制组成规则

  • 由0,1组成,以0b开头,eg:0b00000011

八进制组成规则

  • 由0,1~7组成,以0开头,eg:017

十进制组成规则

  • 由0,1~9组成,整数默认是十进制

十六进制组成规则

  • 一0~9,abcdfe组成,以0x开头,eg:0x01

2.5 不同进制的运算?

二进制的运算 (逢二进1)

ob0000 1111

ob0101 1000

ob0110 0111

八进制的运算(逢八进一) 0127

0765

1 1 1 4

十六进制转换(逢十六进一) 0x1ab

0x99a

0xb45

2.6 任意进制的转换?

系数 eg:157 个位:7 十位:5,百位:1 基数 几进制的基数就是几 一个数据从0开始,从右往左,对每一位数据进行编号,这个编号就是这个位置上的系数 规律 任意进制到几进制的转换都等于这个数据的各个位上的系数乘以基数的权次幂相加的和 十进制转换为二 /八/十六进制(整除取余法) 二进制 1 x 24 + 0 x 23 + 1 x 22 + 0 x 21 + 0 x 20 + 0 x 2-1 + 1 x 2-2 = 16 + 0 + 4+ 0 + 0 + 0 + 0.25 = 20.25 八进制:256(0) 2 x 82 + 5 x 81 + 6 x 80 + 3 x 8-1 = 174 十六进制:6F8A(H)** 注:带有字母 A、B、C、D、E、F(分别表示10,11,12,13,14,15) 6 x 163 + 15 x 162 + 8 x 161 + 10 x 160 = 28554

三 位运算

计算机中所有的计算到计算机底层中都会变成位运算(就是二进制位的运算)。位运算可以提高程序的效率!而且阅后如果我们研究JDK或者某个框架的原码,会发现很多地方都用到

3.1 位运算符的种类

  • &:按位与
  • |:按位或
  • ^:按位异或
  • ~:按位取反
  • <<:左移
  • :右移

  • :无符号右移

3.2 运算

在进行位运算的时候要把数据转换成二进制位!并且全部都是二进制的补码形式! 按位与运算&(同一为一) 3 & 4 00000000 00000000 00000000 00000011

00000000 00000000 00000000 00000100

00000000 00000000 00000000 00000000 按位或运算|(有一为一) 3 | 4 00000000 00000000 00000000 00000011

00000000 00000000 00000000 00000100

00000000 00000000 00000000 00000111 按位异或^ 3 ^ 4 00000000 00000000 00000000 00000011

00000000 00000000 00000000 00000100

0000 0111 按位取反~ ~3

00000000 00000000 0000000 0000 0011

11111111 1111111 11111111 11111100 (补码) 求原码 11111111 11111111 11111111 11111011 取反,符号位不变 10000000 00000000 00000000 000000100 (-4) << 左移运算

  • 左移几位,整体向左移动几位,右边空出的位用0填补,高位左移溢出则舍弃该高位。
  • 总结:左移几位,相当于原数*2的N次方

左移三位 00000000 00000000 00000000 00000111 00000000 00000000 00000000 00111000 >> 右移运算

  • 左边空出的位用0或者1填补。正数用0填补,负数用1填补,(符号位补全)。
  • 注:不同的环境填补方式可能不同;低位右移溢出则舍弃该位。

00000000 00000000 00000000 01110000

00000000 00000000 00000000 00001110

10000000 00000000 00000000 01110000 11111111 11111111 11111111 1111111 >>> 无符号右移

  • 左边空出的位用0补全。

10000000 00000000 00000000 01110000 00000000 00000000 00000000 00001110