各位未来的高级程序员好久不见,好久没有更新数据结构和算法了。差点忘记这一方面的实战才是我的强项。那么我们就来上点猛料,有些人干了三年的开发可能都不知道的神奇用法-Linux内核双向链表。这名字一听就很高大上,相信各位已经摩拳擦掌,按捺不住了。那么我们废话不多说,赶紧发车。
在数据结构中,有两种最基础的数据结构,一种是顺序结构,一种是链式结构。这两种结构都非常的重要。我们称之为顺序表和链表。其中链表区分有头链表,无头链表,单链表,双向链表,循环链表等。那么Linux内核中使用的是无头双向链表,其中区别于普通的双向链表。那么内核的双向链表到底是怎么玩的呢?别急,我们先来对比一下传统的双向链表:
typedef struct _DoubleList
{
void *data; //数据域
struct _DoubleList *last; //下一个指针域
struct _DoubleList *front; //上一个指针域
}DoubleList; //双向链表基本结构体
如上所示,我们普通的双向链表其实就长这样,一个数据域,两个指针域。数据域用来搭载数据,指针域用来连接上一个节点和下一个节点。对于老C程序员来说手写链表其实并不难,可以说是平平无奇,那么Linux内核的链表究竟优秀在哪里呢?对比我们上述的普通双向链表有何强大的地方?
我们接下来揭晓谜底,请看如下结构体:
typedef struct _LinuxList
{
struct _LinuxList *next; //下一个指针域
struct _LinuxList *front; //上一个指针域
}LinuxList,LinuxNode;
怎么样?是不是很惊讶,这就是传说中的内核双向链表?平平无奇,甚至还有点简单?高端的东西往往不在于形,而在于使用。那么需要怎么使用这个内核链表呢?
实际上,Linux内核的双向链表是利用了数据与承载数据的结构分离的模式,所以才会设计成这个样子,那么这个模式是怎么样的呢?知其然知其所以然,那么请看下图:
快递大家都知道吧?快递内的物品是主体,而快递外面那层打包盒,就是我们的载体。现在就有人要问了,这个跟我们的链表有什么关系呢?别急,关系非常大。一般情况下,如果以快递公司为视角来看,快递一旦发出,外面那层包装盒是不是就无法进行第二次利用了?相当于把快递盒送给了物品的主人,这样的关联性太强了,对于我们商业代码来说,有一个准则,高内聚,低耦合。那么数据与数据结构的关联性过强,实际上也是不利的。
那么我们的Linux内核双向链表是怎么做的呢?来,请看下图:
怎么样?看起来是不是很像火车的样子,装货的火车就是这种结构。这个结构的优点在于,数据和载体只有依赖关系,而不是共存亡。有点像UML中的聚合关系,那么到此为止,是不是有个大概的概念了呢?所以这么才能达到图中的效果呢?请看如下结构体:
typedef struct _Star
{
int radius;
int colour;
int x;
int y;
}Star;
上面是一个星星的结构体,里面包含了星星部分属性,包含了半径,坐标,颜色亮度等。如果我们需要做一个星空的话,那么就需要大量的星星变量。这里或许有人疑问了,可以直接使用数组啊,为什么还要使用链表呢?那么请大家思考一下,如果其中的星星会随着亮度的减弱而慢慢消失的话,那么是不是还需要对内存进行回收呢?如果不进行回收的话,那么已经使用完毕的内存放着岂不是造成了资源的浪费?这对一个高级程序员来说是致命的。
那么我们怎么使用这个内核的双向链表呢?其实很简单,如下所示:
typedef struct _Star
{
int radius;
int colour;
int x;
int y;
LinuxNode *n;
}Star;
这样就能达到我说的效果了,但是有人会问,虽然看起来像是车厢一样,但是访问链表没办法访问结构体变量吧?在C语言中有一个宏,offsetof(type, member-designator),其中第一个参数是结构体类型,第二个是结构体类型中的成员变量。那么这个是什么意思呢?实际上就是计算字节偏移数,计算出结构体的成员变量与结构体顶端的字节偏移。
那么有了这个宏之后呢,就可以完美解决找不到结构体对象内存地址的问题了,但是请注意,由于LinuxNode*n结构体成员变量是指针,所以使用的时候一定要搞清楚内存,必须掌握熟练二级指针的水平,否则玩不来的。
最后,大家可以试一下,看看能不能玩起来,如果玩不起来的话可以评论区讨论一下,我会统一做解答,我是程序员玉无涯,一位资深程序员,我们下次见。