- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议27:连接字符串应优先使用 join 而不是 +
字符串处理在大多数编程程序语言中都不可避免,字符串的连接也是在编程过程中经常需要面对的问题。Python中的字符串与其他一些程序语言如C++、Java有一些不同,它为不可变对象,一旦创建便不能改变,它的这个特性直接影响到Python中字符串连接的效率。我们首先来看常见的两种字符串连接方法。
1)使用操作符+连接字符串的方法如下:
>>> str1,str2,str3 = 'testing ','string ','concatenation ' >>> str1+str2+str3 'testing string concatenation ' >>>
2)使用join方法连接字符串的方法如下:
>>> str1,str2,str3 = 'testing ','string ','concatenation ' >>> ''.join([str1,str2,str3]) 'testing string concatenation ' >>>
思考这么一个问题:上述两种字符串连接的方法除了使用形式上的不同还有其他区别吗?性能上会不会有所差异呢?来看下面这个测试例子:
import timeit # 生成测试所需要的字符数组 strlist=["it is a long value string will not keep in memory" for n in range(100000)] #100000 为字符串连接的数目,下面对应的测试数据,每次需要修改 def join_test(): return ''.join(strlist) # 使用join 方法连接strlist 中的元素并返回字符串 def plus_test(): result = '' for i,v in enumerate(strlist): result= result+ v # 使用+ 进行字符串连接 return result if __name__ == '__main__': jointimer = timeit.Timer("join_test()","from __main__ import join_test") print jointimer.timeit(number = 100) plustimer = timeit.Timer("plus_test()","from __main__ import plus_test") print plustimer.timeit(number = 100)
给上面的程序传入一组测试参数(测试参数为3,10,100,1000,10000,100000;分别表示每一次测试所要连接的字符串的数量),程序用于测试join_test()和plus_test()这两个方法在字符串连接规模改变时所消耗时间的变化。测试结果记录如表3-1所示。
表3-1 join_test()和plus_test()连接字符串所耗时间记录
表3-1可以用图3-3所示的X-Y图表示,其中X轴表示所要连接的字符串的数量,Y轴表示消耗的时间。
图3-3 join_test()和plus_test()耗时比较图(运行时间扩大10000倍)
从分析测试结果图表我们不难发现:分别使用join()方法和使用+操作符来连接字符串,join()方法的效率要高于+操作符,特别是字符串规模较大的时候,join()方法的优势更为明显(如连接数为100000的时候,两者耗时相差上百倍)。造成这种差别的原因在哪里呢?我们来探讨一下。当用操作符+连接字符串的时候,由于字符串是不可变对象,其工作原理实际上是这样的:如果要连接如下字符串:S1+S2+S3+.......+SN,执行一次+操作便会在内存中申请一块新的内存空间,并将上一次操作的结果和本次操作的右操作数复制到新申请的内存空间,即当执行S1+S2的时候会申请一块内存,并将S1、S2复制到该内存中,依次类推,如图3-4所示。因此,在N个字符串连接的过程中,会产生N-1个中间结果,每产生一个中间结果都需要申请和复制一次内存,总共需要申请N-1次内存,从而严重影响了执行效率。N越大,对内存的申请和复制的次数越多,+操作符的效率就越低。因此,整个字符连接的过程中,相当于S1被复制N-1次,S2被复制N-2次....SN复制1次(并不完全等同于S1复制N-1次,因为后续复制都是对中间结果的复制),所以字符串的连接时间复杂度近似为O(n^2)。
图3-4 操作符+连接字符串示意图
而当用join()方法连接字符串的时候,会首先计算需要申请的总的内存空间,然后一次性申请所需内存并将字符序列中的每一个元素复制到内存中去,所以join操作的时间复杂度为O(n)。
因此,字符串的连接,特别是大规模字符串的处理,应该尽量优先使用join而不是+。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论