无论是常用的List<T>、Hashtable还是ListDictionary<TKey,TValue>,在保存值的时候都是无序的,而今天要介绍的集合类SortedList和SortedList<TKey,TValue>在保存值的时候是有序保存。

SortedList之二分查找法

一个集合有序,意味着什么?意味着可以利用一些算法来提高遍历集合时的效率,最常见的就是运用二分查找法,SortedList集合的核心就是运用二分查找。

SortedList保存数据时和哈希表一样用Key-Value的方式进行存储,但不同的是,它把Key和Value分别保存在两个object[]数组中,每次插入删除操作都会保持这两个object[]大小的同步性。

SortedList在初始化时如果不指定大小,则会给一个默认的十六进制值0x10(16),在添加操作中,如果容量不足则会自动扩充为2倍容量,这些与ArrayList和Hashtable相同。

SortedList的独特之处在于它保证数据的有序性,这点是如何保证呢?

原来,在Add(key,value)方法中,SortedList会首先用二分查找插入的key值,如果有重复项,则报错,如果没有重复项,则根据key值大小,比较得出在集合中的位置,然后插入到该位置中,代码如下:

public virtual void Add(object key, object value)
{
if (key == null)
{
throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key"));
}
//调用Array的静态方法进行二分查找。
int index = Array.BinarySearch(this.keys, 0, this._size, key, this.comparer);
if (index >= 0)
{
throw new ArgumentException(Environment.GetResourceString("Argument_AddingDuplicate__", new object[] { this.GetKey(index), key }));
}
this.Insert(~index, key, value);
}

二分查找的时间复杂度为O(LogN),所以SortedList的Add方法的时间复杂度为O(LogN),这一点略逊于ArrayList和Hashtable,后两者添加操作的时间复杂度都为O(1),这也是“有序”所付出的代价。

在查询数据时,SortedList会先通过二分查找法,找到key值所在object[]数组的序号,然后根据该序号去保存value的object[]数组中直接取值,代码如下:

public virtual object this[object key]
{
get
{
//IndexOfKey使用Array.BinarySearch进行二分查找;
int index = this.IndexOfKey(key);
if (index >= 0)
{
return this.values[index];
}
return null;
} ......
}

由于二分查找的关系,可以看出SortedList通过object查询操作的时间复杂度为O(logN),这一点强于ArrayList的O(n),逊于Hashtable的O(1)。

SortedList也可以通过序号下标来获取值,这种方式的复杂度为O(1),获取单个元素的方式在下面提到。

SortedList获取单个元素的灵活性

SortedList获取集合中单个元素的方式非常灵活,ArrayList只能通过int型的下标序号来获取,Hashtable只能object型的Key值来匹配,而SortedList既可以用object型的key获取,也可以用int型的序号来获取。

