Python 等价于 Java StringBuffer?

Posted

技术标签:

【中文标题】Python 等价于 Java StringBuffer?【英文标题】:Python equivalent of Java StringBuffer? 【发布时间】:2013-11-24 10:04:22 【问题描述】:

Python 中有没有像 Java 的 StringBuffer 这样的东西?由于字符串在 Python 中也是不可变的,因此在循环中编辑它们的效率会很低。

【问题讨论】:

您可能会通过构建字符串列表并在循环后在其上使用join() 来获得类似的效果。但我确信有一种更 Pythonic 的方式(可能涉及列表理解)。 【参考方案1】:

此链接可能对 python 中的连接有用

http://pythonadventures.wordpress.com/2010/09/27/stringbuilder/

以上链接示例:

def g():
    sb = []
    for i in range(30):
        sb.append("abcdefg"[i%7])

    return ''.join(sb)

print g()   

# abcdefgabcdefgabcdefgabcdefgab

【讨论】:

虽然这在理论上可以回答这个问题,it would be preferable 在此处包含答案的基本部分,并提供链接以供参考。【参考方案2】:

也许使用bytearray:

In [1]: s = bytearray('Hello World')

In [2]: s[:5] = 'Bye'

In [3]: s
Out[3]: bytearray(b'Bye World')

In [4]: str(s)
Out[4]: 'Bye World'

使用字节数组的吸引力在于它的内存效率和方便的语法。它也可以比使用临时列表更快:

In [36]: %timeit s = list('Hello World'*1000); s[5500:6000] = 'Bye'; s = ''.join(s)
1000 loops, best of 3: 256 µs per loop

In [37]: %timeit s = bytearray('Hello World'*1000); s[5500:6000] = 'Bye'; str(s)
100000 loops, best of 3: 2.39 µs per loop

请注意,速度上的大部分差异可归因于容器的创建:

In [32]: %timeit s = list('Hello World'*1000)
10000 loops, best of 3: 115 µs per loop

In [33]: %timeit s = bytearray('Hello World'*1000)
1000000 loops, best of 3: 1.13 µs per loop

【讨论】:

这将使用什么编码?在 Java 中类似的结构会很成问题,因为它们使用平台默认编码,可以是任何东西...... @JoachimSauer:就像str,编码由你决定。就bytearray而言,每个值都只是一个字节。 bytearray 对于真正低级的东西很有用——顾名思义,它实际上是关于“字节数组”,而不是“字符串”。 "...但它比使用临时列表要慢。"什么是临时清单?是(Python 的默认)列表,比如['s', 't', 'r', 'i', 'n', 'g'] @BornToCode:临时列表将是mylist in bruno desthuilliers' code。【参考方案3】:

取决于你想做什么。如果你想要一个可变序列,内置的 list 类型是你的朋友,从 str 到 list 再返回很简单:

 mystring = "abcdef"
 mylist = list(mystring)
 mystring = "".join(mylist)

如果你想使用 for 循环构建一个大字符串,pythonic 方法通常是构建一个字符串列表,然后用适当的分隔符(换行符或其他)将它们连接在一起。

否则,您还可以使用一些文本模板系统、解析器或任何最适合该工作的专用工具。

【讨论】:

"".join(mylist) 的复杂度是O(n)吗? @user2374515 是的, str.join() 方法的复杂度为 O(n)。根据official documentation:“对于性能敏感代码,最好使用str.join() 方法,该方法可确保跨版本和实现的线性连接性能。”【参考方案4】:

Python 3

来自docs:

连接不可变序列总是会产生一个新对象。这意味着通过重复连接构建序列将在总序列长度中具有二次运行时成本。要获得线性运行时成本,您必须切换到以下备选方案之一: 如果连接 str 对象,您可以构建一个列表并在最后使用 str.join() 或写入 io.StringIO 实例并在完成时检索其值

比较几个选项的运行时间的实验:

import sys
import timeit
from io import StringIO
from array import array


def test_concat():
    out_str = ''
    for _ in range(loop_count):
        out_str += 'abc'
    return out_str


def test_join_list_loop():
    str_list = []
    for _ in range(loop_count):
        str_list.append('abc')
    return ''.join(str_list)


def test_array():
    char_array = array('b')
    for _ in range(loop_count):
        char_array.frombytes(b'abc')
    return str(char_array.tostring())


def test_string_io():
    file_str = StringIO()
    for _ in range(loop_count):
        file_str.write('abc')
    return file_str.getvalue()


def test_join_list_compr():
    return ''.join(['abc' for _ in range(loop_count)])


def test_join_gen_compr():
    return ''.join('abc' for _ in range(loop_count))


loop_count = 80000

print(sys.version)

res = 

