本文介绍了一些字、字节、比特、进制、位运算的定义、转换、运算及其使用方法、使用场景、在某些场景下使用二进制的意义。

字、字节、比特。

在计算机领域,"字节"、"字"、"比特"和"位"是基本的数据单位,它们的含义和换算关系如下:

字(Word):

字是计算机一次处理的数据单位,大小取决于系统架构:

32位系统:1 Word = 4 Bytes = 32 bits

64位系统:1 Word = 8 Bytes = 64 bits

字长决定了CPU一次能处理的数据量

字节(Byte):

1 Byte = 8 bits

字节是计算机存储和数据处理的基本单位,例如:

1个英文字符通常占1字节

1个汉字通常占2-3字节(取决于编码)

比特(bit)常用:

计算机中最小的数据单位,表示一个二进制位,只能是0或1。

1 bit = 1个二进制位

又名位(bit)与"比特"是同一概念,只是中文翻译不同。比特码通常指用二进制比特(bits) 表示的编码数据。

例如:

ASCII 编码中,字母 A 的二进制表示是 01000001(8 bits = 1 Byte)。

颜色 RGB 值可以用 24 bits(3 Bytes)表示,如 FF0000(红色)

进制

十六进制(HEX)

基数为 16,使用数字 0-9 和字母 A-F(或 a-f)表示数值。

