.NET中自带的链表是LinkedList类,并且已经直接实现成了双向循环链表。

其节点类LinkedListNode的数据结构如下,数据项包括指示到某个链表的引用,以及左,右节点和值。

  1. public sealed class LinkedListNode<T>
  2. {
  3. internal LinkedList<T> list;
  4. internal LinkedListNode<T> next;
  5. internal LinkedListNode<T> prev;
  6. internal T item;
  7. }

另外,获取前一个节点和后一个节点的实现如下:注意:里面的if-else结构的意义是当前一个(后一个)节点不为空且不是头节点时才不返回null,这样做的意义是当链表内只有1个节点时,其prev和next是指向自身的。

  1. [__DynamicallyInvokable]
  2. public LinkedListNode<T> Next
  3. {
  4. [__DynamicallyInvokable] get
  5. {
  6. if (this.next != null && this.next != this.list.head)
  7. return this.next;
  8. else
  9. return (LinkedListNode<T>) null;
  10. }
  11. }
  12. [__DynamicallyInvokable]
  13. public LinkedListNode<T> Previous
  14. {
  15. [__DynamicallyInvokable] get
  16. {
  17. if (this.prev != null && this != this.list.head)
  18. return this.prev;
  19. else
  20. return (LinkedListNode<T>) null;
  21. }
  22. }

过有一个把链表置为无效的方法定义如下:

  1. internal void Invalidate()
  2. {
  3. this.list = (LinkedList<T>) null;
  4. this.next = (LinkedListNode<T>) null;
  5. this.prev = (LinkedListNode<T>) null;
  6. }

而LinkedList的定义如下:主要的两个数据是头节点head以及长度count。

  1. public class LinkedList<T> : ICollection<T>, IEnumerable<T>, ICollection, IEnumerable, ISerializable, IDeserializationCallback
  2. {
  3. internal LinkedListNode<T> head;
  4. internal int count;
  5. internal int version;
  6. private object _syncRoot;
  7. private SerializationInfo siInfo;
  8. private const string VersionName = "Version";
  9. private const string CountName = "Count";
  10. private const string ValuesName = "Data";

而对此链表的主要操作,包括:

  • 插入节点到最后,Add(),也是AddLast()。
  • 在某个节点后插入,AddAfter(Node, T)。
  • 在某个节点前插入,AddBefore(Node, T)。
  • 插入到头节点之前,AddFirst(T)。
  • 清除所有节点,Clear()。
  • 是否包含某个值,Contains(T),也就是Find()。
  • 查找某个节点的引用,Find()和FindLast()。
  • 复制到数组,CopyTo(Array)
  • 删除某个节点,Remove(T)。
另外有几个内部方法,用来支撑复用插入和删除操作:
  • 内部插入节点,InternalInsertNodeBefore()
  • 内部插入节点到空链表,InternalInsertNodeToEmptyList()
  • 内部删除节点,InternalRemoveNode()
  • 验证新节点是否有效,ValidateNewNode()
  • 验证节点是否有效,ValidateNode()
 
插入新节点到链表最后的代码如下:
  1. public void AddLast(LinkedListNode<T> node)
  2. {
  3. this.ValidateNewNode(node);
  4. if (this.head == null)
  5. this.InternalInsertNodeToEmptyList(node);
  6. else
  7. this.InternalInsertNodeBefore(this.head, node);
  8. node.list = this;
  9. }

插入操作的第一步是验证节点是否有效,即节点不为null,且节点不属于其他链表。

  1. internal void ValidateNewNode(LinkedListNode<T> node)
  2. {
  3. if (node == null)
  4. throw new ArgumentNullException("node");
  5. if (node.list != null)
  6. throw new InvalidOperationException(SR.GetString("LinkedListNodeIsAttached"));
  7. }

如果头节点为空,则执行插入到空链表的操作:将节点的next和prev都指向为自己,并作为头节点。

  1. private void InternalInsertNodeToEmptyList(LinkedListNode<T> newNode)
  2. {
  3. newNode.next = newNode;
  4. newNode.prev = newNode;
  5. this.head = newNode;
  6. ++this.version;
  7. ++this.count;
  8. }

如果头节点不为空,则执行插入到头节点之前(注:因为是双向链表,所以插到头节点之前就相当于插到链表的最后了),具体的指针指向操作如下:

  1. private void InternalInsertNodeBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)
  2. {
  3. newNode.next = node;
  4. newNode.prev = node.prev;
  5. node.prev.next = newNode;
  6. node.prev = newNode;
  7. ++this.version;
  8. ++this.count;
  9. }

而插入新节点到指定节点之后的操作如下:同样还是调用的内部函数,把新节点插入到指定节点的下一个节点的之前。有点绕,但确实让这个内部函数起到多个作用了。

  1. public void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode)
  2. {
  3. this.ValidateNode(node);
  4. this.ValidateNewNode(newNode);
  5. this.InternalInsertNodeBefore(node.next, newNode);
  6. newNode.list = this;
  7. }

