CPython-内置string类型
2021-06-03 11:01
typedef struct { PyObject_VAR_HEAD long ob_shash; // 字符串hash值(-1表示还未计算) int ob_sstate; // interned状态(0表示未被intern) char ob_sval[1]; // 字符数组(ob_size+1,包含结束符) } PyStringObject;
其中PyObject_VAR_HEAD是可变对象的基础数据部分,其中有ob_size表示可变对象的长度;
ob_sval是字符数组,用来存在字符串,以‘\0‘结尾,所以它的长度是ob_size+1;
ob_shash是字符串的hash值,因为字符串经常会被作为字典(dict)的键(key),不管是脚本中还是Python虚拟机中,使用一个字段来保存hash避免每次都需要重新计算(注意字符串对象是不可变对象);
ob_sstate用来保存intern状态,后面会讲到。
[Python] >>> s1 = ‘‘ >>> sys.getsizeof(s1) 29 # 基础大小(PyStringObject_SIZE) >>> s2 = ‘abc‘ >>> sys.getsizeof(s2) 32
PyString_Type
PyTypeObject PyString_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "str", PyStringObject_SIZE, sizeof(char), string_dealloc, /* tp_dealloc */ (printfunc)string_print, /* tp_print */ (hashfunc)string_hash, /* tp_hash */ string_methods, /* tp_methods */ ... };
PyObjectType中有计算字符串hash值的string_hash函数,有兴趣的可以查看string_hash函数的定义;
以及string对象特有的方法string_methods,主要包括:join、split、rsplit、lower、upper、find、index、replace等。
对于Python对象,通常需要关注对象创建、对象销毁、对象操作几部分。
string对象创建
创建string对象需要关注string对象内存分配、字符数组分配和赋值几个部分。可以直接看string对象创建函数:
PyObject * PyString_FromString(const char *str) { register size_t size; register PyStringObject *op; assert(str != NULL); size = strlen(str); // (1)保证字符串不超长 if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) { PyErr_SetString(PyExc_OverflowError, "string is too long for a Python string"); return NULL; } // (2)空字符串nullstring if (size == 0 && (op = nullstring) != NULL) { Py_INCREF(op); return (PyObject *)op; } // (3)单字符串尝试从characters中获取 if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) { Py_INCREF(op); return (PyObject *)op; } // (4)分配PyStringObject对象内存 op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size); if (op == NULL) return PyErr_NoMemory(); (void)PyObject_INIT_VAR(op, &PyString_Type, size); op->ob_shash = -1; op->ob_sstate = SSTATE_NOT_INTERNED; // (5)将字符串复制到对象内部的字符数组中 Py_MEMCPY(op->ob_sval, str, size+1); if (size == 0) { // (6)长度为0的字符串进行intern操作 PyObject *t = (PyObject *)op; PyString_InternInPlace(&t); op = (PyStringObject *)t; nullstring = op; Py_INCREF(op); } else if (size == 1) { // (7)长度为1的字符串进行intern操作 PyObject *t = (PyObject *)op; PyString_InternInPlace(&t); op = (PyStringObject *)t; characters[*str & UCHAR_MAX] = op; Py_INCREF(op); } return (PyObject *) op; }
(1)保证字符串不超长。PY_SSIZE_T_MAX在不同设备和系统上值不同,在我的测试设备上(Windows 10)值是2147483647B(约2GB),应该几乎不可能会超值这个最大限制。
(2)空字符串nullstring。所有的空字符串会共享nullstring静态对象(会增加引用计数)。
(3)单字符串尝试从characters中获取(会增加引用计数)。characters是单字符串缓冲机制,是256个单字符串对象数组(UCHAR_MAX + 1),对应于ascii字符集。
static PyStringObject *characters[UCHAR_MAX + 1];
(4)分配PyStringObject对象内存。为string对象分配内存,大小为基础大小(PyStringObject_SIZE) + 字符串长度(size)。
(5)将字符串复制到对象内部的字符数组中。
(6)长度为0的字符串进行intern操作,首次创建nullstring对象,会进行intern操作。
(7)长度为1的字符串进行intern操作,首次创建该单字符串对象,会进行intern操作。
intern操作是实现string对象驻留机制,用于对string对象的共享。
intern机制
字符串对象驻留机制,即共享机制。通过驻留机制能够避免相同字符串对象的重复创建,仅通过增加引用计数来取用已存在的相同字符串对象,这一切是通过叫interned字典来实现。
先通过Python脚本来观察intern机制的效果,然后再来看intern机制源代码。
string对象销毁
TODO