public void SortedListTest()
{
ArrayList arrayList = new ArrayList();
arrayList.Add("a");
Console.WriteLine(arrayList[0]);
//output: a Hashtable hash = new Hashtable();
hash.Add("a", "aaa");
Console.WriteLine(hash["a"]);
//output: aaa SortedList sortlist = new SortedList();
sortlist.Add("a", "aaa");
sortlist.Add("b", "bbb");
Console.WriteLine(sortlist["b"]);
Console.WriteLine(sortlist.GetByIndex(0));
//output: bbb
// aaa }

SortedList<TKey,TValue>是SortedList对应的泛型集合,除了免装箱拆箱优势和一个TryGetValue方法外,就没有什么太大差别。

结论

SortedList保证集合中数据的有序性,有两种方式来获取单个元素,较为灵活。

添加操作比ArrayList,Hashtable略慢;查找、删除操作比ArrayList快,比Hashtable慢。

当然,如果使用,则优先选择泛型集合SortedList<TKey,TValue>。

从类名就可以看出SortedDictionary<TKey,TValue>和上篇介绍的SortedList一样,都是有序集合,但从类内部的存储结构上看,两者有很大区别,SortedList内部用数组保存,只能算是有序线性表,而SortedDictionary<TKey,TValue>的内部结构是红黑树

园子里有不少关于红黑树的好文章,已将红黑树分析的很透彻。所以这里不讨论红黑树的结构原理,而讨论SortedDictionary和SortedList有什么差异?何时应该选择使用SortedDictionary?

SortedDictionary内部结构是红黑树,红黑树是平衡二叉树的一种,SortedList是有序线性表,内部结构是Array,运用了二分查找法提高效率。从两者查找、插入、删除操作的时间复杂度来看,都为O(LogN),分辨不出优劣,但内部结构的不同导致了实际操作中的性能差异。

SortedDictionary和SortedList性能比较--插入

由于SortedList用数组保存,每次进行插入操作时,首先用二分查找法找到相应的位置,得到位置以后,SortedList会把该位置以后的值依次往后移一个位置,空出当前位,再把值插入,这个过程中用到了Array.Copy方法,而调用该方法是比较损耗性能的,代码如下:

private void  Insert(int  index, TKey key, TValue value)
{
...... if (index < this._size)
{
Array.Copy(this.keys, index, this.keys, index + 1, this._size - index);
Array.Copy(this.values, index, this.values, index + 1, this._size - index);
} ......
}

SortedDictionary在添加操作时,只会根据红黑树的特性,旋转节点,保持平衡,并没有对Array.Copy的调用。

现在我们用数据测试一下:循环一个int型、容量为100000的随机数组,分别用SortedList和SortedDictionary添加。(代码中的CodeTimer类,来自老赵的文章。)

public void SortedAddInTest()
{
Random random = new Random();
int array_count = 100000;
List<int> intList = new List<int>();
for (int i = 0; i <= array_count; i++)
{
int ran = random.Next();
intList.Add(ran);
} SortedList<int, int> sortedlist_int = new SortedList<int, int>();
SortedDictionary<int, int> dic_int = new SortedDictionary<int, int>();
CodeTimer.Time("sortedList_Add_int", 1, () =>
{
foreach (var item in intList)
{
if (sortedlist_int.ContainsKey(item) == false)
sortedlist_int.Add(item, item);
}
});
CodeTimer.Time("sortedDictionary_Add_int", 1, () =>
{
foreach (var item in intList)
{
if (dic_int.ContainsKey(item) == false)
dic_int.Add(item, item);
}
});
}

结果跟之前分析的一样,为:

sortedList_Add_int 
    Time Elapsed:    4,311ms 
    CPU Cycles:    8,249,183,130 
    Gen0:        0 
    Gen1:        0 
    Gen2:        0

sortedDictionary_Add_int 
    Time Elapsed:    217ms 
    CPU Cycles:    278,164,530 
    Gen0:        1 
    Gen1:        1 
    Gen2:        0

由此可以看出:在大量添加操作的情况下,SortedDictionary性能优于SortedList。

SortedDictionary和SortedList性能比较--查询

两者的查询操作中,时间复杂度都为O(LogN),且源码中也没有额外的操作造成性能损失,那么他们在查询操作中性能如何?继续上面一个例子进行测试。

public void SortedAddInTest()
{
...... CodeTimer.Time("sortedList_Search_int", 1, () =>
{
foreach (var item in intList)
{
sortedlist_int.ContainsKey(item);
}
});
CodeTimer.Time("sortedDictionary_Search_int", 1, () =>
{
foreach (var item in intList)
{
dic_int.ContainsKey(item);
}
});
}

结果为:

sortedList_Search 
    Time Elapsed:    602ms 
    CPU Cycles:    1,156,460,630 
    Gen0:        0 
    Gen1:        0 
    Gen2:        0

sortedDictionary_Search 
    Time Elapsed:    667ms 
    CPU Cycles:    1,256,685,950 
    Gen0:        0 
    Gen1:        0 
    Gen2:        0

可以得出:两者在循环10w次的情况下,仅仅相差几十毫秒,可以看出,两者的查询操作性能相差不大。

SortedDictionary和SortedList性能比较--删除

从添加操作例子可以看出,由于SortedList内部使用数组进行存储数据,而数组本身的局限性使得SortedList大部分的添加操作都要调用Array.Copy方法,从而导致了性能的损失,这种情况同样存在于删除操作中。

SortedList每次删除操作都会将删除位置后的值往前挪动一位,以填补删除位置的空白,这个过程刚好跟添加操作反过来,同样也需要调用Array.Copy方法,相关代码如下。

public void RemoveAt(int index)
{
...... 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);
} ......
}

情况跟添加操作一样,所以先在这里预测一下:在大量删除操作的情况下时,SortedDictionary的性能优于SortedList。

让我们继续上面的测试代码来验证这一点。

public void SortedDictionaryTest()
{
//....... CodeTimer.Time("sortedList_Delete_String", 1, () =>
{
foreach (var item in temp_List)
{
sortedlist.Remove(item);
}
}); CodeTimer.Time("sortedDictionary_Delete_String", 1, () =>
{
foreach (var item in temp_List)
{
dic.Remove(item);
}
});
}

结果跟之前预测的一样,SortedDictionary的性能较好,如下:

sortedList_Delete

Time Elapsed:    13,346ms 
    CPU Cycles:    25,040,378,250 
    Gen0:        0 
    Gen1:        0 
    Gen2:        0

sortedDictionary_Delete

Time Elapsed:    731ms 
    CPU Cycles:    1,335,367,350 
    Gen0:        0 
    Gen1:        0 
    Gen2:        0

总结

SortedDictionary内部用红黑表存储数据,SortedList用数组存储数据,两者的查询效率差不多,但由于数组本身的限制,在大量添加删除操作的情况下,SortedDictionary的性能优于SortedList,而SortedList又存在二倍扩充的问题,在内存占用上也处于劣势。(这两者的添加删除操作因Array.Copy造成的性能差异也同样存在于泛型链表LinkedList<T>和线性表中,我之前关于链表的文章里忘记分析这一块了,^_^)

此处我有了一个迷惑,既然SortedDictionary的性能全面优于SortedList,那SortedList存在的意义是什么?我找来找去只发现SortedList的一个优点就两种获取单个元素的方式--key和index,这点在上篇文章也有提到,难道SortedList的优点只有这个?

