[Python] 使用 + 与 join 连接字符串的性能区别

作者 huhamhire,暂无评论,2013年8月2日 09:17 程序实践

一、问题描述

对字符数据的处理是Python在应用上的一大优势,Python在操作字符串数据的时候经常要对字符串进行连接操作,连接的时候无外乎是两种方法。方法一:使用最简单的"+"运算符连接字符串;方法二:使用字符串对象的join方法来对字符串进行连接。

看到好多文章里都说上面的第一种方法效率比较低下,都推荐用join操作来代替"+"运算。但我总觉得此类文章大都对这个问题说的太笼统,有点人云亦云的感觉,在这两种方法的选择上有些盲目的一边倒。

join-vs-plus

从原理上说join的方法的确要比"+"操作来的高效。因为在Python中PyStringObject是一般是不可变的对象,如果用"+"操作进行连接,每次都需要创建新的对象并对内存中的相关数据完整地执行传送操作。join方法则可以对一系列的字符串进行连接,合并为一个新的对象,而并不需要大量申请内存空间,并执行相关的数据移动及复制操作。关于这两种方法的具体事项,可以参考Python的源码,这里就不贴出来了。

原理上面其实挺好理解的,但是我一直有两个疑问,join的方法真的在所有的情形下都是高效的吗?既然能够替代"+"运算,为嘛还会同时需要这两个方法?

要回答这两个问题,还是自己写个测试程序跑一下来的靠谱。

二、设计测试程序

在这个测试中,需要关心的问题主要是内存的占用以及时间的开销。时间的统计很简单,不过Python默认莫有提供内置的内存分析方法,程序就需要借助于memory_profile这个第三方模块了。

在这个试验中,我设计了三个函数分别测试了三种情况。"+"运算采用连加与一次加一串没有本质上的区别,设计了一个测试(str_plus);join方法在一次性合并多个字符串与一次合并一个字符串的区别较大,所以分别设计了一个测试(str_join与str_join_once)。为了体现不同方法的区分度,我每次循环生成一个长度为3000的字符串用于连接,一共循环1000次。测试时候给出时间消耗及内存开销的统计数据。具体可以看下面的代码:


 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 
 4 import time
 5 from memory_profiler import memory_usage
 6 
 7 def str_plus(test_times):
 8     text = ''
 9     for i in range(test_times):
10         text += 'abc'*10**3
11     return text
12 
13 def str_join(test_times):
14     text_list = []
15     for i in range(test_times):
16         text_list.append('abc'*10**3)
17     return ''.join(text_list)
18 
19 def str_join_once(test_times):
20     text = ''
21     for i in range(test_times):
22         text = ''.join([text, 'abc'*10**3])
23     return text
24 
25 def test(str_test, test_times):
26     start_time = time.time()
27     text = str_test(test_times)
28     finish_time = time.time()
29     print "%i times of %s() finished in %.4fs" % (
30         test_times, str_test.__name__, finish_time - start_time)
31 
32 if __name__ == "__main__":
33     str_tests = [str_join, str_plus, str_join_once]
34     for str_test in str_tests:
35         start_mem =  memory_usage()
36         mem = memory_usage((test, (str_test, 1000), ))
37         print "memory usage: %i KB" % ((max(mem) - start_mem[0]) * 1024)

程序比较简单,下面来看实际运行下来的结果统计。

三、测试结果及分析

老规矩,为了避免偶然因素,这里依旧给出三次的测试结果数据:


 1 # Test 1
 2 1000 times of str_join() finished in 0.0080s
 3 memory usage: 868 KB
 4 1000 times of str_plus() finished in 0.8100s
 5 memory usage: 6456 KB
 6 1000 times of str_join_once() finished in 1.2960s
 7 memory usage: 4640 KB
 8 
 9 # Test 2
10 1000 times of str_join() finished in 0.0040s
11 memory usage: 1280 KB
12 1000 times of str_plus() finished in 0.5960s
13 memory usage: 8388 KB
14 1000 times of str_join_once() finished in 0.9070s
15 memory usage: 6328 KB
16 
17 # Test 3
18 1000 times of str_join() finished in 0.0090s
19 memory usage: 868 KB
20 1000 times of str_plus() finished in 0.9750s
21 memory usage: 7152 KB
22 1000 times of str_join_once() finished in 1.5130s
23 memory usage: 5548 KB

从数据上来看,在一次性使用join方法合并大量的字符串时,无论是从内存还是时间的角度上来看,另两种方法的效率与之都不在一个数量级上面。但是join的优势也仅限于在一次性做大量连接的时候,从上面的数据中可以清楚的看到,在使用join方法一次连接一个字符串时,其内存开销与"+"运算相比优势并不是特别明显,只能说是略有优势,毕竟"+"运算创建的中间对象会更多。虽说还是有内存上面的略微优势,但在时间开销上面,一次join一个字符串的速度却比"+"方法慢了很多,这应该是由于join方法在执行过程中,对内存的操作更多造成的。

因此,从本次实验的结果来看,用join方法连接字符串并不适用于所有情况,仅当在可以一次性将大量字符串进行合并,或者在需要合并的字符串非常庞大的时候,才能体现出其在速度及内存占用上面的优势。而在大多数对字符串的简单操作上,完全没有必要用join方法来代替字符串的"+"运算,如果用了join,反而是得不偿失的。

关键词:Python , 小实验
登录后进行评论