长亭百川云 - 文章详情

详解 bytearray 对象的底层实现

古明地觉的编程教室

36

2024-07-13

楔子

前面我们介绍了 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")

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2