自从我上高中的第一门编程课开始,我就听说字符串操作比神话中的“平均操作”要慢,也就是成本更高。为什么让它们这么慢?(这个问题是故意留下的。)
自从我上高中的第一门编程课开始,我就听说字符串操作比神话中的“平均操作”要慢,也就是成本更高。为什么让它们这么慢?(这个问题是故意留下的。)
Answers:
“平均操作”发生在基元上。但是,即使在将字符串视为原始语言的语言中,它们仍然是幕后的数组,进行涉及整个字符串的任何操作都需要O(N)时间,其中N是字符串的长度。
例如,将两个数字相加通常需要2-4条ASM指令。连接(“添加”)两个字符串需要新的内存分配以及一个或两个字符串副本,其中涉及整个字符串。
某些语言因素会使情况更糟。例如,在C语言中,字符串只是指向以null终止的字符数组的指针。这意味着您不知道它有多长时间,因此无法通过快速移动操作来优化字符串复制循环。您需要一次复制一个字符,以便可以测试每个字节的空终止符。
char*
,而不是a strbuf
,然后您回到平方1。当将不良的设计植入语言时,可以做到这一点。
buf
指针在那里。我从来没有暗示过它不可用。而是有必要的。 任何不知道您的优化但非标准字符串类型的代码,包括诸如标准库之类的基本内容,仍然必须依靠缓慢,不安全的方法char*
。您可以根据需要调用该FUD,但这并非不正确。
这是一个老话题,我认为其他答案都不错,但忽略了一些东西,所以这是我的(晚)2美分。
字符串的问题在于,它们在大多数语言中都是二等公民,实际上大多数时候并不是语言规范本身的一部分:它们是由库实现的结构,顶部偶尔有句法糖衣使它们减轻了使用的痛苦。
这样的直接后果是,该语言将大部分复杂性隐藏在您的视线之外,并且您为偷偷摸摸的副作用付了钱,因为您养成了将它们视为低级原子实体的习惯,就像其他原始类型(如最高答案和其他解释)。
这种潜在的“复杂性”的要素之一是,大多数字符串实现都将诉诸使用带有一些连续存储空间的简单数据结构来表示字符串:好的ol'数组。
请注意,这很有意义,因为您希望快速访问整个字符串。但是,这意味着在您要操纵此字符串时可能会付出可怕的代价。如果您知道要在后面的索引,那么访问中间的元素可能会很快,但是基于条件查找元素却不是。
如果您的语言没有缓存字符串的长度,并且需要遍历字符串来计算字符,那么即使返回字符串的大小也可能会付出高昂的代价。
出于类似的原因,添加您的字符串元素会非常昂贵,因为您很可能需要重新分配一些内存才能执行此操作。
因此,不同的语言对这些问题采取不同的方法。例如,出于某些正当的理由(缓存长度,线程安全性),Java采取了使字符串不可变的自由,而对于可变的对应对象(StringBuffer和StringBuilder),它将选择使用较大的块来分配大小,而无需分配每次,但希望有最佳案例。通常效果很好,但缺点是有时要弥补内存影响。
同样,这又是由于您的语言的语法糖衣使您无法玩得开心,您通常不认为它是unicode支持的术语(尤其是在您真正不需要它的情况下)然后撞到那堵墙)。而且,一些具有前瞻性的语言不会使用简单的8位char基元基本数组来实现字符串。它们采用UTF-8或UTF-16或您所需要的支持来进行烘焙,其结果是大大增加了内存消耗(通常不需要),并且分配内存,处理字符串的处理时间也更长,并实现与操纵代码点齐头并进的所有逻辑。
所有这些的结果是,当您在伪代码中执行等效的操作以:
hello = "hello,"
world = " world!"
str = hello + world
尽管语言开发人员已尽最大努力使它们表现得与您所期望的一样,但事实并非如此。
a = 1;
b = 2;
shouldBeThree = a + b
作为后续,您可能需要阅读:
短语“平均操作”可能是理论上随机访问存储程序机器的单个操作的简写。这是通常用来分析各种算法的运行时间的理论机器。
通常将一般操作视为加载,加法,减法,存储,分支。也许还可以阅读,打印和暂停。
但是大多数字符串操作都需要其中一些基本操作。例如,复制字符串通常需要复制操作,因此需要进行与字符串长度成正比(即“线性”)的许多操作。在另一个字符串中查找子字符串也具有线性复杂度。
让我用一个问题回答你的问题。为什么说一串字比说一个字要花更长的时间?