而插入新节点到指定节点之前的操作如下:直接调用插入新节点的内部函数,另外还要判断指定的节点是否是头节点,如果是的话,要把头节点变成新的节点。

  1. public void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)
  2. {
  3. this.ValidateNode(node);
  4. this.ValidateNewNode(newNode);
  5. this.InternalInsertNodeBefore(node, newNode);
  6. newNode.list = this;
  7. if (node != this.head)
  8. return;
  9. this.head = newNode;
  10. }

把新链表插入到第一个节点(也就是变成头节点)的操作如下:如果链表为空就直接变成头节点,否则就插入到头节点之前,取代头节点。

  1. public void AddFirst(LinkedListNode<T> node)
  2. {
  3. this.ValidateNewNode(node);
  4. if (this.head == null)
  5. {
  6. this.InternalInsertNodeToEmptyList(node);
  7. }
  8. else
  9. {
  10. this.InternalInsertNodeBefore(this.head, node);
  11. this.head = node;
  12. }
  13. node.list = this;
  14. }

查找链表中某个值的操作如下:注意直接返回null的条件是头节点为空。然后就是遍历了,因为是双向链表,所以要避免死循环(遍历到头节点时跳出)。

  1. public LinkedListNode<T> Find(T value)
  2. {
  3. LinkedListNode<T> linkedListNode = this.head;
  4. EqualityComparer<T> @default = EqualityComparer<T>.Default;
  5. if (linkedListNode != null)
  6. {
  7. if ((object) value != null)
  8. {
  9. while (!@default.Equals(linkedListNode.item, value))
  10. {
  11. linkedListNode = linkedListNode.next;
  12. if (linkedListNode == this.head)
  13. goto label_8;
  14. }
  15. return linkedListNode;
  16. }
  17. else
  18. {
  19. while ((object) linkedListNode.item != null)
  20. {
  21. linkedListNode = linkedListNode.next;
  22. if (linkedListNode == this.head)
  23. goto label_8;
  24. }
  25. return linkedListNode;
  26. }
  27. }
  28. label_8:
  29. return (LinkedListNode<T>) null;
  30. }

删除某个节点的操作如下:

  1. public void Remove(LinkedListNode<T> node)
  2. {
  3. this.ValidateNode(node);
  4. this.InternalRemoveNode(node);
  5. }

同样,内部删除节点的实现如下:如果节点指向自己,说明是头节点,所以直接把头节点置null。然后就是指针的指向操作了。

  1. internal void InternalRemoveNode(LinkedListNode<T> node)
  2. {
  3. if (node.next == node)
  4. {
  5. this.head = (LinkedListNode<T>) null;
  6. }
  7. else
  8. {
  9. node.next.prev = node.prev;
  10. node.prev.next = node.next;
  11. if (this.head == node)
  12. this.head = node.next;
  13. }
  14. node.Invalidate();
  15. --this.count;
  16. ++this.version;
  17. }

而清空链表的操作如下:遍历链表,逐个设置为无效,最后将内部的头节点也置为null。

  1. public void Clear()
  2. {
  3. LinkedListNode<T> linkedListNode1 = this.head;
  4. while (linkedListNode1 != null)
  5. {
  6. LinkedListNode<T> linkedListNode2 = linkedListNode1;
  7. linkedListNode1 = linkedListNode1.Next;
  8. linkedListNode2.Invalidate();
  9. }
  10. this.head = (LinkedListNode<T>) null;
  11. this.count = 0;
  12. ++this.version;
  13. }
 
链表转数组的实现如下:首先判断入参的有效性,然后从头节点开始遍历,依次复制到数组中,直到头结点(尽头)。
  1. public void CopyTo(T[] array, int index)
  2. {
  3. if (array == null)
  4. throw new ArgumentNullException("array");
  5. if (index < 0 || index > array.Length)
  6. {
  7. throw new ArgumentOutOfRangeException("index", SR.GetString("IndexOutOfRange", new object[1]
  8. {
  9. (object) index
  10. }));
  11. }
  12. else
  13. {
  14. if (array.Length - index < this.Count)
  15. throw new ArgumentException(SR.GetString("Arg_InsufficientSpace"));
  16. LinkedListNode<T> linkedListNode = this.head;
  17. if (linkedListNode == null)
  18. return;
  19. do
  20. {
  21. array[index++] = linkedListNode.item;
  22. linkedListNode = linkedListNode.next;
  23. }
  24. while (linkedListNode != this.head);
  25. }
  26. }

以上。

