百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

Cpython源码阅读16-Unicode字符串底层存储结构

off999 2024-11-06 11:28 35 浏览 0 评论

底层有一个函数叫Py_Unicode_New的函数,为Unicode字符串申请空间,借助这个函数看一下unicode字符串的内存分布情况。像这样的特性API很多。


PyObject *

PyUnicode_New(Py_ssize_t size, Py_UCS4 maxchar)//这个函数负责为Unicode字符串申请空间

{/* 传入两个参数,size表示字符的个数,多出来一个maxchar;

最大字符,最大字符不同,申请的空间不同*/

/* 对空字符串的优化 */

if (size == 0) {

return unicode_new_empty();//字符个数为0,则不用申请空间

}

PyObject *obj; //所有对象的父类

PyCompactUnicodeObject *unicode;//字符型对象

void *data;//空类型,需要时将数据转化为实际存储的类型

enum PyUnicode_Kind kind;//字符实际编码使用的类型

int is_sharing, is_ascii;

Py_ssize_t char_size; //实际编码需要几个字符

Py_ssize_t struct_size;//PyASCIIObject基本类型结构体的大小


is_ascii = 0;

is_sharing = 0;

struct_size = sizeof(PyCompactUnicodeObject);

//如果maxchar不小于128,基本类型为PyCompactUnicodeObject

//最大字符小于128,并且字符位宽为1个字节,即标准的ASCII可识别的有效字符仅有128个

//此时创建PyASCIIObject对象,字符实际编码使用的类型为PyUnicode_1BYTE_KIND()

if (maxchar < 128) {

kind = PyUnicode_1BYTE_KIND;

char_size = 1;

is_ascii = 1;

struct_size = sizeof(PyASCIIObject);

}

//最大字符大于128小于256,并且字符位宽为1个字节,即标准的ASCII可识别的有效字符仅有128个

//此时创建PyCompactUnicodeObject对象,字符实际编码使用的类型为PyUnicode_1BYTE_KIND()

else if (maxchar < 256) {

kind = PyUnicode_1BYTE_KIND;

char_size = 1;

}

//最大字符大于256小于65536,并且字符位宽为2个字节

//此时创建PyCompactUnicodeObject对象,字符实际编码使用的类型为PyUnicode_2BYTE_KIND()

else if (maxchar < 65536) {

kind = PyUnicode_2BYTE_KIND;

char_size = 2;

if (sizeof(wchar_t) == 2)

is_sharing = 1;

}

//最大字符大于65536,并且字符位宽为4个字节

//此时创建PyCompactUnicodeObject对象,字符实际编码使用的类型为PyUnicode_4BYTE_KIND()

else {

if (maxchar > MAX_UNICODE) {

PyErr_SetString(PyExc_SystemError,

"invalid maximum character passed to PyUnicode_New");

return NULL;

}

kind = PyUnicode_4BYTE_KIND;

char_size = 4;

if (sizeof(wchar_t) == 4)

is_sharing = 1;

}


/*确保字符个数没有溢出. */

if (size < 0) {

PyErr_SetString(PyExc_SystemError,

"Negative size passed to PyUnicode_New");

return NULL;

}

if (size > ((PY_SSIZE_T_MAX - struct_size) / char_size - 1))

return PyErr_NoMemory();


/* 来自_PyObject_New()的重复分配代码,而不是对PyObject_New()的调用,

因此我们能够为对象及其数据缓冲区分配空间。

*/

obj = (PyObject *) PyObject_MALLOC(struct_size + (size + 1) * char_size);

if (obj == NULL) {

return PyErr_NoMemory();

}

//绑定PyUnicode_Type的类型信息

_PyObject_Init(obj, &PyUnicode_Type);

//根据使用的不同结构体头,通过指针偏移找到实际字符串data的开始位置

unicode = (PyCompactUnicodeObject *)obj;

if (is_ascii)

data = ((PyASCIIObject*)obj) + 1;

else

data = unicode + 1;

//设定state内部类的状态信息

_PyUnicode_LENGTH(unicode) = size;

_PyUnicode_HASH(unicode) = -1;

_PyUnicode_STATE(unicode).interned = 0;

_PyUnicode_STATE(unicode).kind = kind;

_PyUnicode_STATE(unicode).compact = 1;

_PyUnicode_STATE(unicode).ready = 1;

_PyUnicode_STATE(unicode).ascii = is_ascii;

//实际data使用的编码字节

if (is_ascii) {

((char*)data)[size] = 0;

_PyUnicode_WSTR(unicode) = NULL;

}

//一个字节

else if (kind == PyUnicode_1BYTE_KIND) {

((char*)data)[size] = 0;

_PyUnicode_WSTR(unicode) = NULL;

_PyUnicode_WSTR_LENGTH(unicode) = 0;

unicode->utf8 = NULL;

unicode->utf8_length = 0;

}

//两个字节

else {

unicode->utf8 = NULL;

unicode->utf8_length = 0;

if (kind == PyUnicode_2BYTE_KIND)

((Py_UCS2*)data)[size] = 0;

//四个字节

else /* kind == PyUnicode_4BYTE_KIND */

((Py_UCS4*)data)[size] = 0;

if (is_sharing) {

_PyUnicode_WSTR_LENGTH(unicode) = size;

_PyUnicode_WSTR(unicode) = (wchar_t *)data;

}

else {

_PyUnicode_WSTR_LENGTH(unicode) = 0;

_PyUnicode_WSTR(unicode) = NULL;

}

}

#ifdef Py_DEBUG

unicode_fill_invalid((PyObject*)unicode, 0);

#endif

assert(_PyUnicode_CheckConsistency((PyObject*)unicode, 0));

return obj;

}

