楔子
前面我们介绍了 bytes 对象,本篇文章来聊一聊 bytearray 对象,这两者都表示字节串或者字节序列,它们的创建方式和支持的操作也是类似的。
# 基于列表创建,里面的元素为 0 ~ 255 的整数
b1 = bytes([97, 98, 99])
b2 = bytearray([97, 98, 99])
print(b1)
print(b2)
"""
b'abc'
bytearray(b'abc')
"""
# 基于字符串创建
b1 = bytes("hello", encoding='utf-8')
b2 = bytearray("hello", encoding='utf-8')
print(b1)
print(b2)
"""
b'hello'
bytearray(b'hello')
"""
b1 = bytes.fromhex("61626364")
b2 = bytearray.fromhex("61626364")
print(b1)
print(b2)
"""
b'abcd'
bytearray(b'abcd')
"""
但区别在于 bytes 对象是不可变对象,bytearray 对象是可变对象。
# 也可以直接基于 bytes 对象创建 bytearray 对象
# 反过来也是如此
b = bytearray(b"satori")
print(b) # bytearray(b'satori')
# 既然是可变对象,那么就意味着可以本地修改内部元素
# 由于字节序列内部的每个元素都是 0 ~ 255 的整数
# 因此这里修改时,也必须赋值整数
b[0] = ord("S")
print(b) # bytearray(b'Satori')
# 当然,如果基于切片修改,那么需要赋值一个 bytes 对象
b[0: 2] = b"SA"
print(b) # bytearray(b'SAtori')
# 当然也可以赋值一个 bytearray 对象
# 它和 bytes 对象本质一样,无非是 bytes 对象不能本地修改
b[0: 3] = bytearray(b"saT")
print(b) # bytearray(b'saTori')
# 在尾部追加字节
b.append(ord(" "))
b.append(ord("h"))
b.append(ord("e"))
b.append(ord("l"))
b.append(ord("l"))
b.append(ord("o"))
print(b) # bytearray(b'saTori hello')
还是那句话,如果不需要对字节序列做修改的话,那么 bytes 对象和 bytearray 对象是等价的,我们使用 bytes 对象即可。但若是希望字节序列可变,那么只能使用 bytearray 对象。
下面我们来分析一下 bytearray 对象的底层实现。
bytearray 对象的底层实现
bytearray 对象在底层由 PyByteArrayObject 结构体表示,定义如下。
// Include/cpython/bytearrayobject.h
typedef struct {
PyObject_VAR_HEAD
Py_ssize_t ob_alloc;
char *ob_bytes;
char *ob_start;
Py_ssize_t ob_exports;
} PyByteArrayObject;
解释一下每个字段的含义:
PyObject_VAR_HEAD:变长对象的公共头部,包含引用计数、类型和长度。
ob_alloc:底层缓冲区的长度,即最多能容纳多少个字节。注意它和 ob_size 的区别,ob_size 表示 bytearray 对象的长度,也就是缓冲区当前已经容纳了多少个字节。如果 append 的时候发现 ob_size 达到了 ob_alloc,那么要对缓冲区进行扩容。
ob_bytes:指向缓冲区的指针,缓冲区是一个连续的内存块,用于存储字节序列的所有数据。
ob_start:ob_bytes 指向缓冲区的物理起始位置,而 ob_start 指向缓冲区的逻辑起始位置。说白了 ob_start 可以指向缓冲区的任意位置,允许字节序列使用部分缓冲区,比如通过切片 [1:] 进行截取,那么 ob_start 便指向缓冲区的第二个元素(逻辑起始位置),而无需重新分配内存。
ob_exports:缓冲区被外部对象引用的次数。
下面我们来创建几个 bytearray 对象,并通过画图来描述对应的底层结构。
b = bytearray(b"")
每个结构体字段都是 8 字节,所以一个空 bytearray 对象的大小是 56 字节。
>>> b = bytearray()
>>> b
bytearray(b'')
>>> b.__sizeof__()
56
结论和我们分析的一样。
b = bytearray(b"abc")