.NET源码中的链表的更多相关文章

  1. ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开

    ASP.NET MVC Filters 4种默认过滤器的使用[附示例]   过滤器(Filters)的出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每个请求都会响 ...

  2. Android 源码中的设计模式

    最近看了一些android的源码,发现设计模式无处不在啊!感觉有点乱,于是决定要把设计模式好好梳理一下,于是有了这篇文章. 面向对象的六大原则 单一职责原则 所谓职责是指类变化的原因.如果一个类有多于 ...

  3. 从express源码中探析其路由机制

    引言 在web开发中,一个简化的处理流程就是:客户端发起请求,然后服务端进行处理,最后返回相关数据.不管对于哪种语言哪种框架,除去细节的处理,简化后的模型都是一样的.客户端要发起请求,首先需要一个标识 ...

  4. Android 网络框架之Retrofit2使用详解及从源码中解析原理

    就目前来说Retrofit2使用的已相当的广泛,那么我们先来了解下两个问题: 1 . 什么是Retrofit? Retrofit是针对于Android/Java的.基于okHttp的.一种轻量级且安全 ...

  5. Eclipse与Android源码中ProGuard工具的使用

    由于工作需要,这两天和同事在研究android下面的ProGuard工具的使用,通过查看android官网对该工具的介绍以及网络上其它相关资料,再加上自己的亲手实践,算是有了一个基本了解.下面将自己的 ...

  6. String源码中的"avoid getfield opcode"

    引言: 之前一篇文章梳理了String的不变性原则,还提到了一段源码中注释"avoid getfield opcode",当时通过查阅资料发现,这是为了防止 getfield(获取 ...

  7. android源码中修改wifi热点默认始终开启

    在项目\frameworks\base\wifi\java\android\net\wifi\WifiStateMachine.java里面,有如下的代码,是设置wifi热点保持状态的:如下: pri ...

  8. rxjava源码中的线程知识

    rxjava源码中的线程知识 rx的最精简的总结就是:异步 这里说一下以下的五个类 1.Future2.ConcurrentLinkedQueue3.volatile关键字4.AtomicRefere ...

  9. MMS源码中异步处理简析

    1,信息数据的查询,删除使用AsycnQueryHandler处理 AsycnQueryHandler继承了Handler public abstract class AsyncQueryHandle ...

随机推荐

  1. JavaScript:原生模拟$.ajax以及jsonp

    现实项目中,常常会用到请求,但是在考虑低版本的浏览器时,promise相关的axios,fetch这类第三方库的支持率就不那么好了,再考虑到最大的一个问题,跨域,更是让人头痛,虽然也有fetch-js ...

  2. 做到让DBCP连接池不超时

    前些天部署了一个项目,但每次隔一段时间打开都会报如下所示的错误:  javax.servlet.ServletException: org.springframework.transaction.Ca ...

  3. poj 3279(暴力)

    题意:有一个n*m的格子,每个格子都有黑白两面(0表示白色,1表示黑色).我们需要把所有的格子都反转成黑色,每反转一个格子,它上下左右的格子都会跟着反转.请求出用最小步数完成反转时每个格子反转的次数. ...

  4. str相关操作

    大小写转换:*——记住 * upper() 全大写 title() 首字母大写(只要是不属于英文字母的都是分隔符) 切来切去: center(10,'*') 强行用*在原字符串左右两端拼接,拼接成十个 ...

  5. Struts2下载及简介

    Struts2下载及简介 一.Struts2下载: 进入网址:http://struts.apache.org/download.cgi#struts23163 可以下载最新的Struts2. 1.F ...

  6. MySQL--事务隔离级别RR和RC的异同

    在MySQL中,事务隔离级别RC(read commit)和RR(repeatable read)两种事务隔离级别基于多版本并发控制MVCC(multi-version concurrency con ...

  7. 使用 Resharper 快速做适配器

    如果需要做一个类的重写,需要重新写这个类的所有属性和函数,本文提供一个简单的方法让大家快速重写一个类的所有属性和函数. 在有 Resharper 之后,对一个比较长的类进行重构.例如有这个类 clas ...

  8. AJAX从入门到放弃(一)

    AJAX可以用于创建快速动态的网页(无需重新加载整个网页的情况下,能够更新部分网页的技术) 即异步的Javascript和XML,通过后台与服务器进行少量数据交换,AJAX可以使网页实现异步更新. A ...

  9. Codeforces gym101612 E.Equal Numbers(贪心)

    传送:http://codeforces.com/gym/101612 题意:给出一个大小为n的序列a[i],每次选其中一个数乘以一个正整数,问进行k步操作后最少剩下多少种数字,输出0≤k≤n,所有的 ...

  10. Http Header 之 Requests Header 和 Responses Header

    在开发中,经常会遇到对网络请求添加相应的头信息,下面我们梳理一下Http Header相关的内容. 一.Requests Header Header 解释 示例 Accept 指定客户端能够接收的内容 ...