这个函数通过maxchar的不同创建了两个字符串对象,PyASCIIObject和PyCompactUnicodeObject,通过源码可以发现,PyCompactUnicodeObject是继承PyASCIIObject的。整个函数流程为:maxchar小于128,并且字符位宽为1个字节,创建PyASCIIObject对象;maxchar小于256,并且字符位宽为1个字节,创建PyCompactUnicodeObject对象。maxchar小于65536,并且字符位宽为2个字节,创建PyCompactUnicodeObject对象;码位个数大于65536且小于MAX_UNICODE,,创建PyCompactUnicodeObject对象。通过这句代码obj = (PyObject *) PyObject_MALLOC(struct_size + (size + 1) * char_size);实际分配了内存。调用PyObject_INIT(obj, &PyUnicode_Type)函数来将PyUnicode_Type实例绑定到字符串对象的头部。就赋予了字符串对象实际类型和属性。

根据不同的maxchar对应不同的kind


根据不同的kind对应不同的底层结构体,和字符存储单元


typedef struct {

PyObject_HEAD//不可变长对象公用头

Py_ssize_t length; //字符串长度

Py_hash_t hash; /* 字符串哈希值 */

struct {

unsigned int interned:2;//是否interned机制开启

unsigned int kind:3; //字符类型,根据maxchar区分

unsigned int compact:1;//是否紧凑,实际数据域对象头是否分离

unsigned int ascii:1;//是否为纯ASCII

unsigned int ready:1;//针对传统字符串,使用ready函数复制到data块中

unsigned int :24;//保留字段

} state;//Unicode对象标志位

wchar_t *wstr; /* wchar_t representation (null-terminated) */

} PyASCIIObject;

PyASCIIObject内存结构,是纯ASCII字符串通过PyUnicode_New函数申请的。申请时state.ascii和state.compact赋值为1,数据紧跟在头部结构后面,叫做紧凑型。

我们以纯ASCII字符串“hua”,看一下它的底层数据结构


从这个结构可以看出,Unicode字符串应该是可以变长的,但是实际没有使用可变长的PyVarObject头,而是使用了不可变长PyObject头,又增加了length这个字段。PyObject_VAR_HEAD用于描述每个元素大小都一样的变长对象,元素的大小由ob_size字段描述;而Unicode字符串对象,每个字符到底用多大的存储单元,与字符范围(maxchar)决定,底层做了特殊处理。

typedef struct {

PyASCIIObject _base;

Py_ssize_t utf8_length; /* Number of bytes in utf8, excluding thterminating \0. */

char *utf8; /* UTF-8 representation (null-terminated) */

Py_ssize_t wstr_length; /* Number of code points in wstr, possible

* surrogates count as two code points. */

} PyCompactUnicodeObject;

当maxchar大于128小于256 时,虽然一个字节可以存储,此时为非ASCII,Unicode字符串对象由PyCompactUnicodeObject结构体保存。但是它的state中的字段ascii为0,举例图示一下“xiaohuaê”