for k, v in dict(globals()).items():
    if k.startswith('test_'):
        res[k] = timeit.timeit(v, number=10)

for k, v in sorted(res.items(), key=lambda x: x[1]):
    print(':.5f '.format(v, k))

结果

3.7.5 (default, Nov  1 2019, 02:16:32) 
[Clang 11.0.0 (clang-1100.0.33.8)]
0.03738 test_join_list_compr
0.05681 test_join_gen_compr
0.09425 test_string_io
0.09636 test_join_list_loop
0.11976 test_concat
0.19267 test_array

Python 2

Efficient String Concatenation in Python 是一篇相当老的文章,它的主要陈述是天真的连接远比连接慢得多,因为从那时起这部分已经在 CPython 中进行了优化。来自docs:

CPython 实现细节:如果 s 和 t 都是字符串,一些 Python 实现(例如 CPython)通常可以对 s = s + t 或 s += t 形式的赋值执行就地优化。如果适用,这种优化会大大降低二次运行时间的可能性。这种优化既依赖于版本,也依赖于实现。对于性能敏感的代码,最好使用 str.join() 方法,该方法可确保跨版本和实现的线性连接性能一致。

我稍微修改了他们的代码,并在我的机器上得到了以下结果:

from cStringIO import StringIO
from UserString import MutableString
from array import array

import sys, timeit

def method1():
    out_str = ''
    for num in xrange(loop_count):
        out_str += `num`
    return out_str

def method2():
    out_str = MutableString()
    for num in xrange(loop_count):
        out_str += `num`
    return out_str

def method3():
    char_array = array('c')
    for num in xrange(loop_count):
        char_array.fromstring(`num`)
    return char_array.tostring()

def method4():
    str_list = []
    for num in xrange(loop_count):
        str_list.append(`num`)
    out_str = ''.join(str_list)
    return out_str

def method5():
    file_str = StringIO()
    for num in xrange(loop_count):
        file_str.write(`num`)
    out_str = file_str.getvalue()
    return out_str

def method6():
    out_str = ''.join([`num` for num in xrange(loop_count)])
    return out_str

def method7():
    out_str = ''.join(`num` for num in xrange(loop_count))
    return out_str


loop_count = 80000

print sys.version

print 'method1=', timeit.timeit(method1, number=10)
print 'method2=', timeit.timeit(method2, number=10)
print 'method3=', timeit.timeit(method3, number=10)
print 'method4=', timeit.timeit(method4, number=10)
print 'method5=', timeit.timeit(method5, number=10)
print 'method6=', timeit.timeit(method6, number=10)
print 'method7=', timeit.timeit(method7, number=10)

结果:

2.7.1 (r271:86832, Jul 31 2011, 19:30:53) 
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)]
method1= 0.171155929565
method2= 16.7158739567
method3= 0.420584917068
method4= 0.231794118881
method5= 0.323612928391
method6= 0.120429992676
method7= 0.145267963409

结论:

join 仍然胜过 concat,但幅度很小 列表推导比循环更快 (when building a list) 加入生成器比加入列表慢 其他方法没有用(除非您正在做一些特别的事情)

【讨论】:

MutableString 类在 python 2.6 中已被弃用并在 Python 3 中完全删除,这可能一文不值。请参阅here 警告! CPython 对此进行优化的声明不再适用于最近的版本(v3.5-v3.8+)。这已替换为警告以这种方式连接不可变对象是总是二次的:docs.python.org/3/library/stdtypes.html @jtschoonhoven:我已将帖子转为 CW,请编辑您的评论。谢谢!【参考方案5】:

之前提供的答案几乎总是最好的。但是,有时字符串是在许多方法调用和/或循环中建立起来的,因此建立一个行列表然后将它们连接起来并不一定很自然。由于无法保证您使用的是 CPython,或者 CPython 的优化将适用,因此另一种方法是使用 print!

这是一个帮助类示例,虽然帮助类很简单而且可能没有必要,但它用于说明方法(Python 3):

import io

class StringBuilder(object):

    def __init__(self):
        self._stringio = io.StringIO()
    
    def __str__(self):
        return self._stringio.getvalue()
    
    def append(self, *objects, sep=' ', end=''):
        print(*objects, sep=sep, end=end, file=self._stringio)

sb = StringBuilder()
sb.append('a')
sb.append('b', end='\n')
sb.append('c', 'd', sep=',', end='\n')
print(sb)  # 'ab\nc,d\n'

【讨论】:

【参考方案6】:

只是我在 python 3.6.2 上运行的测试表明“加入”仍然大获全胜!

from time import time


def _with_format(i):
    _st = ''
    for i in range(0, i):
        _st = "".format(_st, "0")
    return _st


def _with_s(i):
    _st = ''
    for i in range(0, i):
        _st = "%s%s" % (_st, "0")
    return _st


