有符号位的数据表示法以及位运算

有符号位的数据表示法与位运算

无套路、关注即可领。持续更新中
关注公众号:搜 【架构研究站】 回复:资料领取,即可获取全部面试题以及1000+份学习资料

前言:在计算机底层,数据的运算主要通过 “补码” 来进行。每个数据都有原码、反码和补码三种表示形式。

一、有符号位的数据表示法

(一)正数

  • 在计算机中,通常以固定的位数来表示整数,例如 8 位、16 位或 32 位等。以 8 位二进制表示为例,正数的原码、反码和补码都相同。例如数字 7,其 8 位二进制原码、反码、补码均为 00000111。

(二)负数

  • 原码:首先按照对应数据类型规定的二进制表示位数求出该数字的二进制,然后将最高位设为 1,表示该数为负数。例如在 8 位有符号整数中,-7 的原码为 10000111。

  • 反码:原码的最高符号位保持不变,其余数值位按位取反(0 变为 1,1 变为 0)。如 -7 的反码为 11111000。

  • 补码:反码的末尾加 1 即为补码。所以 -7 的补码是 11111001。

(三)类型转换实例

  • 考虑byte b = (byte)130的情况。在 32 位 int 类型中,130 的二进制表示为 00000000 00000000 00000000 10000010。由于 byte 类型的存储范围是 -128 到 127,将 int 类型的 130 强制转换为 byte 类型时,只取低 8 位,得到 10000010(这是补码形式)。因为最高位为 1,可知这是一个负数。对其补码末尾减 1 得到反码 10000001,再将反码的符号位不变,其余数值位按位取反得到原码 11111110,换算为十进制即为 -126。

二、位运算

(一)基本位运算符

  • 位与(&):运算规则是有 0 则 0。常用于取某些位上的值,例如判断一个整数的奇偶性,可以将该数与 1 进行位与运算,如果结果为 0,则为偶数,否则为奇数。
  • 位或(|):有 1 则 1。
  • 位异或(^):相同为 0,不同为 1。在数据加密的简单变换等场景中有应用,例如对数据进行简单的位异或加密操作后,再通过相同的密钥进行位异或解密可以恢复原始数据。
  • 反码(~):按位取反。

(二)移动符号

  • 左移(<<):将最高符号位舍弃,末尾补 0。
    例如: 3 的二进制为 0b11(以 8 位为例表示为 00000011)
    3 << 2 的运算过程为:
00000000 00000000 00000000 00000011
(00) 00000000 00000000 00000000 00001100(正数,原码 = 反码 = 补码)
结果为 12。
  • 右移(>>):如果最高位为 0,在左边补 0;如果最高位为 1,左边补 1。
    例如:24 的二进制为 0b11000(以 32 位为例表示为 00000000 00000000 00000000 00011000)
    24 >> 2 的运算过程为:
00000000 00000000 00000000 00011000
00000000 00000000 00000000 00000110 (00)(正数,原码 = 反码 = 补码)
结果为 6。

例如:-24 的二进制原码为 10000000 00000000 00000000 00011000,其反码为 11111111 11111111 11111111 11100111,补码为 11111111 11111111 11111111 11101000
-24 >> 2 的运算过程为:
1

1111111 11111111 11111111 11101000
11111111 11111111 11111111 11111010 (00)
反码为 11111111 11111111 11111111 11111001
原码为 10000000 00000000 00000000 00000110
结果为 -6。
  • 无符号右移(>>>):无论最高符号位是 0 还是 1,都在左边补 0
    例如 -24 的二进制原码为 10000000 00000000 00000000 00011000,反码为 11111111 11111111 11111111 11100111,补码为 11111111 11111111 11111111 11101000
    -24 >>> 2 的运算过程为:
11111111 11111111 11111111 11101000
00111111 11111111 11111111 11111010 (00)(正数,原码 = 反码 = 补码)
原码为 00111111 11111111 11111111 11111010
结果为 1073741818

在JAVA语言JDK 1.8中HashMap涉及到位运算的代码举例

一、HashMap中哈希码计算的入口

在HashMap的put方法中,当插入一个键值对时,会先计算键的哈希值。相关代码如下:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

其中hash(key)方法是计算哈希值的关键。在JDK 1.8中,hash方法的实现如下:

static final int hash(Object key) {
    int h;
    // key的hashCode方法返回一个整数作为原始哈希码
    // 当key为null时,哈希码为0,否则对非null的key的哈希码进行扰动操作
    return (key == null)? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

二、哈希码的扰动操作(位运算)

  • 对于非null的键,首先获取其hashCode并赋值给h。然后进行h ^ (h >>> 16)操作。
  • 以一个简单的例子来说明这个操作的意义。假设h的二进制表示为11111111 00000000 11111111 00000000。
  • h >>> 16的操作是无符号右移 16 位,得到00000000 00000000 11111111 00000000。
  • 接着进行位异或^操作,即相同位为 0,不同位为 1。对于上面的例子,经过位异或后得到一个新的哈希值,这个新哈希值融合了原始哈希值高低位的信息,使得哈希值更加随机和均匀。
  • 这种操作的目的是为了减少哈希冲突。如果不进行这样的操作,仅仅使用hashCode的低 16 位(因为在计算索引时主要是和数组长度 - 1 进行位与操作,对于较小的数组长度,主要是使用哈希值的低几位)可能会导致许多不同对象的哈希值在低位部分相同,从而增加冲突的概率。通过将高低位混合,可以更好地利用哈希值的所有位信息,使得哈希值在整个范围内更加均匀地分布。

三、哈希表索引计算中的位运算

  • 在putVal方法中,计算出哈希值后,会根据哈希值确定键值对在哈希表数组中的存储位置(索引)。相关代码如下:
 if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);
  • 其中n是哈希表数组的长度(在HashMap初始化或者扩容后确定),hash是经过扰动后的哈希值。计算索引的操作是(n - 1) & hash。
  • HashMap为了性能优化,其数组长度n通常是 2 的幂次方。例如,当n = 16(二进制为10000)时,n - 1的二进制为01111。通过位与&操作,将哈希值和n - 1进行运算,可以得到一个范围在0到n - 1之间的索引值。
  • 以一个简单的哈希值hash = 11111111 00000000 11111111 00000000和n - 1 = 01111为例,位与操作会根据hash的低位部分和n - 1进行运算,得到一个合适的索引值,确保索引值不会超出哈希表数组的范围,同时利用位运算的高效性快速确定存储位置。

四、位运算在性能和均匀分布上的综合作用

  • 性能方面:位运算(如位异或和位与)在计算机底层是非常高效的操作。在现代处理器架构中,这些位运算指令通常可以在一个或几个时钟周期内完成。相比其他复杂的算术运算或比较操作,使用位运算来计算哈希值和索引可以显著提高HashMap的操作速度,特别是在频繁的put和get操作中。
  • 均匀分布方面:通过在哈希码计算中的扰动操作,使得哈希值能够充分利用所有位的信息,避免了哈希值仅依赖于低位部分而导致的冲突集中问题。而在索引计算中,利用位与操作和数组长度为 2 的幂次方的特性,保证了索引在合法范围内均匀分布。这样,键值对能够更均匀地分布在哈希表数组中,减少了哈希冲突导致的链表(在哈希冲突时存储键值对的结构)长度增长。在JDK 1.8中,当链表长度超过一定阈值(默认为 8)时,还会将链表转换为红黑树来进一步优化性能,而均匀分布的哈希值可以减少这种转换的频率,从而提高HashMap整体的性能。

相关资料已更新
关注公众号:搜 架构研究站,回复:资料领取,即可获取全部面试题以及1000+份学习资料

你可能感兴趣的:(有符号位的数据表示法以及位运算)