当maxchar大于256小于65536 时,Unicode字符串对象由PyCompactUnicodeObject结构体保存。但是它的state中的字段ascii为0,state中的字段kind为2举例图示一下“xiaohua”


当maxchar大于65536小于2^32 时,Unicode字符串对象由PyCompactUnicodeObject结构体保存。但是它的state中的字段ascii为0,state中的字段kind为4,举例图示一下“xiaohua+表情符号”



相关推荐

推荐一款Python的GUI可视化工具(python 可视化工具)

在Python基础语法学习完成后,进一步开发应用界面时,就需要涉及到GUI了,GUI全称是图形用户界面(GraphicalUserInterface,又称图形用户接口),采用图形方式显示的计算机操...

教你用Python绘制谷歌浏览器的3种图标

前两天在浏览matplotlib官方网站时,笔者无意中看到一个挺有意思的图片,就是用matplotlib制作的火狐浏览器的logo,也就是下面这个东东(网页地址是https://matplotlib....

小白学Python笔记:第二章 Python安装

Windows操作系统的python安装:Python提供Windows、Linux/UNIX、macOS及其他操作系统的安装包版本,结合自己的使用情况,此处仅记录windows操作系统的python...

Python程序开发之简单小程序实例(9)利用Canvas绘制图形和文字

Python程序开发之简单小程序实例(9)利用Canvas绘制图形和文字一、项目功能利用Tkinter组件中的Canvas绘制图形和文字。二、项目分析要在窗体中绘制图形和文字,需先导入Tkinter组...

一文吃透Python虚拟环境(python虚拟环境安装和配置)

摘要在Python开发中,虚拟环境是一种重要的工具,用于隔离不同项目的依赖关系和环境配置。本文将基于windows平台介绍四种常用的Python虚拟环境创建工具:venv、virtualenv、pip...

小白也可以玩的Python爬虫库,收藏一下

最近,微软开源了一个项目叫「playwright-python」,作为一个兴起项目,出现后受到了大家热烈的欢迎,那它到底是什么样的存在呢?今天为你介绍一下这个传说中的小白神器。Playwright是...

python环境安装+配置教程(python安装后怎么配置环境变量)

安装python双击以下软件:弹出一下窗口需选择一些特定的选项默认选项不需要更改,点击next勾选以上选项,点击install进度条安装完毕即可。到以下界面,证明安装成功。接下来安装库文件返回电脑桌面...

colorama,一个超好用的 Python 库!

大家好,今天为大家分享一个超好用的Python库-colorama。Github地址:https://github.com/tartley/coloramaPythoncolorama库是一...

python制作仪表盘图(python绘制仪表盘)

今天教大家用pyecharts画仪表盘仪表盘(Gauge)是一种拟物化的图表,刻度表示度量,指针表示维度,指针角度表示数值。仪表盘图表就像汽车的速度表一样,有一个圆形的表盘及相应的刻度,有一个指针...

总结90条写Python程序的建议(python写作)

  1.首先  建议1、理解Pythonic概念—-详见Python中的《Python之禅》  建议2、编写Pythonic代码  (1)避免不规范代码,比如只用大小写区分变量、使用容易...

[oeasy]python0137_相加运算_python之禅_import_this_显式转化

变量类型相加运算回忆上次内容上次讲了是从键盘输入变量input函数可以有提示字符串需要有具体的变量接收输入的字符串输入单个变量没有问题但是输入两个变量之后一相加就非常离谱添加图片注释,不超过1...

Python入门学习记录之一:变量(python中变量的规则)

写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...

掌握Python的&quot;魔法&quot;:特殊方法与属性完全指南

在Python的世界里,以双下划线开头和结尾的"魔法成员"(如__init__、__str__)是面向对象编程的核心。它们赋予开发者定制类行为的超能力,让自定义对象像内置类型一样优雅工...

11个Python技巧 不Pythonic 实用大于纯粹

虽然Python有一套强大的设计哲学(体现在“Python之禅”中),但总有一些情况需要我们“打破规则”来解决特定问题。这触及了Python哲学中一个非常核心的理念:“实用主义胜于纯粹主义”...

Python 从入门到精通 第三课 诗意的Python之禅

导言:Python之禅,英文名是TheZenOfPython。最早由TimPeters在Python邮件列表中发表,它包含了影响Python编程语言设计的20条软件编写原则。它作为复活节彩蛋...

取消回复欢迎 发表评论: