算法与数据结构——基本数据类型与编码

风陵南·2024-08-23 10:17·154 次阅读

算法与数据结构——基本数据类型与编码

基本数据类型

基本数据类型是计算机CPU可以直接进行运算的类型,在算法中直接被使用,主要包括以下几种

  • 整数类型byteshortintlong
  • 浮点数类型floatdouble,用于表示小数
  • 字符类型char,用于表示各种语言的字母、标点符号甚至表情符号等。
  • 布尔类型bool,用于表示“是”与“否”判断。

基本数据类型以二进制的形式存储在计算机中。一个二进制位即1比特。在绝大多数现代操作系统中,1字节(byte)由8bite(bit)组成。

基本数据类型的取值范围取决于其占的空间大小,下面以Java为例。

  • 整数类型byte占用1字节=8比特,可以表示28个数字
  • 整数类型int占用4字节=32比特,可以表示232个数字

下表列举了各种基本数据类型的占用空间、取值范围和默认值。

类型 符号 占用空间 最小值 最大值 默认值
整数 byte 1 字节 -27(-128) 27 - 1(127) 0
short 2 字节 -215 231 - 1 0
int 4 字节 -231 231 - 1 0
long 8字节 -263  263 - 1 0
浮点数 float 4 字节  1.175 * 10-38 3.403 * 1038 0.0f
double 8 字节 2.225 * 10-308 1.798 * 10-308 0.0
字符 char 2 字节 0 216-1 0
布尔 bool 1 字节 false true false

字符char的大小在C和C++中为1字节。

基本数据类型提供了数据的“内容类型”,而数据结构提供了数据的“组织方式”。

 

数字编码

在前面的取值范围中,所有整数类型能够表示的负数都比正数多一个,例如bute的取值范围是[-128,127]。其原因涉及原码、反码、补码的相关知识。

  • 原码:我们将数字的二进制表示的最高位视为符号位,其中0表示正数,1表示负数,其余位表示数字的值。
  • 反码:正数的反码与其原码相同,负数的反码是对其原码除符号位外的所有位取反。
  • 补码:正数的补码与其原码相同,负数的补码是在其反码的基础上加1。

注意:数字是以“补码”的形式存储在计算机中的

原码(sign-magnitude)虽然最直观,但存在一些局限性。一方面,负数的原码不能直接用于运算。例如在原码下计算 1 + ( - 2),得到的结果是 - 3,这显然是不对的。

为解决此问题,计算机引入了反码。如果我们先将原码转换为反码,并在反码下计算 1 + (- 2),最后将结果从反码转换回原码,则可得到正确结果 -1。

另一方面,数字零的原码有 +0 和 -0 两种表示方式。这意味着数字零对应两个不同进制的二进制编码,这可能会带来歧义。与原码一样,反码也存在正负零歧义问题,因此计算机进一步引入了补码。下面是负零的原码、反码、补码的转换过程。

在负零的反码基础上加1会产生进位,但byte类型的长度只有8位,因此溢出到第9位的1会被舍去,这样,负零的补码为 0000 0000,与正零的补码相同。这意味着在补码表示中只存在一个零,正负从而得到解决。

注意:byte类型的取值范围是[-128, 127],多出来的一个负数-128是如何得到的?

我们注意到,区间[-127, +127]内的所有整数都有对应的原码、反码和补码,并且原码和补码之间可以互相转换。

然而,补码1000 0000 是一个例外,它并没有对应的原码。根据转换方法,我们得到该补码的原码为 0000 0000。这显然是矛盾的,因为该原码表示数字0,它的补码应该是自身。计算机规定这个特殊的补码1000 0000 就代表 -128。实际上(-1)+(-27)在补码下的计算结果就是 -128 。

事实上,计算机内部的硬件电路主要是基于加法运算设计的。这是因为加法运算相对于其他运算(比如乘法除法或减法)来说,硬件实现起来更简单,更容易进行并行化处理,运算速度更快。

字符编码

在计算机中,所有数据都是以二进制数的形式存储的,字符char也不例外。为表示字符,我们需要建立一套“字符集”,规定每个字符和二进制数之间一一对应关系。有了字符集之后,计算机就可以通过查表完成二进制数到字符的转换。

ASCII字符集#

ASCII码是最早出现的字符集,其全称为 American Standard Code for Information Interchange(美国标准信息交换代码)。它使用7位二进制数(一个字节的低7位)表示一个字符,最多能够表示128个不同的字符。如下图所示,ASCII码包括英文字母大小写、数字0~9、一些标点符号,以及一些控制字符(如换行符和制表符)。

然而,ASCII码仅能表示英文。随着计算机的全球化,诞生了一种能够表示更多语言的EASCII字符集,它在ASCII的基础上扩展到8位,能够表示256个不同字符。

GBK字符集#

EASCII码仍然无法满足许多语言的字符数量要求。比如汉字有近10万个,光日常使用的就有几千个。中国国窖标准总局在1980年发布了GB2312字符集,其收录了6763个汉字,基本满足了汉字的计算机处理需要。

