.net学习笔记----有序集合SortedList、SortedList<TKey,TValue>、SortedDictionary<TKey,TValue>
无论是常用的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>的更多相关文章
- Java学习笔记之---集合
Java学习笔记之---集合 (一)集合框架的体系结构 (二)List(列表) (1)特性 1.List中的元素是有序并且可以重复的,成为序列 2.List可以精确的控制每个元素的插入位置,并且可以删 ...
- 软件测试之loadrunner学习笔记-02集合点
loadrunner学习笔记-02集合点 集合点函数可以帮助我们生成有效可控的并发操作.虽然在Controller中多用户负载的Vuser是一起开始运行脚本的,但是由于计算机的串行处理机制,脚本的运行 ...
- java学习笔记之集合家族2
集合体系 一.数据结构 List集合储存数据结构 <1>堆栈结构 特点:先进后出 <2>队列结构 特点:先进先出 <3>数组结构 特点:查询快,增删慢 <4& ...
- [知了堂学习笔记]_集合接口list与集合接口set的区别
在Java中 除了 Map以外的集合的根接口都是Collection接口,而在Collection接口的子接口中,最重要的莫过于List和Set集合接口. 今天我们就来谈谈List集合接口与Set集合 ...
- python学习笔记整理——集合 set
python学习整理笔记--集合 set 集合的用途:成员测试和消除重复的条目,进行集合运算 注意:花括号或set()函数可以用于创建集合. 注意:若要创建一个空的集合你必须使用set(),不能用{} ...
- Java学习笔记之集合
集合(Collection)(掌握) (1)集合的由来? 我们学习的是Java -- 面向对象 -- 操作很多对象 -- 存储 -- 容器(数组和StringBuffer) -- 数组而数组的长度固定 ...
- 【原】Java学习笔记026 - 集合
package cn.temptation; public class Sample01 { public static void main(String[] args) { // 需求:从三国演义中 ...
- JavaSE 学习笔记之集合框架(十八)
集合框架:,用于存储数据的容器. 特点: 1:对象封装数据,对象多了也需要存储.集合用于存储对象. 2:对象的个数确定可以使用数组,但是不确定怎么办?可以用集合.因为集合是可变长度的. 集合和数组的区 ...
- Oracle 学习笔记 14 -- 集合操作和高级子查询
Oracel提供了三种类型的集合操作:各自是并(UNION) .交(INTERSECT). 差(MINUS) UNION :将多个操作的结果合并到一个查询结果中,返回查询结果的并集,自己主动去掉反复的 ...
随机推荐
- 对象的constructor属性
对象的constructor属性, 最初是用来标识对象类型的. 比如 ,我们定义一个 Person类,然后实例化两个对象. function Person(name, age, job){this.n ...
- 网页中的CSS换行控制
在进行DivCSS布局时,需要对文本进行控制,向大家介绍一下,CSS中控制换行的四种属性.一.white-space 可以实现HTML中PRE标签的效果,以及单元格的noWrap效果.语法: whit ...
- winkawaks模拟器
Winkawaks (温科沃克斯)win+ka(日文)+wa(也是日文)+k+s 最好的街机模拟器之一,与Nebula和MAME齐名,支持的游戏的有CAPCOM公司的CPS1,CPS2所有游戏,如三国 ...
- Android 简易XML解析
首先创建在Android工程中创建一个Assets文件夹 app/src/main/assets 在这里添加一个名为 data.xml的文件,然后编辑这个文件,加入如下XML格式内容 <?xml ...
- C/C++相对论——C++中为什么要使用异常?
C++中为什么要使用异常? 很多人也许知道C++中的异常机制,很多人也许不知道.很多人知道C中常用的assert,也知道在编译时候指定NODEBUG来忽略它. 对于C语言,使用正常的if-else即是 ...
- PHP网页缓存技术
http://blog.sina.com.cn/s/blog_646e51c40100weu9.html 前台静态化:把动态页面解析后保存为静态页面 文件缓存:把查询结果保存为文件,XML 内存缓存: ...
- 【leetcode】Unique Binary Search Trees II
Unique Binary Search Trees II Given n, generate all structurally unique BST's (binary search trees) ...
- Kendo UI
http://www.cnblogs.com/libingql/category/585455.html http://www.scala-china.net/discuz/forum.php?mod ...
- cookie相关内容
----客户端删除cookie <script type="text/javascript"> function DelC ...
- Effective C++ -----条款41:了解隐式接口和编译期多态
classes和templates都支持接口(interface)和多态(polymorphism). 对classes而言接口是显式的(explicit),以函数签名为中心.多态则是通过virtua ...