熟练使用内存

计算机是进行数据处理的设备,而程序表示的就是处理顺序和数据结构。由于处理对象数据是存储在内存和磁盘上的,因此程序必须能自由地使用内存和磁盘。

内存的物理机制

内存实际上是一种名为内存IC的电子元件,虽然内存IC包括DRAM、SRAM、ROM等多种形式,但从外部来看,其基本机制都是一样的。内存IC中有电源、地址信号、数据信号、控制信号等用于输入输出的大量引脚(IC的引脚),通过为其指定地址来进行数据的读写。

内存的引脚

内存IC能存储多少数据呢?

  • 数据信号引脚D0~D7:表示一次可输入输出8bit(1byte)的数据
  • 地址信号引脚A0~A9:表示可指定0000000000至1111111111共2的10次方即1024个地址

地址是用来表示数据的存储场所,因此这个内存IC可存储1024个1字节的数据,即容量为1KB。

假如要往该内存IC中写入1字节的数据,为了实现该目的,可以给VCC接入+5V,给GND接入0V的电源,并使用A0到A9的地址信号来指定数据的存储场所,然后再把数据的值输入给D0到D7的数据信号,并把WR(write 写入)信号设定为1。执行完这些操作后就可以在内存IC内部写入数据了。

往0101010101地址写入1111 0000数据时

读出数据时,只需要通过A0到A9的地址信号指定数据的存储场所,然后再将RD(read 读出)信号设成1即可。执行完这些操作,指定地址中存储的数据就会被输出到D0到D7的数据信号引脚中。另外,像WR和RD这样可让IC运行的信号称为控制信号。其中,当WR和RD同时为0时,写入和读出的操作都无法进行。

读出0101010101地址的数据时

内存的逻辑模型

对于程序员来说,用楼房的图形来表示内存,1层可存储1个字节的数据,楼层号表示的就是地址。能够形象的解说有助于了解内存。

虽然内存的实体是内存IC,不过从程序员的角度来看,也可以把它假想成每层都存储着数据的楼房,并不需要过多地关注内存IC的电源和控制信号的。

1KB的内存模型

程序员眼中的内存模型中,包含着物理内存中不存在的概念,那就是数据类型。编程语言中的数据类型表示存储的是何种类型的数据。从内存来看,就是占用的内存大小(占有的楼层数)的意思。即使是物理上以1个字节为单位来逐一读写数据的内存,在程序中,通过指定其类型(变量的数据类型等),也能实现以特定字节数为单位来进行读写。

// 定义变量
char a;// 1字节长度的char
short b;// 2字节长度的short
long c;// 4字节长度的long
// 给变量赋值
a = 123;
b = 123;
c = 123;

将数据低位存储在内存低位地址的低字节序(little endian)方式:

变量的数据类型不同所占用的内存大小也不一样

指针

理解指针的关键点就是要弄清楚数据类型这个概念。

指针也是一种变量,它所表示的不是数据的值,而是存储着数据的内存的地址。通过使用指针,可对任意指定地址的数据进行读写。

各种数据类型指针的定义,这些数据类型表示的是从指针存储的地址中一次能够读写的数据字节数。

char *d;// char类型的指针d的定义
short *e;// short类型的指针e的定义
long *f;// long类型的指针f的定义
指针的数据类型表示一次可以读写的长度

数组是高效使用内存的基础

数组是指多个同样数据类型的数据在内存中连续排列的形式。作为数据元素的各个数据会通过连续的编号被区分开来,这个编号称为索引(index)。指定索引后,就可以对该索引所对应地址的内存进行读写操作。而索引和内存地址的变换工作则是由编译器自动实现的。

各种类型的数组定义

char g[100];// char类型数组变量g的定义
short h[100];//short类型数组变量h的定义
long i[100];//long类型数组变量i的定义

数组的定义中所指定的数据类型,也表示一次能读写的内存大小,数组是使用内存的基本。

  • char类型的数组以1个字节为单位对内存进行读写
  • short类型的数组以2个字节为单位对内存进行读写
  • long类型的数组以4个字节为单位对内存进行读写

之所以说数组是内存的使用方法的基础,是因为数组和内存的物理构造式一样的。

不同数据类型的数组

栈、队列以及环形缓冲区

栈和队列都可以不通过指定地址和索引来对数组的元素进行读写。需要临时保存计算过程中的数据、连接在计算机上的设备或输入输出的数据时,都可以通过这些方法来使用内存。如果每次保存临时数据都需要指定地址和索引,程序就会变得比较麻烦,因此要加以改进。

