字节序的大小端模式-字节与基础类型的转换

  1. 字节序 与 字节对齐

  2. java基础类型与字节类型转换

    • 2.1 struct ==》 字节类型

    • 2.2 基础类型 ==》 字节类型

    • 2.3 字节类型 ==》 基础类型

  3. python使用ctypes调用c处理字节


1.1 字节序

  • 大端字节序(Big-Endian):高位字节在内存的低地址,低位字节在内存的高地址,这是人类读写数值的方法。

  • 小端字节序(Little-Endian):低位字节在内存的低地址,高位字节在内存的高地址,计算机的内部处理大部分是小端字节序。计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。

1
2
3
4
5
6
7
8
9
10
11

示例: 0x01234567

大端模式:
地址: 0x0001 0x0002 0x0003 0x0004
数据: 01 23 45 67

小端模式:
地址: 0x0001 0x0002 0x0003 0x0004
数据: 67 45 23 01

在C语言中,默认是小端(但在一些对于单片机的实现中却是基于大端,比如Keil 51C),Java是平台无关的,默认是大端。在网络上传输数据普遍采用的都是大端。

1.2 字节对齐

对于char型数据,其自身对齐值为1,对于short型为2,对于int,float类型,其自身对齐值为4,对于double型,其自身对齐值为8,单位字节。

需要字节对齐的根本原因在于CPU访问数据的效率问题.

gcc中结构体默认是4个字节对齐,即为32的倍数

先看个简单的例子(32位,X86处理器,GCC编译器):

1
2
3
4
5
6
7
8
9
10
11
struct A{
int a;
char b;
short c;
};
struct B{
char b;
int a;
short c;
};

结果是:sizeof(strcut A)值为8;sizeof(struct B)的值却是12。

结构体A中包含一个4字节的int数据,一个1字节char数据和一个2字节short数据;B也一样。按理说A和B大小应该都是7字节。之所以出现上述结果,就是因为编译器要对数据成员在空间上进行对齐。

对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:

  • 数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。

  • 联合 :按其包含的长度最大的数据类型对齐。

  • 结构体: 结构体中每个数据类型都要对齐。

__attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。
如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。

__attribute__ ((packed)),取消结构在编译过程中的优化对齐,
按照实际占用字节数进行对齐。

2.1 java 使用struct的ByteBuffer转换为字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  public class DemoStruct extends Struct {

Unsigned8 a = new Unsigned8();
Unsigned16 b = new Unsigned16();
Unsigned32 c = new Unsigned32();
UTF8String d = new UTF8String(12);

/**
* 取消优化对齐
*/
@Override
public boolean isPacked() {
return true;
}

/**
* 设置为小端数据
*/
@Override
public ByteOrder byteOrder() {
return ByteOrder.LITTLE_ENDIAN;
}

public byte[] getBytes(){
this.isPacked();
ByteBuffer buff = this.getByteBuffer();
buff.order(this.byteOrder());
byte[] bytes = new byte[buff.capacity()];
IntStream.range(0, buff.capacity()).forEach(i -> bytes[i] = buff.get(i));
// Base64.getEncoder().encodeToString(bytes)
return bytes;
}
}


- 这里使用struct的无符号类型(unsigned),然后用ByteBuffer获取对应位置的字节,存储到byte[] 中。
- 但对于需要动态生成struct的地方不太适合,于是使用下面的方法依次获取数据的字节数组,然后按照顺序连接为一个byte[]。

2.2 基础类型 转换 byte[]

  • String 转换byte[],可以使用String的getBytes()
1
2
3
4
5
6
public static byte[] getStringBytes(String data,int length){
byte[] bytes = new byte[length];
byte[] srcByte = data.getBytes();
byteCopy(srcByte,bytes);
return bytes;
}
  • 对于其他没有getBytes()方法的基本数据类型,例如:大端序 int转换byte[]
1
2
3
4
5
6
7
public static byte[] getIntBytes(int data) {
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.putInt(data);
byte[] bytes = buffer.array();
return bytes;
}
  • java文档中java.nio.ByteBuffer 有putInt,putChar,putShort,putFloat,putDouble,putLong等方法,但在使用过程中遇到问题:

对于定义的unsigned32的数据,需要获取4个字节的byte[]. unsigned32最大值(2^32)-1 =4294967295大于int的取值范围(-(2^31 ),(2^31 )-1),于是转换为Long使用putLong.但putLong默认写入8个byte. 当ByteBuffer.allocate(4),再putLong时报错 buffer overflow exception。

1
2
3
4
5
6
7
8
9
10
11
12
public abstract ByteBuffer putLong(long value)
Relative put method for writing a long value (optional operation).
Writes eight bytes containing the given long value, in the current byte order,
into this buffer at the current position, and then increments the position by eight.

Parameters:
value - The long value to be written
Returns:
This buffer
Throws:
BufferOverflowException - If there are fewer than eight bytes remaining in this buffer
ReadOnlyBufferException - If this buffer is read-only

于是采用二进制移位运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 小端序
//内存的低地址 存储 数据的低位字节
// 则bytes[0] 存储 data & (1111 1111)
// bytes[1] 存储 data 右移8位 再 &1111 1111
public static byte[] getLongBytesBig(long data,int length) {
// 设置length=4
byte[] bytes = new byte[length];

for (int i = 0; i < length; i++) {
bytes[i] = (byte) ((data >> (i*8)) & 0xff);
}
return bytes;
}

//大端序
public static byte[] getLongBytesLittle(long data,int length) {
// 设置length=4
byte[] bytes = new byte[length];

for (int i = 0; i < length; i++) {
bytes[i] = (byte) ((data >> ((length-1-i)*8)) & 0xff);
}
return bytes;
}

2.3 字节流 转换java 基本数据类型

2.3.1 使用ByteBuffer自带的方法

1
2
3
4
5
6
7
public static int getInt(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.put(bytes);
int result = buffer.getInt(0);
return result;
}

2.3.2 移位运算

1
2
3
4
5
6
7
public static int getInt(byte[] bytes) {
int result = (int) ((0xff & bytes[0])
| ((0xff & bytes[1]) << 8)
| ((0xff & bytes[2]) << 16)
| ((0xff & bytes[3]) << 24));
return result;
}

  1. python 使用ctypes 调用c 处理字节
  • demodata.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

struct __attribute__((packed)) DemoData
{
uint8_t a;
uint16_t b;
uint32_t c;
char d[12];

char * get_data(int int_param[3],char * str_param[1])
{
char * data_result;
struct DemoData *t = (struct DemoData*)malloc(sizeof(struct DemoData));
bzero(t, sizeof(struct DemoData));

//init
t->a = int_param[0];
t->b = int_param[1];
t->c = int_param[2];
strncpy(t->d, str_param[0],12);
unsigned char bindata[4096];
char base64[4096];
memcpy(bindata, t, sizeof(struct DemoData));
return bindata;
}

void freeme(char *ptr)
{
free(ptr);
}
};

在linux上编译,gcc -shared -Wl,-soname,demodata -o demodata.so -fPIC demodata.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# coding=utf-8

from ctypes import *
demo = CDLL('./demodata.so')

# 获取base64
def get_data(demo):
'''
int_param:
0 a
1 b
2 c

str_param:
0 d 12
'''
INT_PARAMS = c_int*3
STR_PARAMS = c_char_p*1
int_param = INT_PARAMS(1,2,3)
str_param = STR_PARAMS("demo")
demo.freeme.argtypes = c_void_p,
demo.freeme.restype = None
demo.get_data.restype = c_char_p
result = demo.get_data(int_param,str_param)
print result
return result