EDIT 1 增加了一些来源链接;改进了 Lisp 的历史故事;回答了为什么 Java 有原语。
EDIT 2 评论现代脚本语言,解释效率如何不再是一个问题
在过去,内存很昂贵——即使是简单的计算机也只有几千字节。您必须同意的典型服务条款将超过整个系统的 RAM。这意味着数据结构必须非常比您今天可以设计的要小得多。
计算机始于 1940 年代的英国和美国,这些工程师所需的最低字符集是西欧字母,没有任何令人兴奋的口音。 0-9、A-Z 和 a-z 为 62 个字符。添加 31 个控制字符、空格和一些标点符号,您可以将所有内容放入 7 位中。非常适合电传打字机。
现在,这 7 位可以在不同的架构上以不同的方式布局。如果您使用 IBM,您必须知道 EBCDIC 与 ASCII 完全不同。
60 年代和 70 年代的语言反映了这些问题,并将字符串压缩到尽可能小的空间中:
- Pascal:压缩的字节数组 - 固定长度且不以 null 结尾
- C:以空值结尾的字节序列(通常被认为是一个数组,使用了疯狂的黑客思想,即数组下标只是 pointer arithmetic)
- Fortran 66:字符串?你不需要它们。将一对字符存储在一个整数中并使用READ、WRITE和FORMAT
作为这些语言的程序员,我可以说这很糟糕。特别是因为大多数商业程序需要大量的文本输入和操作。随着内存变得更便宜,程序员倾向于先编写字符串实用程序,以便能够做任何有成效的事情。
固定长度的字符串(例如 Pascal)很有效,但如果您需要扩展或收缩它们甚至是单个字符,则很麻烦。
C 的空终止方法的缺点是长度不与字符串一起存储,因此很容易覆盖缓冲区并使应用程序崩溃。此类错误仍然是计算机不安全的主要原因。有两种方法可以解决这个问题:
- 每次写入时检查字符串长度:这意味着扫描内存,直到找到空字符。丑
-
malloc新建内存并将字符串复制到新内存中,然后free
在 80 年代,越来越多的标准库被引入来处理字符串 - 这些是由工具供应商和操作系统提供商提供的。标准化有大动作,但各方为了控制标准而争吵不休,很丑。
日益国际化也带来了另一个问题——国际字符集。首先,ASCII 扩展为 8 位为 ISO 8859-1 用于不同的欧洲语言(口音、希腊语、西里尔语),然后 Unicode 将计算机完全带到了世界的各个角落。这就带来了UTF-8、UTF-16等字符编码的问题,以及如何在这些不同的方法之间进行转换。
我还应该注意到Lisp 引入了垃圾收集。这用malloc/free 解决了C 的复杂性。 Lisp 极其强大的数组和序列库可以自然地处理字符串。
第一个将这些趋势结合在一起的主要流行语言是 Java。它结合了语言的三个改进:
- 国际化和 Unicode:一种不同的数据类型,
Character 和原语 char
- 封装:固定长度与空终止的问题通过以下方式消除:
- 不可变
- VM 和 GC 中的巧妙优化
- 库:所有基本的字符串操作功能都已在该语言中标准化。
如今,有些语言的每个值都是一个对象。然而,当 Java 在 90 年代后期构思时,GC 和 JIT/Hotspot 技术远没有现在这么快(至少部分是由于 RAM 的限制,但算法也得到了改进)。 Gosling was concerned about performance 并保留原始数据类型。
另外一点:在 Java 中,有一个 Character 类是很自然的——它是许多操作和实用方法的自然归宿,例如 isWhiteSpace() 和 isLetter(),后者在日语、韩语中有些复杂和印度语言。
Python 做了一个poor early decision 来定义一个字符为 8 位 ASCII;您可以通过首先引入另一种略有不同且不兼容的数据类型(unicode)来看到随之而来的问题,并且现在只能通过复杂的迁移到 Python 3.x 来解决。
现代语言(包括脚本语言)遵循关于字符串库外观的广泛共识,例如 Java 和 Python。
每种语言都是为特定目的而设计的,因此以不同的方式平衡相互竞争的设计问题。现代语言受益于过去 60 年来在性能和内存方面的巨大改进,因此它们更倾向于泛化、纯度和实用性,而不是 CPU 和 RAM 的效率。这对于脚本语言来说是明确的,因为脚本的性质已经做出了这个决定。因此,现代语言往往只有高级字符串类型。
TL/DR 早期的计算机在内存方面受到了可怕的限制,不得不采用最简单的实现方式。现代语言受益于 GC 识别国际化(8bit->16bit)字符并封装字符串数据类型以使字符串操作安全且容易。