数据结构
1)计算机程序的灵魂计算机程序的灵魂
2)数据结构选取的好坏直接决定了算法效率的高低和实现的复杂程度
3)数据结构的组织与访问特性数据结构的组织与访问特性,,决定了算法的选取决定了算法的选取与实现
List<T>
/// <summary>
/// List泛型对象实际数据项的个数,一定要与区别_items.Length。(_items.Length >= _size)
/// </summary>
private int _size;
/// <summary>
/// 数据项被修改的次数,主要限制在检查过程中对数据的修改(在枚举中实现数据的同步)
/// </summary>
private int _version;
/// <summary>
/// 默认构造函数
/// </summary>
public List()
{
//_items(保存实际数据的数组)指向了Size为0的数组
this._items = List<T>._emptyArray;
}
1)采用数组保存数据
a) 数据项查找复杂度为O(n)
b) 下标查找复杂度O(1)
/// <summary>
/// 查找下标,复杂度为O(n)
/// </summary>
/// <param name="item">数据项</param>
/// <returns></returns>
public int IndexOf(T item)
{
return Array.IndexOf<T>(this._items, item, 0, this._size);
}
2)当数据空间不够时,扩大1倍空间
a) 将数据从原有缓冲区复制到新的缓冲区中
b) 带来O(n)的数据复制开销
c) 最坏可能带来sizeof(T) * (n – 1)的空间浪费:可以使用TrimExcess来缩紧
/// <summary>
/// 添加元素:不是线程安全的
/// </summary>
/// <param name="item">添加项</param>
public void Add(T item)
{
//判断当前数据已满,该方法非线程安全
if (this._size == this._items.Length)
{
this.EnsureCapacity(this._size + 1);
}
//已经有足够的空间了,添加数据。this._size 实际项数据++
//可以看到这句。多线程访问造成重复对_size赋值,而_size++造成某些项没有数据
this._items[this._size++] = item;
this._version++;
}
/// <summary>
/// 空间不够了,分配空间
/// </summary>
/// <param name="min">现在数组需求的最小的长度</param>
private void EnsureCapacity(int min)
{
if (this._items.Length < min) //如果空间不够
{
//如果当前数组容量为0,默认设为4。否则,将数组空间扩充为两倍
//两倍扩容造成数据的浪费,特别是在原数据量很大的时候。
//解决方法是:1.调用TrimExcess()方法;2.换用链表实现
int num = (this._items.Length == 0) 4 : (this._items.Length * 2);
if (num < min) // 是不是满足最小值
{
num = min;
}
this.Capacity = num; //容量扩充
}
}
/// <summary>
/// 数组最大容量
/// </summary>
public int Capacity
{
get
{
return this._items.Length;
}
set
{
if (value != this._items.Length) //要设置的长度和数组的长度不等
{
if (value < this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value,
ExceptionResource.ArgumentOutOfRange_SmallCapacity);
}
if (value > 0)
{
// 分配一个新的数组
T[] destinationArray = new T[value];
if (this._size > 0)
{
//复制原有的数组数据,数组大的时候会造成大的内存开销。
//严重影响性能,原有的内存空间变为垃圾,等待被回收。但是垃圾回收的时候,
//构建对象图的开销也是很大的。GC被唤醒也可能影响系统执行效率。
Array.Copy(this._items, 0, destinationArray, 0, this._size);
}
this._items = destinationArray;
}
else
{
this._items = List<T>._emptyArray;
}
}
}
}
/// <summary>
/// 紧缩数组
/// </summary>
public void TrimExcess()
{
int num = (int)(this._items.Length * 0.9);
if (this._size < num)
{
this.Capacity = this._size;
}
}
3)插入数据时最坏可能会带来能会带来n-1次数据复制
a) 小心InsertRange里面的效率陷阱:对于非System.Collections.Generic.ICollection<T>集合的操作
b) 用Insert插入多个数据可能会导致效率低下
/// <summary>
/// 插入数据:如果0位置插入数据,开销最大。
/// 另一种方法:逆转数组,尾部插入数据,逆转数组。开销要小
/// </summary>
/// <param name="index">下标</param>
/// <param name="item">数据项</param>
public void Insert(int index, T item)
{
if (index > this._size)//判断Index是不是合法,不合法就跑出异常
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_ListInsert);
}
if (this._size == this._items.Length)
{
this.EnsureCapacity(this._size + 1);//空间不足,扩容
}
if (index < this._size)
{
Array.Copy(this._items, index, this._items, index + 1, this._size - index);
}
this._items[index] = item;
this._size++;
this._version++;
}
/* 对InsertRange 方法的理解。(Count 为collection中包含的元素的数量)
* 在当前数组容量满足插入后容量时:
* 1、当collection为System.Collection,Generic.ICollection<T>类型,且collection不是指向自己时,该操作会涉及到(_size - index + 2*count)次数据复制操作。
* 2、当collection为System.Collection,Generic.ICollection<T>类型,且collection指向自己时,该操作会涉及到(2*size - index)次数据复制操作。
* 3、当collection不是System.Collection,Generic.ICollection<T>类型时,改操作会循环调用count次Insert函数。
*
* 分析情况1与使用Insert操作之间的复杂度比较:
* 在这种情况下,如果使用Insert方法完成相同的操作,需要(count * (_sizse - index))次操作,要求(_size -index + 2*count) > (count * (_size - index)),
* 化简,有:2*count > (count -1) * (_size - index)。
* 当count很大时,近似认为count等于count -1,因此有(_size - index) <2,index > _size -2,
* 由于index必然有index < _size,所以只有当 index = _size -1 时,该表达式成立,除此之外使用Index的效率都不会差于多次调用Insert方法。
*
* 对于情况2与使用Insert之间的复杂度比较:略。
* 对于情况3,实际等效于count次调用Insert操作的开销。
* 在当前数据容量不满足插入后容量时:
* 1. 当collection为System.Collection,Generic.ICollection<T>类型,且collection不是指向自己时,该操作会涉及到(2 * _size - index + 2*count)次数据复制操作。
* 2. 当collection为System.Collection,Generic.ICollection<T>类型,且collection指向自己时,该操作会涉及到(3 *_size - index)次数据复制操作。
* 结论:对于大量数据的插入,尽量使用InsertRange方法,而不是Insert方法。
* 另外,尽量保证插入的数据为System.Collection,Generic.ICollection<T>类型。
*/
/// <summary>
/// 插入一个数据集合
/// </summary>
/// <param name="index"></param>
/// <param name="collection"></param>
public void InsertRange(int index, IEnumerable<T> collection)
{
if (collection == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
}
if (index > this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index);
}
ICollection<T> is2 = collection as ICollection<T>;
//是否实害现了ICollection接口,性能是不一样的
if (is2 != null) //实现了ICollection接口
{
int count = is2.Count;
if (count > 0)
{
this.EnsureCapacity(this._size + count);//扩容处理
if (index < this._size)
{
//将原有缓冲区中的数据从index开始的数据项向后移动count个位置
Array.Copy(this._items, index, this._items, index + count, this._size - index);
}
//判断一下插入的集合is2是不是就是这个集合本身(不太理解)
if (this == is2)
{
Array.Copy(this._items, 0, this._items, index, index);
Array.Copy(this._items, (int)(index + count), this._items, (int)(index * 2), (int)(this._size - index));
}
else
{
T[] array = new T[count];
is2.CopyTo(array, 0);
array.CopyTo(this._items, index);
}
this._size += count;
}
}
else //这种方式效率低些
{
using (IEnumerator<T> enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
this.Insert(index++, enumerator.Current);
}
}
}
this._version++;
}
4)当删除数据时最坏可能带来n-1次数据复制
a) RemoveAt, RemoveRange, RemoveAll(Predict match)的复杂度在同一个数量级上
b) 对于一组数据的删除循环调用RemoveAt效率最低
/// <summary>
/// 该操作的复杂度为O(n),比较高效的数据删除
/// 不会涉及到创建新的缓冲区和大量数据项大缓冲区之间的复制操作
/// 对于删除符合特定条件的数据项,使用它们该函数的效率要高于外部通过条件判断来迭代调用RemoveAt删除多个数据项的效率
/// </summary>
/// <param name="match"></param>
/// <returns></returns>
public int RemoveAll(Predicate<T> match)
{
if (match == null)//简单的判断
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
int index = 0;
while ((index < this._size) && !match(this._items[index]))//对于不匹配的项,直接跳过
{
index++; //index会递增
}
if (index >= this._size)//没找到匹配项,返回0
{
return 0;
}
int num2 = index + 1;//当前index指向了可以删除的第一项,而num2指向了index的下一项
while (num2 < this._size)
{
while ((num2 < this._size) && match(this._items[num2]))
{
//如果this._items[num2]可以删除,
//则将num2递增,num2始终指向的是可以删除项的下一项
num2++;
}//此循环结束,num2指向了index后面的第一个不可删除的项
if (num2 < this._size) //如果数组没有遍历完毕
{
//将index后面的第一个不可删除的项赋值给index对应项(index对应数据可删除)
//执行此句之后,index指向了一下存储数据的位置,num2也后移一项
this._items[index++] = this._items[num2++];
}
}//继续遍历数组,当while结束,index指向了最后一个实际存储数据下标的下一项
//数组里面后面清零,同样数组长度没有缩减
Array.Clear(this._items, index, this._size - index);
int num3 = this._size - index;
this._size = index;
this._version++;
return num3;
}
/// <summary>
/// 删除数据项
/// 该操作会涉及到(_size - index)次数据移动开销
/// 注意:数据在删除后,保存数据 的数组容量并没有缩减,如果不对数据容量进行紧缩,可能出现数组中大量单元空闲,进而对内存的使用产生浪费
/// </summary>
/// <param name="index">下标</param>
public void RemoveAt(int index)
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
this._size--;
if (index < this._size)
{
//所有数据前移,覆盖第index项的值
Array.Copy(this._items, index + 1, this._items, index, this._size - index);
}
//最后一项设置为默认值,数组的大小是不变的
this._items[this._size] = default(T);
this._version++;
}
/// <summary>
/// 删除index开始的count项数据
/// 该操作会涉及到(_size - index)次数据移动开销
/// 对于RemoveRange调用性能优于count次单独的RemoveAt调用
/// 注意:数据在删除之后,柏村数据的数组容量并没有缩紧,
/// 如果不对数据容量进行缩紧,可能出现数组中大量单元空闲,进而对内存的使用产生浪费
/// </summary>
/// <param name="index">下标</param>
/// <param name="count">删除项数目</param>
public void RemoveRange(int index, int count)
{
if ((index < 0) || (count < 0))
{
ThrowHelper.ThrowArgumentOutOfRangeException((index < 0) ? ExceptionArgument.index : ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
if ((this._size - index) < count)
{
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
}
if (count > 0)
{
this._size -= count;
if (index < this._size)
{
Array.Copy(this._items, index + count, this._items, index, this._size - index);
}
//数组里面后面清零,同样数组长度没有缩减
Array.Clear(this._items, this._size, count);
this._version++;
}
}
5)提供QuickSort排序(快排)和二分查找的方法
6)对象在枚举操作时不能够被修改
/// <summary>
/// 在foreach迭代枚举过程中,.Net Framework 不允许修改List本身的数据(if 里面保证的)
/// </summary>
/// <returns></returns>
public bool MoveNext()
{
List<T> list = this.list;
//通过 version的判断,在每次数据移动的过程中会比较当前Enumerator的version 与当初拿到list的version是不是一样的
//如果不一样,就会抛出一个异常???
if ((this.version == list._version) && (this.index < list._size))
{
this.current = list._items[this.index];
this.index++;
return true;
}
return this.MoveNextRare();
}
7)非线程安全(如上面的 Add(T item)方法)
8)在多线程环境中需要竞争条件保护
9)构建lock-free的List<T>
SortedList<K,V>
1)Key与Value分别保存在两个数组中
a) Key在数组中有序排列
b) 保证K,V在数组中的下标相同
/// <summary>
/// 构造函数
/// </summary>
public SortedList()
{
//保存Key的数据
this.keys = SortedList<TKey, TValue>.emptyKeys;
//保存Value的数据
this.values = SortedList<TKey, TValue>.emptyValues;
this._size = 0;
//使用默认的排序比较器
this.comparer = Comparer<TKey>.Default;
}
2)当数据空间不够时,扩大1倍空间
a) 将数据从原有缓冲区复制到新的缓冲区中
b) 带来带来O((n))的数据复制开销的数据复制开销
c) 最坏可能带来sizeof(T) * (n – 1)的空间浪费:可以使用TrimExcess来缩紧
/// <summary>
/// 对于Capacity可能涉及到 2*_size 次数据复制和产生两个垃圾对象
/// </summary>
public int Capacity
{
get
{
return this.keys.Length;
}
set
{
if (value != this.keys.Length)
{
if (value < this._size)
{
System.ThrowHelper.ThrowArgumentOutOfRangeException(System.ExceptionArgument.value, System.ExceptionResource.ArgumentOutOfRange_SmallCapacity);
}
if (value > 0)
{
TKey[] destinationArray = new TKey[value];
TValue[] localArray2 = new TValue[value];
if (this._size > 0)
{
//同时对键和值进行扩容,以及产生两倍的内存垃圾
Array.Copy(this.keys, 0, destinationArray, 0, this._size);
Array.Copy(this.values, 0, localArray2, 0, this._size);
}
this.keys = destinationArray;
this.values = localArray2;
}
else
{
this.keys = SortedList<TKey, TValue>.emptyKeys;
this.values = SortedList<TKey, TValue>.emptyValues;
}
}
}
}
3)以Key检索数据时复杂度为O(log2 N):采用二分法查找
/// <summary>
/// 基于索引的查找复杂度为Log2(n),不同于List的O(1)
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public int IndexOfKey(TKey key)
{
if (key == null)
{
System.ThrowHelper.ThrowArgumentNullException(System.ExceptionArgument.key);
}
//也是基于二分查找
int num = Array.BinarySearch<TKey>(this.keys, 0, this._size, key, this.comparer);
if (num < 0)
{
return -1;
}
return num;
}
/// <summary>
/// 基于索引的查找复杂度为Log2(n),不同于List的O(1)
/// </summary>
/// <param name="key">键</param>
/// <returns>值</returns>
public TValue this[TKey key]
{
get
{
int index = this.IndexOfKey(key);
if (index >= 0) //根据索引找Value(Keys和Values共享相同的下标)
{
return this.values[index];
}
System.ThrowHelper.ThrowKeyNotFoundException();
return default(TValue);
}
set //下面类似
{
if (key == null)
{
System.ThrowHelper.ThrowArgumentNullException(System.ExceptionArgument.key);
}
int index = Array.BinarySearch<TKey>(this.keys, 0, this._size, key, this.comparer);
if (index >= 0)
{
this.values[index] = value;
this.version++;
}
else
{
this.Insert(~index, key, value);
}
}
}
4)在添加/插入数据时会先通过二分查找定位
a) 对原有缓冲区的部分数据进行移动对原有缓冲区的部分数据进行移动,誊出要插誊出要插入数据数据应该在的位置
b) 最坏情况下需要移动n-1个元素
/// <summary>
/// 添加操作:首先通过二分查找判断插入的key是否已经存在于数组中,如果是,则直接抛出异常
/// 否则,对二分查找的结果取补(为什么取补),该数值为新加入的(key,value)在数据中所应该操作的下标,调用Insert执行插入操作
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Add(TKey key, TValue value)
{
if (key == null)//key 非法
{
System.ThrowHelper.ThrowArgumentNullException(System.ExceptionArgument.key);
}
//二分查找keys里面有没有key下标
int num = Array.BinarySearch<TKey>(this.keys, 0, this._size, key, this.comparer);
if (num >= 0) //已经存在此项
{
System.ThrowHelper.ThrowArgumentException(System.ExceptionResource.Argument_AddingDuplicate);
}
this.Insert(~num, key, value);
}
/// <summary>
/// 插入操作:
/// 在数组不扩容的情况下,单个数据插入会涉及到 2*(_size - index)的数据复制
/// 在频繁的数据操作的情况下,使用SortedList的效率只有 List的1/2(SortedList对应两份数据的拷贝)
/// </summary>
/// <param name="index">下标</param>
/// <param name="key">键</param>
/// <param name="value">值</param>
private void Insert(int index, TKey key, TValue value)
{
if (this._size == this.keys.Length)//尝试扩容操作
{
this.EnsureCapacity(this._size + 1);
}
if (index < this._size) //不是在数据尾部
{
//将index后面的数据往后移
Array.Copy(this.keys, index, this.keys, index + 1, this._size - index);
Array.Copy(this.values, index, this.values, index + 1, this._size - index);
}
//赋值
this.keys[index] = key;
this.values[index] = value;
this._size++;
this.version++;
}
5)数据删除
/// <summary>
/// 单个数据删除会涉及到 2 * (_size - index)的数据复制
/// </summary>
/// <param name="index"></param>
public void RemoveAt(int index)
{
if ((index < 0) || (index >= this._size))
{
System.ThrowHelper.ThrowArgumentOutOfRangeException(System.ExceptionArgument.index, System.ExceptionResource.ArgumentOutOfRange_Index);
}
this._size--;
if (index < this._size)
{
Array.Copy(this.keys, index + 1, this.keys, index, this._size - index);
Array.Copy(this.values, index + 1, this.values, index, this._size - index);
}
this.keys[this._size] = default(TKey);
this.values[this._size] = default(TValue);
this.version++;
}
6)非线程安全
Stack<T>
1)数据以数组的形式保存
/// <summary>
/// 构造函数
/// </summary>
public Stack()
{
this._array = Stack<T>._emptyArray;
this._size = 0;
this._version = 0;
}
2)当数据空间不够时,扩大1倍空间
3)将数据从原有缓冲区复制到新的缓冲区中
a) 带来O((n))的数据复制开销
b) 最坏可能带来sizeof(T) * (n – 1)的空间浪费
4)数据数据Push/Pop的复杂度均为O(1)
a) 但是如果原有空间不足时,会使用O(n)的开销来进行缓冲区之间的复制
/// <summary>
/// 返回栈顶部数据项
/// </summary>
/// <returns></returns>
public T Peek()
{
if (this._size == 0)
{
System.ThrowHelper.ThrowInvalidOperationException(System.ExceptionResource.InvalidOperation_EmptyStack);
}
return this._array[this._size - 1];
}
/// <summary>
/// 出栈:栈顶元素返回并在栈中移除
/// </summary>
/// <returns></returns>
public T Pop()
{
if (this._size == 0)
{
System.ThrowHelper.ThrowInvalidOperationException(System.ExceptionResource.InvalidOperation_EmptyStack);
}
this._version++;
T local = this._array[--this._size];
this._array[this._size] = default(T);
return local;
}
/// <summary>
/// 压栈:在原有数据缓冲区不扩容的情况下,复杂度为O(1),否则需要涉及到_size次数据复制
/// </summary>
/// <param name="item"></param>
public void Push(T item)
{
if (this._size == this._array.Length)
{
T[] destinationArray = new T[(this._array.Length == 0) ? 4 : (2 * this._array.Length)];
Array.Copy(this._array, 0, destinationArray, 0, this._size);
this._array = destinationArray;
}
this._array[this._size++] = item;
this._version++;
}
5)数据查找开销为O(n)
/// <summary>
/// while循环迭代查找
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool Contains(T item)
{
int index = this._size;
EqualityComparer<T> comparer = EqualityComparer<T>.Default;
while (index-- > 0)
{
if (item == null)
{
if (this._array[index] == null)
{
return true;
}
}
else if ((this._array[index] != null) && comparer.Equals(this._array[index], item))
{
return true;
}
}
return false;
}
6)非线程安全
Queue<T>
1)通过数组和首尾下标指针组成环形队列
2)当空间不够时,根据扩张因子决定新缓冲区大小
a) 将数据从原有缓冲区复制到新的缓冲区中
b) 带来O(n)的数据复制开销
c) 最坏可能带来sizeof(T) * (n – 1)的空间浪费
/// <summary>
/// 设置空间
/// </summary>
/// <param name="capacity"></param>
private void SetCapacity(int capacity)
{
T[] destinationArray = new T[capacity];
if (this._size > 0)//当前有数据
{
if (this._head < this._tail)//环形队列头部数据项索引在尾部数据项索引前面
{
Array.Copy(this._array, this._head, destinationArray, 0, this._size);
}
else//环形队列头部数据项索引在尾部数据项索引后面
{
//需要拷贝两部分:_head 到 Length 和 0 到_tail
Array.Copy(this._array, this._head, destinationArray, 0, this._array.Length - this._head);
Array.Copy(this._array, 0, destinationArray, this._array.Length - this._head, this._tail);
}
}
this._array = destinationArray;
this._head = 0;
this._tail = (this._size == capacity) ? 0 : this._size;
this._version++;
}
3)入队和出队复杂度为O(1)
a) 但是如果原有空间不足时,会使用O(n)的开销来进行缓冲区之间的复制
/// <summary>
/// 入队
/// </summary>
/// <param name="item"></param>
public void Enqueue(T item)
{
if (this._size == this._array.Length) //需要扩充空间
{
//默认也是扩展为2倍。
//增长因子为200
int capacity = (int)((this._array.Length * 200L) / 100L);
if (capacity < (this._array.Length + 4))
{
//保证了每次扩展空间有一个下线,总能增长 4个空间
capacity = this._array.Length + 4;
}
this.SetCapacity(capacity);
}
this._array[this._tail] = item;
//取余:环形队列
this._tail = (this._tail + 1) % this._array.Length;
this._size++;
this._version++;
}
4)非线程安全
注意:List, SortedList, Stack, Queue
1)均采用数组作为实现数据结构的基础
2)在原有空间不足时,均要创建新的缓冲区并且把数据复制过去
3)均无法保证线程安全均无法保证线程安全
LinkedList<T>
1)结点为LinkedListNode<T>对象
2)外部表现为双向线性链表
a) 通过Next, Previous对next, previous包装实现:Next, Previous均为只读
b) 首尾不相接构成环
3) 内部实现实际为双向环状链表:对next, previous操作
4) 无法保证线程安全
/// <summary>
/// 用于同步的
/// </summary>
private object _syncRoot;
/// <summary>
/// 双向环状链表的第一个节点,该节点为有效的数据结构,链表没有哨兵节点
/// </summary>
internal LinkedListNode<T> head;
public LinkedListNode<T> AddAfter(LinkedListNode<T> node, T value){
//先验证节点合法性
this.ValidateNode(node);
//创建新的节点
LinkedListNode<T> newNode = new LinkedListNode<T>(node.list, value);
//将 newNode 节点插入到node后面
this.InternalInsertNodeBefore(node.next, newNode);
return newNode;
}
/// <summary>
/// 在node节点前面插入一个节点newNode
/// </summary>
/// <param name="node"></param>
/// <param name="newNode"></param>
private void InternalInsertNodeBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)
{
newNode.next = node;
newNode.prev = node.prev;
node.prev.next = newNode;
node.prev = newNode;
this.version++;
this.count++;
}
private void InternalInsertNodeToEmptyList(LinkedListNode<T> newNode)
{
newNode.next = newNode;
newNode.prev = newNode;
this.head = newNode;
this.version++;
this.count++;
}
/// <summary>
/// 链表的数据序列化
/// </summary>
/// <param name="info"></param>
/// <param name="context"></param>
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
// 需要序列化的值填入info中
info.AddValue("Version", this.version); //版本
info.AddValue("Count", this.count); //节点个数
if (this.count != 0)
{
T[] array = new T[this.Count];
this.CopyTo(array, 0); //复制到数组array中
info.AddValue("Data", array, typeof(T[])); //数组的 Data
}
}
/// <summary>
/// 反序列化
/// </summary>
/// <param name="sender"></param>
public virtual void OnDeserialization(object sender)
{
if (this.siInfo != null)
{
int num = this.siInfo.GetInt32("Version");//取出Version
if (this.siInfo.GetInt32("Count") != 0) //取出Count
{
T[] localArray = (T[])this.siInfo.GetValue("Data", typeof(T[]));//取出数组
if (localArray == null)
{
throw new SerializationException(SR.GetString("Serialization_MissingValues"));
}
// 循环,将数组的值加入本对象
for (int i = 0; i < localArray.Length; i++)
{
this.AddLast(localArray[i]);
}
}
else
{
this.head = null;
}
this.version = num;
this.siInfo = null;
}
}
/// <summary>
/// 获取首部节点
/// </summary>
public LinkedListNode<T> First
{
get
{
return this.head;
}
}
/// <summary>
/// 获取尾部节点:实际是不保存尾部节点的,通过头结点来获取。减少了复杂度:O(1)
/// </summary>
public LinkedListNode<T> Last
{
get
{
//外部表现为双向值链表,内部实现为双向环链表
if (this.head != null)//如果头不为空,返回其前一个节点
{
return this.head.prev;
}
return null;
}
}
节点的实现:LinkedListNode<T>
/// <summary>
/// 保存了节点所在链表的引用,注意一下
/// </summary>
internal LinkedList<T> list;
/// <summary>
/// 前一个节点:只读的
/// </summary>
public LinkedListNode<T> Previous
{
get
{
//如果prev为空,或者当前节点为头结点的话,返回null
if ((this.prev != null) && (this != this.list.head))
{
return this.prev;
}
return null;
}
}
/// <summary>
/// 下一个节点:只读的
/// </summary>
public LinkedListNode<T> Next
{
get
{
//如果next指向了null,或者指向了链表的头指针的话,返回null
if ((this.next != null) && (this.next != this.list.head))
{
return this.next;
}
return null;
}
}
LockFree 数据结构(不加锁的数据结构)
LockFree是在CPU支持多核,以及并行处理环境开始普及,其在高性能应用程序开发的时候所重视。
加锁:.Net Framework 的所有泛型都是非线程安全的,如果不加锁的话多线程访问就有可能出现问题。解决办法就是对于可能产生竞争条件的临界区加锁,如:lock、信号量等线程同步操作,但是会牺牲程序的性能为代价。例如:多线程访问加锁之后,除了进入临界区的线程,其它的线程都是处理等待状态。等待:如果这个锁不是自旋锁,系统需要从用户态切换到内核态,操作系统在内核态会对当前线程列表进行维护操作,如把线程挂起,完成操作之后,系统又从内核态切换到用户态,两次切换会消耗很多CPU指令,第二个开销的地方是当线程从临界区退出,操作系统判断临界区没有线程访问,就会回到线程列表中找到可以被唤醒的线程,唤醒线程让它进入临界区中,如果OS实现不好,可能会将所有在此临界区等待的线程唤醒,这种情况下,可以知道只有一个线程可以进入临界区,其它的线程需要重新进入等待状态,造成了很大的CPU的开销。
改进:自旋锁出现。如果临界区中有线程,其它访问的线程会"自旋"一段时间。可以理解为空循环,如果在此时间内,线程退出了后,空循环的线程就可以进入临界区了,如果在空循环的时间内临界区还是在占用,就进入上面的过程。空循环相对与状态切换还是可以带来性能上的提升。
对于系统状态切换,可以控制并行,同时又可以提高对于锁性能的方法:LockFree。它实现的方法是通过CPU提供的比较交换的原子指令实现LockFree的数据结构。
1)以“比较-交换” (CAS) 原子操作为其工作原理:如果A与B相等,那么将C赋值给A
原子操作:最小的操作,不会有其它的操作影响,最基本的操作结构。
2)避免通常加锁所导致的严重性能开销
a) 内核态与用户态之间的切换开销
b) 线程调度开销
3)实现更细力度的并行控制
a) 提高提高吞吐量
b) 在CPU指令级别的操作,它在并行控制的时候,可以细化到CPU执行的并行指令,而不是像信号量一样通过一大段代码实现。
3)并行环境(多CPU)下性能显著提升
a) 有些情况下可以达到上千倍的关键业务的性能提升
4)天下没有免费的午餐,LockFree也存在很多问题:
a) 与硬件体系结构的内存读写模型相关:存在移植问题
对于LockFree基于CAS的操作,虽然是原子性的,但是就具体的硬件体系而言,其内部会把其拆为具体的微指令,另一部分,对于不同的硬件体系结构而言,可能会对CPU指令乱序的执行,如果当前的硬件体系结构不能很好的符合LockFree的要求的话,就没有在这种硬件体系结构中来使用LockFree的数据结构,例如:有一些指令序列R1,R2,W1,R3,W2,R4,R代表读,W代表写;顺序执行:CPU按照顺序执行R1,R2......。但是如果按照顺序执行的话,CPU执行的效率并不一定是高的。因此,不同的CPU会对指令进行排序,CPU就会乱序的处理,对于LockFree的数据结构,当CPU支持这种并发的乱序的操作的时候,LockFreek可能不好用。LockFree虽然叫做锁无关,但是在最下面还是会加锁的,只不过锁的粒度很细,细化到了硬件的水平。可以看出,LockFree和具体的硬件体系结构是相关的。
对于不同的硬件体系结构,在做LockFree的具体CPU指令时候操作方法是不一样的。但是可以通过一些方式(内存栈等)使得硬件内存读写模型符合LockFree的特点。LockFree数据机构就存在了移植的问题,硬件改变,代码可能就需要改写。
但是,在.Net中,提供了InterLocked的静态类,里面就有cmpxchg的指令。.Net Framework屏蔽了底层的实现,并且CLR 的内存读写模型是很好的实现LockFree数据结构的模型,因此在.Net里面实现起来没有问题。
b) 实现复杂,其正确性很难被证明:例如:ABA 问题
因为他要使用“比较—交换”来保证线程安全的操作,其能否实现需要受限CPU指令的实现,比如:CPU只支持32bit的"比较-交换",如果我们现在要做128bit的"比较-交换",实现起来就比较困难。以前看起来很简单的数据结构,在LockFree实现起来比较复杂。而且我们在看代码的时候,其正确性也是很难被证明的。典型的实现复杂数据结构就是LockFree的堆,链表等等。可以从网上查找相关的资料。
ABA的问题:也是基于"比较-交换"衍生出来的问题。
c) 代码难以维护
d) 存在活锁问题
进行 LockFree改进
1)LockFreeStack:实现不同于.Net实现的栈,比较-交换在每次压栈出栈的时候,判断栈顶是不是原有的数值,如果不是的话等待。
a) 采用链表结构
b) 实现相对简单
c) 在处理前比较栈顶是否是原有数值
/// <summary>
/// 可能发生ABA问题:
/// 内部节点保留一个计数器,通过计数器来判断(LockFreeWQueue的时候讲解)
/// </summary>
/// <typeparam name="T"></typeparam>
public class LockFreeStack<T>
{
/// <summary>
/// 节点类
/// </summary>
/// <typeparam name="T"></typeparam>
class ListNode<T>
{
public T value;
/// <summary>
/// 下一个节点
/// </summary>
public ListNode<T> next;
public ListNode(T t)
{
value = t;
next = null;
}
}
private ListNode<T> topNode;
public bool IsEmpty()
{
if (null == topNode)
{
return true;
}
return false;
}
/// <summary>
/// 并行处理的问题:
/// 关键的操作是在CompareExchange里面操作
/// 乐观锁:只有需要的时候,操作加锁
/// 悲观锁:开始就加锁
/// </summary>
/// <param name="item"></param>
public void Push(T item)
{
ListNode<T> newTopNode = new ListNode<T>(item);
ListNode<T> oldTopNode;
do
{
oldTopNode = topNode;
//1. 让新节点的下一个节点指向原有的顶部节点
newTopNode.next = oldTopNode;
} while (Interlocked.CompareExchange<ListNode<T>>(ref topNode, newTopNode, oldTopNode) != oldTopNode);
//Interlocked.CompareExchange<ListNode<T>>(ref T topNode, T newTopNode, T oldTopNode):原子操作
//比较topNode 和 oldTopNode的值是不是相等(在这里就是判断topNode有没有被其它线程修改),如果相等的话(没有被修改)则将newTopNode赋值给topNode
//此方法的返回值:topNode的原有值
}
/// <summary>
/// 出栈
/// </summary>
/// <returns></returns>
public T Pop()
{
ListNode<T> oldTopNode, newTopNode;
do
{
oldTopNode = topNode;
//当栈为null时,由于非法的地址访问会抛出异常
newTopNode = topNode.next;
} while (Interlocked.CompareExchange<ListNode<T>>(ref topNode, newTopNode, oldTopNode) != oldTopNode);
T item = oldTopNode.value;
return item;
}
public T Peak()
{
//如果栈为空,由于topNode地址非法,系统会抛出异常
return topNode.value;
}
}
2)LockFreeQueue
a) 采用链表结构
b) 实现相对复杂
c) 需要处理好Tail结点,Tail->next接点和Head与Tail之间数据的关系
d) 在处理前首先找到真正队尾位置,然后比较交换
e) 内部维护的Head, Tail成员可能并没有指向真正的队列首部与尾部
public class LockFreeQueue<T>
{
class node_t
{
public T value;
public pointer_t next;
/// <summary>
/// 默认构造函数
/// </summary>
public node_t()
{ }
}
struct pointer_t
{
/// <summary>
/// 用于解决ABA的问题
/// </summary>
public long count;
public node_t ptr;
/// <summary>
/// copy constructor
/// </summary>
/// <param name="p"></param>
public pointer_t(pointer_t p)
{
ptr = p.ptr;
count = p.count;
}
public pointer_t(node_t node, long s)
{
ptr = node;
count = s;
}
}
private pointer_t Head;
private pointer_t Tail;
public LockFreeQueue()
{
node_t node = new LockFreeQueue<T>.node_t();
Head.ptr = Tail.ptr = node;
}
private bool CAS(ref pointer_t destination, pointer_t compared, pointer_t exchange)
{
if (compared.ptr == Interlocked.CompareExchange(ref destination.ptr, exchange.ptr, compared.ptr))
{
Interlocked.Exchange(ref destination.count, exchange.count);
return true;
}
return false;
}
/// <summary>
/// 出队
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public bool DeQueue(ref T t)
{
pointer_t head;
bool bDeQueue = true;
while (bDeQueue)
{
head = Head;
pointer_t tail = Tail;
pointer_t next = head.ptr.next;
if (head.count == Head.count && head.ptr == Head.ptr)
{
if (head.ptr == tail.ptr)
{
if (null == next.ptr) //队列为空
{
return false;
}
CAS(ref Tail, tail, new pointer_t(next.ptr, tail.count + 1));
}
else
{
t = next.ptr.value;
if (CAS(ref Head, head, new LockFreeQueue<T>.pointer_t(next.ptr, head.count + 1)))
{
bDeQueue = false;
}
}
}
}
return true;
}
/// <summary>
/// 入队
/// </summary>
/// <param name="t"></param>
public void EnQueue(T t)
{
node_t node = new LockFreeQueue<T>.node_t();
bool bEnQueue = true;
while (bEnQueue)
{
//记住尾部节点
pointer_t tail = Tail;
//记住尾部节点的下一个节点
pointer_t next = tail.ptr.next;
if (tail.count == Tail.count && tail.ptr == Tail.ptr)
{
if (null == next.ptr)//是一个真正的尾部节点
{
if (CAS(ref tail.ptr.next, next, new pointer_t(node, next.count + 1)))//完成入队操作
{
bEnQueue = false;
}
}
else
{
//其他并发请求入队操作完成,当前操作需要继续获得真正的入队操作:将tail后移
CAS(ref Tail, tail, new LockFreeQueue<T>.pointer_t(next.ptr, tail.count + 1));
}
}
}
}
}