在C++中实现字符串:数组还是链表,哪个更快?为什么?

本文来自:IT宝库(https://www.itbaoku.cn)

推荐答案1

好吧,您偶然发现了一个经典的计算机科学问题,就像哥斯拉和金刚之间的史诗般的摊牌一样.哎呀,让我们直接潜入其中.

想象我们正在谈论 sandbox .图片将 数组 作为盒装沙盒,对吗?这都是系统性的和有条理的.您已经布置了沙箱,并且确切知道每个玩具在哪里.全部都在 顺序顺序 中.现在很整洁,嗯?

现在, 链接列表 更像是寻宝.您会得到一张地图并遵循线索,从一个点跳到另一个点. 没有直路;您只需继续从一个节点到下一个节点. 有点酷.

strings一起工作时 in c ++ arrays 取冠.这是踢脚: 数组 是快速的冈萨雷斯,因为它们为元素提供 直接访问 .您不必扮演侦探并追溯您采取的每一步.聚会就在这里!

在另一侧, 链接列表 ,涉及更多的腿部工作. y ou需要按节点遍历整个列表节点, 在处理大字符串时可能是一个真正的嗡嗡声.

<跨度>因此,如果您全都需要速度需要,那么数组是 c ++的字符串的方式对于一个快乐的编码员.继续学习,保持编码,不要犹豫,问更多问题!

推荐答案2

在C ++中,在字符串实现上,数组比链接列表快.数组提供顺序的内存分配,并且数组中的元素在存储器中彼此隔绝.这意味着CPU不必经过数据搜索的整个层次结构,而可以根据其索引直接选择元素.考虑一个需要字符数组的第四个元素的示例.计算该元素的内存地址是在恒定时间内完成的,因此更快地完成.
相比之下,链接的列表将存储元素存储到节点中,每个节点由指向下一个元素的指针组成.这种不连续的内存分配通常会导致访问时间比数组较慢.要跳到链接列表中的节点,CPU必须沿所有列表的节点行驶,然后从一个节点到另一个节点,直到它落在正确的节点为止.因此,此结果旅行时间与影响速度的列表大小成正比.
在这种情况下,这使得阵列比经常获得特定零件或诸如搜索或索引的链接列表更快的阵列比链接列表更快.访问是恒定的,独立于时间.但是链接列表具有自己的优点,尤其是在频繁插入或删除的情况下,因为它们为列表和列表的开始和末端的插入和删除提供了恒定时间操作.

推荐答案3

都有问题.

链接的列表是一个可怕的想法,我什至不会理会这个

char阵列是我们在过去的糟糕时代如何进行弦乐.使用char数组时发生了许多问题.

使用std :: string.它会自动调整大小.

在某些情况下,char数组是正确的方法. IMO,仅在某些情况下.

推荐答案4

即使在某些情况下选择链接列表的理论原因,大多数情况下,向量仍然是一个更好的选择.

几年前,我问自己这个确切的问题,并决定不严格地基于教学书籍找到答案.

我抓住了STL源代码并查看了在C ++中实现这些容器.

是,一个良好的实现可能非常聪明,并且在向量中间插入几乎和插入一样快链接列表的中间,负面副作用较少.

cpus cpus在内存的连续块中效果最好.

链接列表倾向于将内存分散并使其效率低下,而C ++中的向量总是使用连续的内存块,甚至可以预先分配额外的内存.

block通过块进行预授权将避免不必要的片段,并节省大量时间,当完成插入并且不需要内存分配时.

考虑一下,当您在链接的中间插入新元素时列表,您必须分配一个新的内存块.

在向量中,您只能使用一个prefeal的空间,这根本不花任何费用,在此特定点及时.

插入向量中间的插入可能会导致元素重新安置.

在向量中,将可以通过上部元素的副本完成.

STL的良好实现将使用块复制低级功能,这是快速的.

在最佳情况下,当块拟合在缓存中时,副本几乎可以在单个时钟刻度中发生.

它无法获得

即使是o(1)链接列表也不会更快.

让我们考虑一下用例情况,当链接列表长时间保存在内存中并缓慢增长时.

一段时间后,堆非常分散,并分配了一个新元素将导致页面例外.

现在,CPU必须努力工作才能在缓存中重新加载页面,在最坏的情况下,该页面已互换为硬盘驱动器.

从理论上讲,列表中间的插入应该很快,但是在这种特殊情况下,它变成了总速度噩梦.<<<<<<<<<<<

因此,实际上,连续的内存能够留在缓存中总是会击败其他所有内容.

在此上面,在大多数情况下,使用向量更为自然.

因此,对于99.9%的情况,向量击败了链接列表,放下链接.

…嗯……至少在C ++中,使用STL./span>

以这种精度学习C ++需要多年的专门工作,这可能是对学习者的关闭,但这是一种非常有意义的语言,拥有数十年数以百万计的开发人员积累的智慧珍珠.

始终记住,PC是物理设备,因此所有理论必须始终在特定的上下文中应用于有意义.

并且没有"一个尺寸适合所有"魔术子弹.

这就是为什么我们始终需要越来越多的新开发人员,尽管自动化增长.

(edit)

这是关于此主题的有趣线索:

Do C++ experts and committees encourage C++ programmers to use vector wherever possible? Why?

我想在这里分享一个答案,但是我不能.

所以,我'll只需添加stroustrup视频:

" bjarne stroustrup:为什么要避免链接列表" >

(/edit)

推荐答案5

这是我对该主题的观点.毫无疑问,char []比字符串快.我建议 char [] 因为字符串本质上是不可变的.编译器当然必须进行分支和结合优化技术.字符串生成字符串池数据结构以在堆中管理其分配.以下简单练习可以验证理解.使用 debugdiag 生成3个连续的工作过程内存转储.将转储加载到 windbg .执行以下命令 执行!heap -s .它将为您提供越来越多的堆.执行 "!HEAP -STAT –H 00330000" ,这将为您提供堆的统计信息.您可以使用 !HEAP -FLT S 1F64 获得地址块.您可以使用 !heap -p -p -a usrptr 显示呼叫堆栈.您可以比较 char [] vs string 的内存脚印.如果您要存储密码或要保持密码更安全,那肯定不是字符串,因为我们可以在转储中查看您的 明文 .当心字符串类的子字符串方法.如果不明智地使用,它将吹您的公羊.我会推荐 字符串 如果您放松并且不关心性能.如果您真的想挤压您的RAM中最好的 char [] .String内部使用 char [] 但是为什么如果您知道自己在做什么并且具有固定尺寸的算法的开销.使用后字符串的内容.此功能使字符串对象不适合存储安全敏感信息,例如用户密码.您应该始终将其收集和存储安全敏感信息而改为Char阵列.您的密码很容易看到 纯文本 .没有什么是傻瓜证明,但这是您诚实的努力,使折衷轻松地妥协. 密码 不应轻易折衷是您的最大利益.

在澄清字符串文字上进行了更新.

了解什么是可变和不变的.一旦我们解决了这个想法,我们就可以讨论类是可变还是不可变的.

这是 String 工作的方式:

  1. String str = "I hate Java"; 

与往常一样,它会创建一个包含 "I hate Java" 的字符串,并为其分配参考 str .足够简单吗?让我们执行更多功能:

  1. String s = str; // assigns a new reference to the same string "I hate Java" 

让我们看看下面的语句的工作方式:

  1. str = str.concat(" base"); 

字符串 " base" to str .但是,等等,这是怎么可能的,因为 String 对象是不可变的?令您惊讶的是. > "I hate Java" 和Appends " base" ,给我们值 "I hate Java base" .现在,由于 String 是不可变的,因此VM无法将此值分配给 str ,因此它创建了一个新的 String 对象,给它一个值 "I hate Java base" ,并给予参考 str .这里要注意的重要点是, String 对象是不可变的, 其参考变量不是. ,所以这就是为什么,这就是为什么在上面的示例,提到了引用新形成的 String 对象.

在上面的示例中,我们有两个 String 对象:我们创建的第一个使用Value "I hate Java" ,指向 s ,第二个 s 跨度> "I hate Java base" ,指向 str .但是,从技术上讲,我们有三个 String 对象,第三个是 "base" concat 语句.<

有关字符串和内存用法的重要事实

如果我们没有其他参考 s怎么办 to "I hate Java" ?我们会失去 String .但是,它仍然存在,但由于没有参考文献而被认为丢失.查看下面的另一个示例

  1. String s1 = "java"; 
  2. s1.concat(" rules"); 
  3. System.out.println("s1 refers to "+s1); // Yes, s1 still refers to "java" 

发​​生了什么:

  1. 第一行很简单:创建一个新的 String "java" 并参考 s1 .跨度>接下来,VM创建另一个新的 String "java rules" ,但没有任何意见.因此,第二个 String 立即丢失.我们无法到达.

参考变量 s1 仍然是指原始 String "java" .

几乎所有方法都应用于 String 对象以修改它创建新的 String 对象.那么,这些 String 对象在哪里?好吧, 它们存在于内存中,任何编程语言的关键目标之一是有效地利用内存.

随着应用程序的增长, String 文字非常常见,占据大量内存,甚至可能导致冗余. 因此,为了使Java更加高效, JVM将一个特殊的内存区域放在一边,称为"字符串常数池". C0> 字面意思,它在池中寻找 String .如果找到匹配,则将对新文字的引用定向到现有的 String ,并且没有创建新的 String 对象.现有的 String 只有一个参考.这是制作 String 对象不变的重点:

/span> String 对象可能具有一个或多个参考. 如果有几个引用指向相同的 String 甚至不知道它,那么如果修改了其中一个参考文献,那将是不好的.这就是为什么 String 对象是不变的.

好吧,现在您可以说, 如果某人覆盖功能怎么办 String class? 这就是 String 类标记 final ,以便没有人可以覆盖其方法的行为.

字符串是不可分割的,意味着您不能更改对象本身,但是您可以将引用更改为目的.当您称为 a = "ty" 时,实际上是在将 a 的引用更改为字符串文字 "ty" 创建的新对象.更改对象意味着使用其方法更改其字段之一(或字段是公共而不是最终的字段,以便可以从外部进行更新而无需通过方法访问它们),例如:

  1. Foo x = new Foo("the field"); 
  2. x.setField("a new field"); 
  3. System.out.println(x.getField()); // prints "a new field" 

虽然在不变的类中(称为最终,以通过继承来防止修改)(其方法无法修改其字段,而且字段始终是私有的,建议将其定为最终),例如字符串,您不能更改当前字符串,但可以返回一个新字符串,即:

  1. String s = "some text"; 
  2. s.substring(0,4); 
  3. System.out.println(s); // still printing "some text" 
  4. String a = s.substring(0,4); 
  5. System.out.println(a); // prints "some