一、前言
本节主要讲解内容如下:
- 1.python序列类型
- 2.底层次数组
- 3.引用数组
- 4.python中的紧凑数组
- 5.动态数组
二、python的序列类型
python的各种 ’ 序列‘ 类,即内嵌的列表类(list)、元组类(tuple)和字符串类(str)。如何你熟悉python的这些类的操作,那么我们会发现这些类之间有明显的共性,最主要的是:每个类都支持用下标访问序列元素,比如使用语法seq[k];那么这些类的公共行为和内部运作机制是什么呢?
行为
列表、字符串和元组的使用看似简单,然而在理解与这些类的相关行为上,却又一些重要的细节(比如说复制序列意味着什么,或者取序列的一部分又意味着什么。)对类的行为有误解会导致很严重的错误,所以,我们要为这些类提供一些准确的模型。这些模型会帮助我们研究更高级的用法,比如使用多维数据集合表示列表的列表。
三、底层次数组
数组:一组相关变量能够一个接一个地存储在计算机存储器的一块连续的区域内。我们将这样的表示法称之为数组。
例如,一个文本字符串是以一列有序字符的形式存储的。在python中,每个字符都用Unicode字符集表示,对于大多数计算机系统,python内部用16位表示每个Unicode字符(即2个字节)。因此,一个5个字符的字符串,比如’HELLO’,将会被存储在连续的10个字节中,如下图(图有点丑,哈哈哈)。
虽然该字符串需要12个字节的存储空间,但我们仍然把它描述为6字符数组。我们会将数组中的每个位置称为单元,并非整数索引值描述该数组,其中单元的开始编号为0、1、2等。例如,在上图中,索引为4的数组单元的内容为E,并且存储在存储器的2009和2010字节中。
注:数组的每个单元必须占据相同数量的字节。这样的好处是,使用索引值能够在常量时间内访问数组内的任一单元。例如,知道数组的起始地址2001,每个元素所占字节数2,那么’E’的内存地址是2001+2*4=2009,当然,在数组内计算内存地址的算法是自动处理的。
四、引用数组
在举一个好的例子:假设想要为某医院开发一套医疗信息系统,来记录当前分配到病床的病人的名字。假定医院有200张床,为方便起见,这些床编号为0-199。我们可以考虑使用基于数组的数据结构来记录最近分配到这些病床上病人的名字。
但是如果使用数组表示这样的列表,必须要满足数组的每个单元字节数都相同的这一条件。然而,元素是字符串,他们串的长度显然不同。python可以使用最长字符串来为每个单元预留足够的空间,但这样会比较浪费内存空间。那么,我们怎么解决这个问题呢?
python使用数组内部存储机制(即对象引用,来表示一列或者一组元素序列。在最低层,存储的是一块连续的内存地址序列,这些地址指向一组元素序列。),如下图
虽然单个元素的相对大小可能不同,但是每个元素存储地址的位数是固定的。在这种方式下,python可以通过索引值以常量时间访问元素列表或元组。
注:一个列表实例可能会以多个指向同一对象的引用作为列表元素,一个对象也可能被两个或更多列表中的元素所指向。如下图:
我们可以看到,其实两个列表是共享元素了的。那么,修改一个列表,会影响到另一个列表吗?
对于不可变元素:不会影响,因为一旦修改一个不可变元素,就相当于指向了一个新的元素。
对于可变元素:会影响,因为修改一个可变元素,它的内存地址不变,还是原来的元素。
a = [3,4,5]
t1 = [1,a]
t2 = [2,a]
print(t1)
print(t2)
t1[1][0]=11
print(t1)
print(t2)
#打印结果:
#[1, [3, 4, 5]]
#[2, [3, 4, 5]]
#[1, [11, 4, 5]]
#[2, [11, 4, 5]]
我们发现,这个是不是和python中的深浅拷贝很相似呢?
当我们通过复制创建一个新的列表时也是同样的情况:比如list3 = list(list2),这样就会对原列表复制出一张新列表。这张新列表即为浅拷贝,该列表和原列表指向同样的元素。当这些元素是不可变时,浅拷贝就会生成两张互不影响的列表了,如果是可变对象,那么就需要deepcopy来深拷贝了。
五、python中的紧凑数组
紧凑数组–就是数组存储的是原始数据,而不是元素的引用。
像字符串就是采用的这种结构。
紧凑结构的优点:
- 1.使用紧凑结构会占用更少的内存
- 2.高性能计算
六、动态数组
由于系统可能会占用相邻的内存位置去存储其他数据,因此数组大小不能靠扩展内存单元来无限增加。在表示python元组或者字符串实例的情形中,这种限制就没什么问题了。由于这些类的实例变量都是不可变的,因此当对象实例化时,底层数组的大小就已经确定了。
python的列表类提供了更有趣的抽象。虽然列表在被构造时已经有了确定的长度,但该类可以进行增添操作,对列表的大小没有明显的限制。为了实现这种抽象,我们就需要用到动态数组这种算法技巧。
假如用户持续增添列表元素,所有的预留单元被耗尽。此时,列表类向系统请求一个新的、更大的数组,并初始化该数组,使其前面的部分能与原来的小数组一样。届时,原来的数组不再需要,因此被系统回收。实验证明,如下:
import sys
data = []
for k in range(30):
a = len(data)
b = sys.getsizeof(data)
print('Length:{0:3d};Size in bytes:{1:4d}'.format(a,b))
data.append(None)
#实验结果如下:
# Length: 2;Size in bytes: 96
# Length: 3;Size in bytes: 96
# Length: 4;Size in bytes: 96
# Length: 5;Size in bytes: 128
# Length: 6;Size in bytes: 128
# Length: 7;Size in bytes: 128
# Length: 8;Size in bytes: 128
# Length: 9;Size in bytes: 192
# Length: 10;Size in bytes: 192
# Length: 11;Size in bytes: 192
# Length: 12;Size in bytes: 192
# Length: 13;Size in bytes: 192
# Length: 14;Size in bytes: 192
# Length: 15;Size in bytes: 192
# Length: 16;Size in bytes: 192
# Length: 17;Size in bytes: 264
# Length: 18;Size in bytes: 264
# Length: 19;Size in bytes: 264
# Length: 20;Size in bytes: 264
# Length: 21;Size in bytes: 264
# Length: 22;Size in bytes: 264
# Length: 23;Size in bytes: 264
# Length: 24;Size in bytes: 264
# Length: 25;Size in bytes: 264
# Length: 26;Size in bytes: 344
# Length: 27;Size in bytes: 344
# Length: 28;Size in bytes: 344
# Length: 29;Size in bytes: 344
参考书籍:《数据结构与算法》