前言
最近做项目,发现一些规律,比如数字的存储和字符串的存储,先说数字吧,最常见的整数,就涉及反码和补码,根据这些规则,甚至我们自己也能造一种数据存储结构,比如1个字节8bit,在byte里面就是一半正数一半负数,但是在二进制却是我们不理解的一种方式。计算机存储都是字节存储,字符实际上也是字节,那么byte[]数组在很多时候就很关键,比如grpc算法那一期:grpc Java demo与Springboot改造支持grpc通信_grpc-client-spring-boot-starter-CSDN博客
准备demo
demo解说
比如byte 127表示01111111,byte是8bit,所以有一个符号位,但是负数呢
-128就是10000000,这里转int了,4个字节而byte是一个字节,实际上byte=字节,就是直译的
那么我们把int强转为byte呢
这就涉及数据的存储了,计算机不能运算减法,使用加法做减法,那么负数就需要一种表现形式,即符号位,但是bit2进制存储,并不是我们理解的存储方式,这就涉及反码和补码了,数据以补码的形式存储。
原码、反码、补码
数据都是2进制存储,计算机为了做减法,对每个字节的从左到右第一个bit定义为符号位,为什么从左到右,实际上在看UTF-16的编码设计,就有高位和低位的说法,真难懂,字节byte的第一bit为符号位定义清晰。
原码
原码是使用数据本身的定义存储,即符号位1bit,其他是我们真实的数据,非常符合我们的认知。
|-----|-----------|-----|-----------|
| 正整数 | 二进制原码 | 负整数 | 二进制原码 |
| 1 | 0000 0001 | -1 | 1000 0001 |
| 2 | 0000 0010 | -2 | 1000 0010 |
| 3 | 0000 0011 | -3 | 1000 0011 |
| 4 | 0000 0100 | -4 | 1000 0100 |
| 5 | 0000 0101 | -5 | 1000 0101 |
但是原码有个问题:1+ (-1) ≠ 0,这是不允许的。
反码
既然原码1 + (-1) ≠ 0那么,按位取反,就可以了,表示负数
整数的反码与原码一致,负数的反码是除了符号bit,其余bit位按位取反,毕竟反码就是为负数做加法设计的。但是因为负数是按位取反,所以1+(-1) ≠ 0。
|-----|-----------|-----|-----------|
| 正整数 | 二进制反码 | 负整数 | 二进制反码 |
| 1 | 0000 0001 | -1 | 1111 1110 |
| 2 | 0000 0010 | -2 | 1111 1101 |
| 3 | 0000 0011 | -3 | 1111 1100 |
| 4 | 0000 0100 | -4 | 1111 1011 |
| 5 | 0000 0101 | -5 | 1111 1010 |
补码
负数除了符号位按位取反后,虽然语义清晰了,但是做加法运算仍然是有问题的:
1+(-1)≠ 0,这就需要补码了
正数的原码、反码、补码一致;负数的补码=反码+1bit
|-----|-----------|-----|-----------|
| 正整数 | 二进制补码 | 负整数 | 二进制补码 |
| 1 | 0000 0001 | -1 | 1111 1111 |
| 2 | 0000 0010 | -2 | 1111 1110 |
| 3 | 0000 0011 | -3 | 1111 1101 |
| 4 | 0000 0100 | -4 | 1111 1100 |
| 5 | 0000 0101 | -5 | 1111 1011 |
实际上数据就是补码存储的,但是每个字节做加法也有问题
1+(-1) = 1 0000 0000
就是超限了,计算机就把超限的bit位舍弃,来达到1+(-1)=0的计算,所以-1就是1111 1111了,但是对于byte来说-128呢,毕竟0111 1111 = 127,即一个字节,所以-128没有原码,当然也没有反码了,只有补码,毕竟0这个特殊的数据,不存在+0和-0的说法。
-128原码= 1 1000 0000 超限byte了,byte只有8bit,去掉符号位只有7位,即原码最大-127
-128反码= 1 0111 1111 原码没有就没有反码了
-128补码= 1 1000 0000 补码
说说0
-0的原码=1 000 0000
-0的反码=1 111 1111
-0的补码=0 000 0000 = +0
神奇的设计。
byte[]数组
那么byte[]数组呢,奇特的地方是每个byte是-128~127,但是多个字节时只有开始的一个bit是符号位,对于多个字节的整个bit位是遵循补码存储的思路,但是对于单个字节就会出现负数表示255的情况。
可以看到255刚好够一个byte存储,转为byte看看
-1,相当于-1表示255,128表示-128
相当于考虑符号位和多字节不考虑字节本身符号位的表示规则:
127后是-128,然后依次递增+1,知道255=-1
当然,int转byte[]就涉及高低位顺序了,类似utf-16编码。
上次grpc的算法
grpc Java demo与Springboot改造支持grpc通信_grpc-client-spring-boot-starter-CSDN博客
java
package org.springframework.http.converter.protobuf;
public class LengthBytesUtils {
public static byte[] intToBytes(int length, int size) {
byte[] bytes = new byte[size];
int temp;
for (int i = size - 1; i > -1; i--) {
temp = (int) ((length / (Math.pow(253, (size - 1 - i)))) % 253);
if (temp > 127) {
temp = -128 + 1 + temp - 127 + 1;
}
bytes[i] = (byte) temp;
}
return bytes;
}
public static int bytesToInt(byte[] bytes) {
int length = 0;
int size = bytes.length;
for (int i = 0; i < size; i++) {
if (bytes[i] == 0){
continue;
}
if (bytes[i] > 0) {
length += bytes[i] * Math.pow(253, size-1-i);
} else {
length += (127-1 + bytes[i] + 128-1) * Math.pow(253, size-1-i);
}
}
return length;
}
}
实际上是剔除了一些byte数据表示,但是grpc把长度从int的4字节提升到5字节,相当于创造了一种正整数 数据类型,实际上可以优化为
java
public static byte[] intToBytes2(int length, int size) {
byte[] bytes = new byte[size];
int temp;
for (int i = size - 1; i > -1; i--) {
temp = (byte) ((length / (Math.pow(253, (size - 1 - i)))) % 253);
if (temp < 0) {
temp = 3 + temp;
}
bytes[i] = (byte) temp;
}
return bytes;
}
public static int bytesToInt2(byte[] bytes) {
int length = 0;
int size = bytes.length;
for (int i = 0; i < size; i++) {
if (bytes[i] == 0) {
continue;
}
if (bytes[i] > 0) {
length += bytes[i] * Math.pow(253, size - 1 - i);
} else {
length += (bytes[i] + 253) * Math.pow(253, size - 1 - i);
}
}
return length;
}
因为int转byte,符号位生效,byte会认为第一个bit是符号位,但是对int的字节数组的每个字节就不一定了,只有最高位bit是符号位,其他都是数据位
int 4个字节
0 0000000 00000000 00000000 10000000 = int 128 = byte[]{0, 0, 0, -128}
但是byte -128转int还是-128,因为是向上转型,是byte类型一定是int类型,反之则不对。
总结
实际上这次是一些碎碎念,核心还是计算机的原理,计算机因为电气性能只设计有0、1,所以是2进制,存算都是2进制。而且计算机在设计之初只设计了加法,没设计或者设计减法有问题,导致数据相减运算都是加运算,所以需要存储和运算负数,那么就需要定义负数的存储和相加逻辑,就设计了反码和补码来存储负数。
另外一个字节存储能力有限,往往需要很多字节来存储一个内容,那么符号位的定义在总体结构很明晰,但是对于字节数组的单个字节就会存在歧义,造成数据可读性很迷惑,因为符号位的特殊意义。同时,我们可以根据需要自定义数据类型,但是字节数组存储会有读取顺序的高低位问题,类似UTF-16编码的左右读取模式。