def _with_list(i):
    l = []
    for i in range(0, i):
        l.append("0")
    return "".join(l)


def _count_time(name, i, func):
    start = time()
    r = func(i)
    total = time() - start
    print("%s done in %ss" % (name, total))
    return r

iterationCount = 1000000

r1 = _count_time("with format", iterationCount, _with_format)
r2 = _count_time("with s", iterationCount, _with_s)
r3 = _count_time("with list and join", iterationCount, _with_list)

if r1 != r2 or r2 != r3:
    print("Not all results are the same!")

输出是:

with format done in 17.991968870162964s
with s done in 18.36879801750183s
with list and join done in 0.12142801284790039s

【讨论】:

如您所见,使用 printf 和 .format 连接字符串效率更低。【参考方案7】:

我在 Roee Gavirel 的代码中添加了 2 个额外的测试,这些测试最终表明将列表加入字符串并不比 s += "something" 快,直到 Python 3.6。以后的版本有不同的结果。

结果:

Python 2.7.15rc1    

Iterations: 100000
format    done in 0.317540168762s
%s        done in 0.151262044907s
list+join done in 0.0055148601532s
str cat   done in 0.00391721725464s
    
Python 3.6.7

Iterations: 100000
format    done in 0.35594654083251953s
%s        done in 0.2868080139160156s
list+join done in 0.005924701690673828s
str cat   done in 0.0054128170013427734s
f str     done in 0.12870001792907715s

Python 3.8.5

Iterations: 100000
format    done in 0.1859891414642334s
%s        done in 0.17499303817749023s
list+join done in 0.008001089096069336s
str cat   done in 0.014998912811279297s
f str     done in 0.1600024700164795s

代码:

from time import time


def _with_cat(i):
    _st = ''
    for i in range(0, i):
        _st += "0"
    return _st


def _with_f_str(i):
    _st = ''
    for i in range(0, i):
        _st = f"_st0"
    return _st


def _with_format(i):
    _st = ''
    for i in range(0, i):
        _st = "".format(_st, "0")
    return _st


def _with_s(i):
    _st = ''
    for i in range(0, i):
        _st = "%s%s" % (_st, "0")
    return _st


def _with_list(i):
    l = []
    for i in range(0, i):
        l.append("0")
    return "".join(l)


def _count_time(name, i, func):
    start = time()
    r = func(i)
    total = time() - start
    print("%s done in %ss" % (name, total))
    return r


iteration_count = 100000

print('Iterations: '.format(iteration_count))
r1 = _count_time("format   ", iteration_count, _with_format)
r2 = _count_time("%s       ", iteration_count, _with_s)
r3 = _count_time("list+join", iteration_count, _with_list)
r4 = _count_time("str cat  ", iteration_count, _with_cat)
r5 = _count_time("f str    ", iteration_count, _with_f_str)

if len(set([r1, r2, r3, r4, r5])) != 1:
    print("Not all results are the same!")

【讨论】:

万岁,感谢“有时简单的方法是最好的方法”部门的你。 使用更高版本的 Python 3.8+ list + join 几乎是 s+= 的两倍【参考方案8】:

在最佳答案中,来自“Python 中的高效字符串连接”的链接不再链接到预期页面(而是重定向到 tensorflow.org)。 但是,这个 2004 年的页面以及引用的确切代码可能代表该页面 https://waymoot.org/home/python_string/。

如果你用谷歌搜索它,你可能已经看到它了:

         efficient python StringBuilder

我不能在评论中留下这个,因为我没有特权。

【讨论】:

我将保留上面的原始答案未经编辑,以记录 *** 上的愚蠢人如何对添加有用链接的答案投反对票。同时,“Python 中的高效字符串连接”不再重定向到 tensorflow.org,因为 skymind.com crunchbase.com/organization/skymind 似乎已更名为 welcome.ai/skymind【参考方案9】:

Python 提供的最接近可变字符串或 StringBuffer 的东西可能是来自 array 标准库模块的 Unicode 类型数组。它在您只想编辑字符串的一小部分的情况下很有用:

modifications = [(2, 3, 'h'), (0, 6, '!')]
n_rows = multiline_string.count('\n')
strarray = array.array('u', multiline_string)
for row, column, character in modifications:
    strarray[row * (n_rows + 1) + column] = character
multiline_string = map_strarray.tounicode()

【讨论】:

以上是关于Python 等价于 Java StringBuffer?的主要内容,如果未能解决你的问题,请参考以下文章

Python 等价于 Java StringBuffer?

Python Java forEach 等价物

c++中istringstream及ostringstream超详细说明

Golang 等价于 Python 的 NotImplementedException

Python 等价于 phpinfo()

Javascript 等价于 python 的 .format()