用途:常用于计算机编程、内存地址、颜色代码(如 #FF0000 表示红色)。

示例:

A(十六进制)= 10(十进制)

FF(十六进制)= 255(十进制)

十进制(DEC)

基数为 10,使用数字 0-9,是我们日常使用的标准计数方式。

用途:普通数学计算、大多数编程语言中的默认数值表示。

示例:

15(十进制)= F(十六进制)= 1111(二进制)

八进制(OCT)

基数为 8,使用数字 0-7。

用途:早期计算机系统(如 Unix 文件权限 chmod 755),现代编程中较少使用。

示例:

10(八进制)= 8(十进制)

777(八进制)= 511(十进制)

二进制(BIN)

基数为 2,仅使用数字 0 和 1。

用途:计算机底层数据处理、位运算(如 AND、OR)、硬件编程。

示例:

1010(二进制)= A(十六进制)= 10(十进制)

11111111(二进制)= FF(十六进制)= 255(十进制)

换算关系

1 Word = 取决于系统架构(通常4或8 Bytes)

1 Byte = 8 bits。例如,01011010 是 1 Byte(8 bits)

易混淆点

纳尼纳尼?8bits为啥不是1000而是01011010 呢?

什么是 1 bit?

1 bit 是计算机最小的数据单位,只能是 0 或 1(即一个二进制位)。

例如:0、1 都是 1 bit。

所以8位8bits是指8个二进制,是数据长度而不是数据大小。

1Byte = 8 bits=8 个连续的二进制位(如 01011010),取值范围为~ ,即二进制的-1000 0000~0111 1111 ,十进制的-128~127。4Byte = 32bits,取值范围为~ 。

电脑计算器中的换算方法(以Windows计算器为例):

打开计算器

切换到"程序员"模式(Programmer Mode)

选择进制(二进制、十进制、十六进制等)

输入数值后,计算器会自动显示其他进制的值和比特数

示例换算

1 KB (Kilobyte) = 1024 Bytes

1 MB (Megabyte) = 1024 KB

1 GB (Gigabyte) = 1024 MB

1 TB (Terabyte) = 1024 GB

注意:存储设备厂商通常使用十进制换算(1KB=1000Bytes),而操作系统使用二进制换算(1KB=1024Bytes),这会导致标称容量和实际显示容量有差异。

位运算、补码

位运算

操作运算符示例(二进制)用途按位与&1100 & 1010 = 1000掩码、清零位按位或|1100 | 1010 = 1110按位异或^1100 ^ 1010 = 0110切换位、交换变量按位取反~~1100 = 0011(实际32位)取反所有位左移<<1100 << 2 = 110000乘以 2^n算术右移>>1100 >> 1 = 1110除以 2^n(保留符号)逻辑右移>>>1100 >>> 1 = 0110无符号右移

位运算直接操作二进制位,以下是 6 种基本操作:

按位与(AND)&

规则:两位均为 1 时结果为 1,否则为 0。

用途:掩码(Mask)、清零特定位。

int a = 0b1100; // 12 注:0b是java中定义整型数据是byte型数据的方式,一般跟int类型一样占4个字节

int b = 0b1010; // 10

int result = a & b; // 0b1000 (8)

按位或(OR)|

规则:两位至少有一个 1 时结果为 1。

用途:设置特定位为 1。

int result = a | b; // 0b1110 (14)

按位异或(XOR)^

规则:两位不同时结果为 1,相同为 0。

用途:切换特定位、交换变量值。

int result = a ^ b; // 0b0110 (6)

// 交换变量

int x = 3, y = 4;

x = x ^ y; y = x ^ y; x = x ^ y; // x=4, y=3

按位取反(NOT)~

规则:所有位取反(包括符号位)。

注意:结果依赖数据类型位数(如 ~0b0001 在 32 位中是 0xFFFFFFFE)。

int result = ~a; // 0b11111111111111111111111111110011 (-13,32位补码)

左移(Left Shift)<<

规则:所有位向左移动,低位补 0。

用途:快速乘以 2^n。

int result = a << 2; // 0b11000 (48)

右移

算术右移 >>:高位补符号位(负数补 1,正数补 0)。

逻辑右移 >>>:高位补 0(无符号右移)。

int c = -8; // 二进制补码0b11111111111111111111111111111000

int arithRight = c >> 1; // 0b11111111111111111111111111111100 (-4)

int logicRight = c >>> 1; // 0b01111111111111111111111111111100 (2147483644)

补码

补码是计算机表示负数的标准方式,其核心规则:

负数补码 = 原码取反 + 1

补码转原码 = 取反 + 1(与生成补码的步骤相同)

补码与位运算的实战应用

判断奇偶

boolean isOdd = (num & 1) == 1; // 最末位为1则是奇数

取绝对值(避免分支预测)

int abs = (num ^ (num >> 31)) - (num >> 31); // 适用于32位整数

标志位组合

final int FLAG_A = 1 << 0; // 0b0001

final int FLAG_B = 1 << 1; // 0b0010

int flags = FLAG_A | FLAG_B; // 同时设置A和B标志

boolean hasFlagA = (flags & FLAG_A) != 0; // 检查A标志

颜色处理(ARGB 32位)

int alpha = (color >> 24) & 0xFF; // 提取Alpha通道

int red = (color >> 16) & 0xFF; // 提取红色通道

手动计算补码(以 8 位为例)

示例:求 -5 的补码

取绝对值原码:5 → 00000101

按位取反:11111010

加 1:11111011(这就是 -5 的补码)

验证:补码转十进制

11111011 → 最高位为 1(负数)→ 取反 00000100 → 加 1 → 00000101(5)→ 结果是 -5。

代码中获取补码

int num = -5;

String binary = Integer.toBinaryString(num); // 输出 "11111111111111111111111111111011"(32位补码)

常见用途

除上述提到的十六进制用于计算机编程、内存地址、颜色代码(如 #FF0000 表示红色),八进制用于计算机系统(如 Unix 文件权限 chmod 755),二进制用于计算机底层数据处理、位运算(如 AND、OR)、硬件编程、位掩码。还有:

编程调试:快速查看十六进制或二进制的内存/寄存器值。

网络/IP计算:十六进制用于 MAC 地址,二进制用于子网掩码。

加密/哈希:分析二进制或十六进制的数据。

ASCII 编码:字母 A 的二进制表示是 01000001(8 bits = 1 Byte)。

颜色RGB值:可以用 24 bits(3 Bytes)表示,如 #FF0000(红色)

java中的8种数据类型:boolean(1或者4个字节)、int(4个字节)、byte(1个字节)、short(2个字节)、longshort(8个字节)、char(2个字节)、float(4个字节)、double(8个字节)

流媒体格式和音视频协议:

FLV组成(header8字节/PreviousTagSize4字节/TagHeader11字节)

一个字节的NALU头信息(1bit F/2 bit R/5 bit T)

ADTS

⼀段分辨率为 1920*1080 ,每个像素点为 RGB 占⽤ 3 个字节,帧率是 25 的视频,对于传输带宽的要求是: 1920*1080*3*25/1024/1024=148.315MB/s。

常见数据及其意义

看到这再看平时看到的乱七八糟的数据总看得懂了吧?来来来,搞点实例,现在强得可怕

颜色

#FF0000是十六进制,

FF → 红色(R)通道:最大值(255),表示纯红色。

00 → 绿色(G)通道:最小值(0)。

00 → 蓝色(B)通道:最小值(0)。

结果:#FF0000 表示 纯红色。

十进制转换:

FF(十六进制)= 255(十进制)

00(十六进制)= 0(十进制)

因此,#FF0000 = RGB(255, 0, 0)。

public static final Color PURE_RED = new Color(0xFF0000); // 十六进制

public static final Color PURE_RED_RGB = new Color(255, 0, 0); //十进制

public static final String PURE_RED = "#FF0000"; //字符串

public static final int PURE_RED = 0xFF0000; // 直接存储十六进制值

十六进制颜色值在 Java 中通常以0x开头(而非 #),#仅用于前端表示。如果颜色包含透明度(ARGB),需使用 0xAARRGGBB 格式(如 0xFFFF0000 表示不透明的红色)。

流媒体格式和音视频协议

flv-audio-soundInfo

请说明下面这段代码的意思:

CFlvParser::CAudioTag::CAudioTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen, CFlvParser *pParser) {

Init(pHeader, pBuf, nLeftLen);

uint8_t *pd = _pTagData;

_nSoundFormat = (pd[0] & 0xf0) >> 4; //音频格式

_nSoundRate = (pd[0] & 0x0c) >> 2; //采样率

_nSoundSize = (pd[0] & 0x02) >> 1; //采样精度

_nSoundType = (pd[0] & 0x01); //是否是立体声

if(_nSoundFormat == 10) { //AAC

ParseAACTag(pParser);

}

}

先了解一下flv结构

当 tag type = 0x08(音频 Tag) 时,第8个字节存储音频配置信息,其二进制位划分如下:

| 7-4 (4 bits) | 3-2 (2 bits) | 1 (1 bit) | 0 (1 bit) |

|---------------|--------------|----------|-----------|

| SoundFormat | SoundRate | SoundSize| SoundType |

SoundFormat (4 bits): 音频编码格式(如 10=AAC)。

SoundRate (2 bits): 采样率(如 3=44.1kHz)。

SoundSize (1 bit): 采样精度(1=16-bit,0=8-bit)。

SoundType (1 bit): 声道(1=立体声,0=单声道)。

具体数字代表的意思如下:

注:UI表示⽆符号整形,后⾯跟的数字表示其⻓度是多少位。⽐如 UI 8 ,表示⽆符号整形,⻓度⼀个字节。UI 24 是三个字节,UI[ 8 *n]表示多个字节。UB表示位域,UB 5 表示⼀个字节的 5 位。

ok,再来看每行代码具体对应的意思:

假设 pd[0] 的值是 0xAE(二进制 10101110),以下是具体解析过程:

(1) 提取音频格式 _nSoundFormat

_nSoundFormat = (pd[0] & 0xF0) >> 4;

操作:

pd[0] & 0xF0:10101110&11110000=1010000。即:保留高 4 位(0b1010),其余置 0 → 0b10100000(0xA0)。即:

>> 4:右移 4 位 → 0b00001010(0x0A)。

结果:_nSoundFormat = 0x0A(十进制 10),表示音频格式为 AAC(对应上面的FLV -audio-soundInfo表来看 AAC 的编码值就是 10)。

(2) 提取采样率 _nSoundRate

_nSoundRate = (pd[0] & 0x0C) >> 2;

操作:

pd[0] & 0x0C:保留第 2-3 位(0b00001100),其余置 0 → 0b00001100(0x0C)。

>> 2:右移 2 位 → 0b00000011(0x03)。

结果:_nSoundRate = 0x03(十进制 3),可能对应 44.1kHz(需查具体协议,FLV 中 3 通常表示 44.1kHz)。

(3) 提取采样精度 _nSoundSize

_nSoundSize = (pd[0] & 0x02) >> 1;

操作:

pd[0] & 0x02:保留第 1 位(0b00000010),其余置 0 → 0b00000010(0x02)。

>> 1:右移 1 位 → 0b00000001(0x01)。

结果:_nSoundSize = 0x01,表示 16-bit 采样精度(FLV 中 1 表示 16-bit,0 表示 8-bit)。

(4) 提取声道类型 _nSoundType

_nSoundType = (pd[0] & 0x01);

操作:

pd[0] & 0x01:保留第 0 位(0b00000001),其余置 0 → 0b00000001(0x01)。

结果:_nSoundType = 0x01,表示 立体声(Stereo)(FLV 中 1 为立体声,0 为单声道)。

flv-tag-header

tag header⼀般占 11 个字节的内存空间。FLV tag结构如下:

解析时位偏移及type类型的对应代码如下:

CFlvParser::Tag *CFlvParser::CreateTag(uint8_t *pBuf, int nLeftLen) {

//开始解析标签头部

TagHeader header;

header.nType = ShowU8(pBuf + 0); //类型

header.nDataSize = ShowU24(pBuf + 1); //标签body的长度,因为type占1个字节,所以数据区偏移1个字节

header.nTimeStamp = ShowU24(pBuf + 4); //时间戳 低24bit,前面加起来4个字节,因此偏移4个字节

header.nTSEx = ShowU8(pBuf + 7); //时间戳的拓展字段 高8bit

header.nStreamID = ShowU24(pBuf + 8); //流的id

header.nTotalTS = (uint32_t)((header.nTSEx << 24)) + header.nTimeStamp;

//标签头部解析结束

cout << "total ts: " << header.nTotalTS << endl;

cout << "nLeftLen:" << nLeftLen << ", nDataSize:" << header.nDataSize << endl;

if((header.nDataSize + 11) > nLeftLen) {

return NULL;

}

Tag *pTag;

switch (header.nType) {

case 0x09: //视频类型的tag

pTag = new CVideoTag(&header, pBuf, nLeftLen, this);

break;

case 0x08: //音频类型的tag

pTag = new CAudioTag(&header, pBuf, nLeftLen, this);

break;

case 0x12: //script tag

pTag = new CMetaDataTag(&header, pBuf, nLeftLen, this);

break;

default:

pTag = new Tag();

pTag->Init(&header, pBuf, nLeftLen);

break;

}

return pTag;

}

用二进制的意义何在?

。。。协议解析真麻烦,对脑子一点也不友好,为什么不直接用十进制呢?

硬件与计算机的本质

计算机底层以二进制运行:计算机底层数据处理、硬件编程以二进制。所有数据最终存储为 0 和 1,直接操作二进制是最高效的方式。

CPU 指令原生支持位操作:&、|、>> 等是 CPU 的基础指令,执行速度极快。

节省存储空间

紧凑编码:一个字节(8 位)可以存储多个字段(如音频配置的 SoundFormat+SoundRate+SoundSize+SoundType),而用十进制需要多个字节分开存储。

// 1 字节存储所有音频参数(二进制掩码)

uint8_t audioConfig = (soundFormat << 4) | (soundRate << 2) | (soundSize << 1) | soundType;

// 十进制方案需多个变量(占用更多空间)

uint8_t soundFormat, soundRate, soundSize, soundType; // 至少 4 字节

协议优化:在网络传输或文件存储中,减少 1 字节可能意味着海量数据下的显著带宽/存储节省。

性能优势

快速解析:

位操作直接通过硬件指令提取字段,比十进制字符串解析(如 atoi)快数十倍。

// 高效解析(位操作)

_nSoundFormat = (pd[0] & 0xF0) >> 4; // 1-2 条 CPU 指令

// 低效解析(十进制字符串)

char str[] = "10,3,1,0"; // 需要分割字符串并转换

sscanf(str, "%d,%d,%d,%d", &format, &rate, &size, &type); // 耗时

明确的位级控制

精准定义协议字段:

二进制掩码允许精确控制每个字段的位数和位置,避免歧义。

例如:SoundRate 严格占用 2 位,强制取值范围 0-3,而十进制可能意外传 4。

跨平台一致性:

二进制协议不依赖字符编码(如 ASCII/Unicode),适合异构系统通信。

历史与行业惯例

早期硬件限制:

旧系统(如嵌入式设备)资源有限,位操作是唯一可行方案。

协议继承性:

现有标准(如 FLV/RTMP/IP 协议)沿用二进制设计,保持兼容性。

十进制方案的缺陷

存储冗余:

十进制数字需要额外分隔符(如逗号),增加协议开销。

解析复杂度:

字符串处理需考虑分隔符、转义、字符集等问题,易出错。

性能瓶颈:

文本解析(如 JSON/XML)比二进制解析慢 10-100 倍,不适合高实时性场景(如视频流)。

二进制 vs 十进制协议

特性二进制掩码 + 位移十进制文本协议存储效率极高(1 字节存多字段)低(需分隔符和额外字节)解析速度纳秒级(CPU 原生支持)微秒-毫秒级(需字符串处理)代码复杂度低(直接位操作)高(需分词/类型转换)适用场景音视频流、网络协议、嵌入式系统配置文件、Web API

总结

计算机底层以二进制运行,位于或操作都是CPU原生指令,直接操作硬件,操作效率极快。一个字节(8 位)可以存储多个字段,比起十进制算是超级节省存储空间了,在一些高频网络超级大文件流传输过程中,比如音视频流,可以极大的节省带宽,二进制精确控制位级,一个位数一个意义,定好了就不容易出错,遵循一套协议的标准,可实现跨平台操作。

由此可见,二进制适合一些高性能、需求变更不频繁、跟系统、硬件打交道的编程场景。