这篇文章上次修改于 2023 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
为什么计算机是基于二进制的
最早人们出现了大量计算的需求,首先经历了人工算然后借助算盘再到步进计算器、差分机、分析机。
计算的需求变得越来越大,机器也越来越先进,首先因为数学家可以通过逻辑运算进行加减乘除等逻辑运算,然后人们发明了机械继电器来描述状态表述二进制,由于机械的东西故障率很高又经历了热电子管(真空管)-> 晶体管、计算机便开始了快速发展,二进制也成为现代计算机的数据承载标准。
自从分析机出现后这些计算工具开始了初步编程化,然后出现了打孔机。直到出现冯·诺依曼结构,计算机开始蓬勃发展。
进制转换
先说一下各个进制的一般的表述方式
进制名称 | Java 中写法 | 一般表示符号 |
---|---|---|
二进制 | 0b101 0b 开头是二进制 | BIN |
八进制 | 011 0 开头是八进制 | OCT |
十进制 | 11 正常数字写法就是十进制 | DEC |
十六进制 | 0x11 0x 开头是十六进制 | HEX |
大家可以使用以下语句输出一下看看具体值是多少。
System.out.println(0b101);
System.out.println(011);
System.out.println(11);
System.out.println(0x11);
二进制与十进制
二进制到十进制:10100 = 1 * 2^4 + 0 * 2^3 + 1 * 2^2 + 0 * 2^1 + 0 * 2^0 = 20
十进制到二进制:
20 / 2 = 10 余 0
10 / 2 = 5 余 0
5 / 2 = 2 余 1
2 / 2 = 1 余 0
1 / 2 = 0 余 1
把余数倒序:10100
八进制与十进制
八进制到十进制:12345 = 1 * 8^4 + 2 * 8^3 + 3 * 8^2 + 4 * 8^1 + 5 * 8^0 = 5349
十进制到八进制:
5349 / 8 = 668 余 5
668 / 8 = 83 余 4
83 / 8 = 10 余 3
10 / 8 = 1 余 2
1 / 8 = 0 余 1
把余数倒序:12345
十六进制与十进制
十六进制到十进制:123BF = 1 * 16^4 + 2 * 16^3 + 3 * 16^2 + B(11) * 16^1 + F(15) * 16^0 = 74687
十进制到十六进制:
74687 / 16 = 4667 余 F(15)
4667 / 16 = 291 余 B(11)
291 / 16 = 18 余 3
18 / 16 = 1 余 2
1 / 16 = 0 余 1
把余数倒序:123BF
字符编码
本质是根据标准存储十进制索引编号。
哈夫曼最小字符编码(五位 32个值)-> Ascll 编码(7位 128个值) -> Unicode 编码(16位)
UTF-8 与 Unicode 的区别
大家看一看阮一峰大神写的这一篇即可。
Base64 原理
首先我们了解一下 Base64 是什么?最初网络传输有很多特殊字符服务器无法识别,传输起来有些问题,所以发明 Base64 编码来进行转码。
Base64 是使用大小写英文字母各26个、数字10个、加号 +
和斜杠 /
64 个字符来表示数据的编码,除了有以上 64 个符号,还有一个 =
作为后缀。因为只有 64 个有效字符,所以二进制有效位也只有 6 位(00111111 可以表示 64 个数)。
因为此处我们是转码文本,所以文本转换二进制是通过 Ascll 码表来转换的,然后每 6 位转换为十进制,然后根据十进制查询 Base64 索引表查询相应字符进行拼接,最后使用 =
代表 6 位去补齐,使位的总长度为 8 的倍数。解码亦如此。
Base64 的原理看到了,我们可以戳破几个不正确的说法。
- Base64 加密算法,我们看到了这根本不是什么加密算法,只是编码算法而已,最多使内容不能让人一眼就能记住。
- Base64 压缩,压缩也是不对的方法,根据他的实现方式,我们基本可以算出经过 Base64 编码会使数据增大 1/3。如果是对 Base64 编码后的数据在进行压缩,那就是其他压缩方式了,就不属于 Base64 编码的范畴了。
byte[] 与 十六进制字符串 转换
public static String toHex(byte[] bytes) {
if (bytes == null) {
return "";
}
final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
// byte 最大值是 255 转成字符串则为 0xFF,所以字符串长度是 byte 数组的两倍。
char[] hexChars = new char[bytes.length * 2];
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF; // 取 8 个位
hexChars[j * 2] = hexArray[v >>> 4]; // 取高 4 位放入 char
hexChars[j * 2 + 1] = hexArray[v & 0x0F]; // 取低 4 位放入 char
}
return new String(hexChars); // 转换为字符串
}
public static byte[] fromHex(String s) {
if (s != null) {
try {
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
// 首先把空格 '\n' '\r' 等特殊字符排除掉
if (!Character.isWhitespace(ch)) {
sb.append(ch);
}
}
s = sb.toString();
int len = s.length();
// 原因已经解释过了 byte 数组是字符串长度的 1/2
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
// 将 char 转换为 int 当做高 4 位
int hi = (Character.digit(s.charAt(i), 16) << 4);
// 将 char 转换为 int 当做低 4 位
int low = Character.digit(s.charAt(i + 1), 16);
if (hi >= 256 || low < 0 || low >= 16) {
return null;
}
// 通过位运算合并
data[i / 2] = (byte) (hi | low);
}
return data;
} catch (Exception ignored) {
}
}
return null;
}
这个字符串转换 byte 不好理解我们画张图,来辅助理解下。
媒体编码
所有媒体都是一样的,规范一个媒体格式为表述标准,然后使用相应格式的解析器来解析。
压缩
压缩在我们日常生活中很常见,一般都是对二进制数据进行压缩,我们今天为了简单就用简单的字符串举例来讲明。
无损压缩
假如文字内容是:你好啊你好你好你好你好你好你好你好你好你好你好
简单定义一个压缩方法,压缩之后就是:1*你好啊,10*你好
通过我们自定义的算法解压缩就可以还原了,一般公司的压缩算法都是比较复杂的。
有损压缩
我们现在使用的声音文件存储的都是声音波形。
假设有个声音文件:12 -32 45 23 -54 0 -7 34 37 89 -23 0 -54
有损压缩就是通过某些算法把某些无用的波形删掉,再执行无损压缩,但是解码回来就会少一部分声音。这就是有损压缩。
至于那些波形是无用的,是很多科学家,数学家研究得知。
编程语言基本类型(以 Java 为例)
类型 | 字节 | 位 |
---|---|---|
boolean | 1 | 8 |
byte | 1 | 8 |
char | 2 | 16 |
short | 2 | 16 |
int | 4 | 32 |
long | 8 | 64 |
float | 4 | 32 |
double | 8 | 64 |
计算机中最小的传递单位就是 1 字节,一般使用 byte 来承载。我们在一般的开发中肯定会遇到这种在流中读取数据或字节数组转换成其他类型或在字节中读取相应位的数据等等业务场景,比如网络传输,蓝牙设备或物联网设备交互的场景,可能都会遇到。我举两个高位在前,低位在后的例子,这类操作一般都是使用位运算来实现。大家在业务中遇到其他的场景随机应变即可。
byte 与 char 的转换(高位在前,低位在后)
char c1 = '中'; // 二进制 0100111000101101 UTF-8 编码为 ’中‘
byte[] bytes = new byte[2];
bytes[0] = (byte) ((c1 >> 8) & 0xff); // 01001110
bytes[1] = (byte) (c1 & 0xff); // 00101101
char c2 = '国'; // 二进制 0100111000101101 UTF-8 编码为 '国'
// 第一步先把 c2 的值清空,两种方式
// 1. 左移 16 位将 c2 清零
c2 = (char) (c2 << 16);
// 2. 无符号右移 16 位将 c2 清零
c2 = (char) (c2 >>> 16);
c2 = (char) (c2 | (bytes[0] & 0XFF)); // 将低八位赋值
c2 = (char) ((c2 << 8) | (bytes[1] & 0XFF)); // 左移八位,继续将低八位赋值
// c2 的值被改为 ’中‘
byte 与 int 的转换(高位在前,低位在后)
int i1 = 1234567890; // 二进制 01001001 10010110 00000010 11010010
byte[] bytes = new byte[4];
bytes[0] = (byte) ((i1 >> 24) & 0xFF);
bytes[1] = (byte) ((i1 >> 16) & 0xFF);
bytes[2] = (byte) ((i1 >> 8) & 0xFF);
bytes[3] = (byte) (i1 & 0xFF);
int i2 = 0;
i2 = i2 | (bytes[0] & 0XFF);
i2 = (i2 << 8) | (bytes[1] & 0XFF);
i2 = (i2 << 8) | (bytes[2] & 0XFF);
i2 = (i2 << 8) | (bytes[3] & 0XFF);
取 Int 中的其中几位
在我们一般的物联网通信中可能会在一个 short 值中把操作符和 data 都放进去。比如一个 short 是 2 字节。也就是 16 位,举个例子比如前四位表示操作符,中间8位表示数据,后四位表示校验位。
假设与设备通讯中规定好收到两个字节。
// byte[] 转换成 short
short data = 0;
data = (short) (data | (bytes[0] & 0XFF));
data = (short) ((data << 8) | (bytes[1] & 0XFF));
//假设 data 十进制的值为 31766 ,二进制为 01111100 00010110
// 取得前四位操作符 operator 为 28672 二进制 0111 0000 0000 0000
short operator = (short) (data & 0xF000); // 0xF000:11110000 00000000
// 取得中间八位数据 data1 为 3088 二进制 0000 1100 0001 0000
short data1 = (short) (data & 0x0FF0); // 0x0FF0:00001111 11110000
// 取得后四位校验位 check 为 6 二进制 0000 0000 0000 0110
short check = (short) (data & 0x000F); // 0x000F:00000000 00001111
高位,低位
假设有个二进制的值为 1001 0110。
高位是左边开始数,比如高四位就是 1001。
低位是右边开始数,比如低四位就是 0110。
小结
这一篇我讲的比较乱,但是魂并没有散,讲的都是二进制层面的一些知识点,也许不是最常用的也不是最全面的,但是如果我这篇博客,你能够看明白可以受益匪浅。
没有评论