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