1.PyStringObject 与 PyString_Type
PyStringObject是对字符串对象的实现,是Python中的可变长度内存的对象,因此继承自PyVarObject。
PyStringObject的定义如下:
typedef struct{
PyObject_VAR_HEAD
long ob_shash; //字符串的hash值,未计算前初始为-1,该字段防止每次使用都要重复计算hash值,提高效率
int ob_sstate;//标记该对象是否经过intern机制处理
char ob_sval[1];//指向字符串在内存中的内存首地址,实际上保存了字符串的第一个字符,具体看下面的例子
} PyStringObject.
其中,PyObject_VAR_HEAD宏定义了PyVarObject的成员(ob_refcnt, ob_type, ob_size)
注意,ob_sval指向的内存大小为ob_size + 1个字节,最后一个多出的位置为'\0'标识字符串结尾。需要说明的是,和C语言不同,PyStringObject的字符串长度又ob_size指定,因此字符串内部可以有'\0'。
PyTypeObject PyString_Type = {
PyObject_HEAD_INIT(&PyType_Type)
0,
...,
sizeof(char). //指定每个元素的大小为一个char的大小
....
&string_as_number, //指定tp_as_number的操作
&string_as_sequence, //指定tp_as_sequence的操作
&string_as_mapping, //指定tp_as_mapping的操作
(hashfunc)string_hash, //指定hash函数
...
};
从PyString_Type的定义中可以看到,指定了tp_as_number, tp_as_sequence和tp_as_mapping操作,因此字符串支持数值、序列和映射操作。
2.Python创建PyStringObject对象
Python有两种方式创建PyStringObject对象,一种直接从C的字符串创建,另一种附加了一个参数size,表示截取前size个字符创建。这里只剖析第一种创建方式,第二种基本没有太大变化。
PyObject * PyString_FromString(const char* str)
{
register size_t size;
register PyStringObject *op;
size = strlen(str);
//判断字符串长度
if(size > PY_SSIZE_MAX)
{
return NULL; //如果字符串的长度超过python预定义的最大长度,则不创建
}
//处理null string
if(size == 0 && (op = nullstring) != null)
{
return (PyObject*) op;//如果为空串,且空串对象之前已经创建过,则直接返回空串对象
}
//处理字符
if(size == 1 && (op = characters[*str & UCHAR_MAX]) != null) return (PyObject*) op;
//创建新的PyStringObject对象,并初始化
op = (PyStringObject*) PyObject_MALLOC(sizeof(PyStringObject) + size);
PyObject_INIT_VAR(op, &PyString_Type, size); //这里,实际上对ob_type和ob_size进行赋值
op -> ob_shash = -1;
op -> ob_sstate = SSTATE_NOT_INTERNED;
memcpy(op -> ob_sval, str, size + 1);
...
return (PyObject*) op;
}
如我们利用“Python"创建PyStringObject对象,内存状态如下:
ob_refcnt | ob_type | ob_size | ob_shash | ob_sstate | ob_sval |
|
|
|
|
|
|
ref | type | 6 | -1 | 0 | P | y | t | h | o | n | \0 |
其中,灰色部分为PyStringObject的内存,白色部分为额外内存,注意,ob_sval实际上存储了字符串的第一个字符。
3.字符串对象的intern机制
PyStringObject对象的intern机制目的是:对于被intern之后的字符串,比如”Python“,在整个Python的运行期间,系统中都只有唯一的一个PyStringObject对象与字符串”Python“对应。
这样做有两个好处: 1. 节省空间 2.当比较两个PyStringObject对象是否相同时,如果它们都被intern了(可以通过ob_sstate判断),那么只需要简单检查它们对应的是不是同一个PyStringObject对象即可
需要注意的是,该机制并不能省去创建PystringObject对象的时间开销,因为在创建字符串对象时,是先创建临时对象,再判断临时对象在intern机制对应的表里是否存在,存在则返回表里对应的对象,否则返回新创建的对象。
4.字符缓冲池
和小整数缓冲池类似,python为一个字节的PyStringObject对象设计了一个缓冲池characters[UCHAR_MAX], 其中UCHAR_MAX默认被宏定义为0xff.
5.PyStringObject效率相关问题
Python中的字符串‘+’操作的效率是非常低下的,这是因为PyStringObject是不可变对象,每次进行叠加操作,都要重新申请内存,拷贝数据。
如果有大量的字符串需要拼接,首先应考虑字符串的join(list)操作,该操作一次性统计list中串的总长度,然后只会申请一次内存,拷贝所有数据进行填充,当拼接的串较多时,效率提升明显。