.net学习笔记----有序集合SortedList、SortedList<TKey,TValue>、SortedDictionary<TKey,TValue>的更多相关文章

  1. Java学习笔记之---集合

    Java学习笔记之---集合 (一)集合框架的体系结构 (二)List(列表) (1)特性 1.List中的元素是有序并且可以重复的,成为序列 2.List可以精确的控制每个元素的插入位置,并且可以删 ...

  2. 软件测试之loadrunner学习笔记-02集合点

    loadrunner学习笔记-02集合点 集合点函数可以帮助我们生成有效可控的并发操作.虽然在Controller中多用户负载的Vuser是一起开始运行脚本的,但是由于计算机的串行处理机制,脚本的运行 ...

  3. java学习笔记之集合家族2

    集合体系 一.数据结构 List集合储存数据结构 <1>堆栈结构 特点:先进后出 <2>队列结构 特点:先进先出 <3>数组结构 特点:查询快,增删慢 <4& ...

  4. [知了堂学习笔记]_集合接口list与集合接口set的区别

    在Java中 除了 Map以外的集合的根接口都是Collection接口,而在Collection接口的子接口中,最重要的莫过于List和Set集合接口. 今天我们就来谈谈List集合接口与Set集合 ...

  5. python学习笔记整理——集合 set

    python学习整理笔记--集合 set 集合的用途:成员测试和消除重复的条目,进行集合运算 注意:花括号或set()函数可以用于创建集合. 注意:若要创建一个空的集合你必须使用set(),不能用{} ...

  6. Java学习笔记之集合

    集合(Collection)(掌握) (1)集合的由来? 我们学习的是Java -- 面向对象 -- 操作很多对象 -- 存储 -- 容器(数组和StringBuffer) -- 数组而数组的长度固定 ...

  7. 【原】Java学习笔记026 - 集合

    package cn.temptation; public class Sample01 { public static void main(String[] args) { // 需求:从三国演义中 ...

  8. JavaSE 学习笔记之集合框架(十八)

    集合框架:,用于存储数据的容器. 特点: 1:对象封装数据,对象多了也需要存储.集合用于存储对象. 2:对象的个数确定可以使用数组,但是不确定怎么办?可以用集合.因为集合是可变长度的. 集合和数组的区 ...

  9. Oracle 学习笔记 14 -- 集合操作和高级子查询

    Oracel提供了三种类型的集合操作:各自是并(UNION) .交(INTERSECT). 差(MINUS) UNION :将多个操作的结果合并到一个查询结果中,返回查询结果的并集,自己主动去掉反复的 ...

随机推荐

  1. 关于jquery自带动画效果的stop()问题

    首先先来看一下jquery中stop()的用法 stop()用于在动画执行前停止正在执行的动画 stop(stopAll,goToEnd)的两个参数为布尔值: stopAll:true/false:是 ...

  2. IC/RFID/NFC 关系与区别

    IC卡 (Integrated Circuit Card,集成电路卡) 有些国家和地区也称智能卡(smart card).智慧卡(intelligent card).微电路卡(microcircuit ...

  3. IE兼容性

    1.ie兼容css3 border-radius.box-shadow.text-shadow的方法 2.ie兼容性问题及解决方法 3.css Hack 一.ie兼容border-radius.box ...

  4. Python面试题库

    1.Python输出中文怎么解决 方法一:用encode和decode如:import os.pathimport xlrd,sys Filename=’/home/tom/Desktop/1234. ...

  5. MappingJacksonHttpMessageConverter过期

    最新在做一个SSM整合学习项目,maven加上jackson库,spring配置json,tomcat不能正常部署启动 Maven配置 <!-- 引入jackson,防止ajax调用返回json ...

  6. Spring Boot+AngularJS中因为跨域导致Session丢失

    http://blog.csdn.net/dalangzhonghangxing/article/details/52446821 如果还在为跨域问题烦恼,请查看博主的 解决angular+sprin ...

  7. C#中使用ADOMD.NET查询多维数据集

    ADOMD.NET 是用于与 Microsoft SQL Server Analysis Services 进行通信的 Microsoft .NET Framework 数据访问接口. ADOMD.N ...

  8. 【leetcode】Populating Next Right Pointers in Each Node II

    Populating Next Right Pointers in Each Node II Follow up for problem "Populating Next Right Poi ...

  9. ffmpeg-20160731-bin.7z

    ESC 退出 0 进度条开关 1 屏幕原始大小 2 屏幕1/2大小 3 屏幕1/3大小 4 屏幕1/4大小 S 下一帧 [ -2秒 ] +2秒 ; -1秒 ' +1秒 下一个帧 -> -5秒 f ...

  10. Mathematics:Dead Fraction(POJ 1930)

    消失了的分式 题目大意:某个人在赶论文,需要把里面有些写成小数的数字化为分式,这些小数是无限循环小数(有理数),要你找对应的分母最小的那个分式(也就是从哪里开始循环并不知道). 一开始我也是蒙了,这尼 ...