栈和队列的区别在于数据出入的顺序不同,在堆内存数据进行读写时,栈用的是LIFO(Last Input First Output,后入先出),队列使用的则是FIFO(First Input First Output,先进先出)。

如果在内存中预留出栈和队列所需的空间,并确定好写入和读出的顺序,就不用再指定地址和索引了。如果要在程序中实现栈和队列,就需要以适当的元素来定义一个用来存储数据的数组,以及对该数组进行读写的函数。

使用数组实现栈和队列的操作

// 向栈中写入数据(入栈/栈底压入)
Push()
// 从栈中读出数据(出栈,栈顶弹出)
Pop()

// 向队列中写入数据(头部追加)
EnQueue()
// 从队列中读出数据(头部移出)
DeQueue()

在栈中,LIFO表示栈的数组中所保存的最后的数据(Last In)会被最先读取出来(First Out)。

// 向栈中写入数据
Push(123); // 写入123
Push(456); // 写入456
Push(789); // 写入789
// 从栈中读出数据
j = Pop(); // 读出789
k = Pop(); // 读出456
l = Pop(); // 读出123
运行时栈的变化

栈(stack)的原意是“干草堆堆积如山”。干草堆堆积成山后,最后堆的干草会被最先抽取出来。干草堆也是用来临时保存家禽饲料的方式。程序中也是如此,为了实现临时保存数据的目的,使用类似干草堆的机制是非常方便的。这种机制提现在内存上就是栈。

在队列中,FIFO表示队列的数组中保存的最初的数据(First In)会最先被读取出来(First Out)。

// 向队列中写入数据
EnQueue(123); // 写入123
EnQueue(456); // 写入456
EnQueue(789); // 写入789
// 从队列中读出数据
m = DeQueue();// 读出123
n = DeQueue(); // 读出456
o = DeQueue(); // 读出789
运行时队列的变化

队列的方式也称为排队,排队指的是买车票时在自动售票机前等候的队列。排队时,站在对前面的乘客先买票,购票后率先从队列中走出来。当随机前来的购票乘客数量和自动售票机的处理速度不相符时,排队能起到很好的缓冲作用。

程序也是如此,为了协调好数据输入和处理时机间的关系,采用类似于排队的机制是很方便的。在内存上实现这种机制的方式就是队列。

队列一般是以环状缓冲区(ring buffer)的方式来实现的

环状缓存区模型

假设要用6个元素的数组来实现一个队列,这时候可以从数组的起始位置开始有序地存储数据,然后再按照储存时的顺序把数据读出。在数组的末尾写入数据后,后一个数据就会被写入数组的起始位置,此时数据已经被读出所以该位置是空的。这样,数组的末尾和开头连接了起来,数据的写入和读出也就循环起来了。

链表使元素的追加和删除更容易

链表和二叉查找树都是不用考虑索引的顺序就可以对数组元素进行读写的方式,通过使用链表可以更加高效地对数组数据(元素)进行追加和删除处理。而通过使用二叉查找树,则可以更加高效地对数组数据进行检索。

在数组的各个元素中,除了数据的值外,通过为其附带上下一个元素的索引,即可实现链表。数据的值和下一个元素的索引组合在一起,就构成了数组的一个元素。这样,数组元素相连就构成了念珠似的链表。由于链表末尾的元素没有后续的数据,因此就需要用别的值来填充。

链表的初始状态

在需要追加或删除数据的情况下,使用链表是很高效的。

删除链表的第3个元素的方法
在链表中追加元素的方法

如果不使用链表数组,那么中途删除或追加元素时,其后的元素就必须全部移动。

单纯使用数组的情况下元素删除
单纯使用数组的情况下元素的追加

二叉查找树使数据搜索更有效

二叉查找树是指在链表的基础上向数组中追加元素时,考虑到数据的大小关系,将其分成左右两个方向的表现形式。

例如:将50个值保存到数组,如果接下来的值比先前保存的值大的话就将其放到右边,反之如果小的话就放到左边。实际的内存并不会分成两个方向,这是在程序逻辑上实现的。

二叉查找树模型(将数颠倒后的形状)

为了实现二叉查找树,数组的每个元素只要有数据的值和两个索引信息就可以了。二叉查找树是由链表构造发展而来的表现形式,因此在追加或删除元素方面也同样是有效的。

使用二叉查找树的便利之处在于可以使数据的搜索等更有效率。在使用一般的数组时,必须从数组的开头按照索引顺序来查找目标数据。而使用二叉查找树时,当目标数据比现在读出来的数据小时就可以转向左侧,反之目标数据较大时即可转到链表的右侧,这样就加快了找到目标数据的速度。

使用数组实现二叉查找树
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容