但GB2312无法处理部分罕见字和繁体字。GBK字符集是在GB2312的基础上扩展得到的,它共收录了21886个汉字。在GBK编码方案中,ASCII字符使用一个字节表示,汉字使用两个字节表示。

Unicode字符集#

随着计算机技术的蓬勃发展,字符集与编码标准百花齐放,这带来了许多问题。一方面,这些字符集一般只定义了特定语言的字符,无法在多语言环境下正常工作。另一方面,同一种语言存在多种字符集标准,如果两台计算机使用的是不同的编码标准,则在信息传递时就会出现乱码。在这种背景下,一个大而全的字符集Unicode应运而生。

Unicode的中文名称为“统一码”,理论上能够容纳100多万个字符。它致力于将全球范围内的字符纳入统一的字符集之中,提供一种通用的字符集来处理和显示各种语言文字,减少因为编码标准不同而产生的乱码问题。在Unicode字符集中,常用的字符占用2个字节,有些生僻的字符占用3字节甚至4字节。

Unicode是一种通用字符集,本质上是给每一个字符分配一个编号(称为“码点”),但它并没有规定在计算机中如何存储这些码点。当多种长度的Unicode码点同时出现在一个文本中时,系统如何确认它是2字节字符还是1字节字符?

对这个问题,一种直接的解决方案是将所有字符存储为等长的编码。如下图,“Hello”中每个字符占用1字节,“算法”中每个字符占用2字节。我们可以通过高位填0将所有字符都编码为2字节长度。这样系统就可以每个2字节解析一个字符了。

而ASCII已经证明了编码英文只需1字节,若采用上述方案,英文文本只能用空间的大小将会是ASCII编码的两倍,非常浪费内存空间。因此需要一种更加高效的Unicode编码方法。

UTF-8编码#

目前,UTF-8已成为国际上使用最广泛的Unicode编码方法。它是一种可变长度的编码,使用1到4字节来表示一个字符,根据字符的复杂性而变。ASCII字符只需1字节,拉丁字母和希腊字母需要2字节,常用的中文字符需要3字节,而其他的一些生僻字符需要4字节。

UTF-8的编码规则并不复杂,分为以下两种情况。

  • 对于长度为1字节的字符,将其最高位设置为0,其余7位设置为Unicode码点,UTF-8编码可以向下兼容ASCII码。
  • 对于长度为n字节字符(其中n > 1),将首个字节的高n位都设置为1,第n + 1位设置为0从第二个字节开始,将每个字节的高两位都设置为10;其余所有位用于填充字符的Unicode码点

下图展示了“Hello算法”对应的UTF-8编码。观察发现,由于最高n位都设置为1,因此系统可以通过读取最高位1的个数来解析出字符的长度为n。

其余字节头部的10能够起到校验符的作用。假设系统从一个错误的字节开始解析文本,字节头部的10能够帮助系统快速判断出异常。

之所以将10当做校验符,是因为在UTF-8编码规则下,不可能有字符的最高位是10。这个结论可以用反证法来证明:假设一个字符的最高位是10,说明该字符的长度为1,对应ASCII码,而ASCII码的最高位应该是0,与假设矛盾。

除了UTF-8之外,常见的编码方式还包括以下两种:

  • UTF-16编码:使用2或4字节来表示一个字符。所有的ASCII字符和常用的非英文字符都用2字节表示;少数字符需要用到4字节表示。对于2字节的字符,UTF-16编码与Unicode码点相等。
  • UTF-32编码:每个字符都使用4字节。这意味着UTF-32比UTF-8和16更占空间,特别是对于ASCII字符占比比较高的文本。

从存储空间占用的角度看,使用UTF-8表示英文字符非常高效,因为它仅需1字节;使用UTF-16编码某些非英文字符(例如中文)会更加高效,因为它仅需2字节,而UTF-8需要3字节。

从兼容性的角度看,UTF-8的通用性能最佳,许多工具和库优先支持UTF-8。

编程语言的字符编码#

对于以往的大多数编程语言,程序运行中的字符串都采用UTF-16或是UTF-32这类等长编码。在等长编码下,我们可以将字符串看做数组来处理,这种做法具有以下优点:

  • 随机访问:UTF-16编码的字符串可以很容易地进行随机访问。UTF-8是一种变长编码,要想找到地 i 个字符我们需要从字符串的开始处遍历到第 i 个字符,这需要O(n)的时间。
  • 字符计数:与随机访问类似,计算UTF-16编码的字符串长度也是O(1)的操作,但是计算UTF-8编码的字符串长度需要遍历整个字符串。
  • 字符串操作:在UTF-16编码的字符串上,很多字符串操作(如分割、连接、插入、删除等)更容易进行。在UTF-8编码的字符串上,这些操作通常需要额外的计算,以确保不会产生无效的UTF-8编码。
posted @   风陵南  阅读(154)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示
目录