C#核心知识浅尝-集合
C#核心知识-集合
- 1.C#各类集合详解
- 2.LinQ语法
-
- 2.1 where
- 2.2 OfType
- 2.3 Skip、Take、SkipLast、TakeLast
- 2.4 SkipWhile、TakeWhile
- 2.5 Select
- 2.6 SelectMany
- 2.7 Cast、Chunk
- 2.8 Any、All、Contains
- 2.9 Append、Prepend
- 2.10 Count、TryGetNonEnumeratedCount、LongCount
- 2.11 Max、Min、MaxBy、MinBy
- 2.12 Aggregate
- 2.13 First、Single
- 2.14 ElementAt、ElementAtOrDefault
- 2.15 DefaultIfEmpty
- 2.16 ToArray、ToList、ToDictionary、ToHashTable
- 2.17 Distinct、DistinctBy、Union、Intersect、Except
- 2.18 Union、Intersect、Except
- 2.19 SequenceEqual
- 2.20 Zip、Concat、GroupBy
- 2.21 OrderBy、OrderByDescending、ThenBy、ThenByDescending
- 3.Parallel LINQ
CSDN代码块不支持LINQPAD8的写法,换了C#和csharp都试过,不过输出结果基本有注释,没注释的话一般很简单能看出来输出结果
1.C#各类集合详解
-
C# 集合是一个四层接口体系:IEnumerable(只能遍历)→ ICollection(能增删)→ IList/IDictionary<TKey,TValue>(具体功能)→ 具体实现类。
-
需要注意的是,大部分线程都是不安全的,单线程可以阻止集合为空,多线程操作时如果集合为空可能会抛出异常终止程序运行
-
ICollection:
- IList:
- Array(连续不可变空间)
- ArrayList(连续可变空间)
- List
- LinkedList(双向链表)
- ISet:
- HashSet
- TreeSet
- IList:
-
IMap:
- HashMap
- TreeMap
1.1 Array
- 连续不可变空间
- 数组元素必须一致
- 数组查找速度很快,新增和删除速度很慢
- 在初始化时要指定长度,或元素能够推断,或者说不提供初始值时必须指定长度
以下代码在LINQPad8运行:
void Main()
{
TestArray(); // 调用方法
}
void TestArray()
{
int[] num1 = new int[] { 1, 2, 3, 4, 5 };
num1.Dump();
num1.Length.Dump(); //输出长度
num1[1].Dump(); //输出数组的第二个数(数组从0计数)
num1.Reverse().Dump(); //反转数组
Array.Clear(num1, 1, 2); //将设定的数组元素变为默认值
num1.Dump();
int[] numSort = new int[] { 3, 4, 1, 2, 5 };
Array.Sort(numSort); //对数组进行排序,要注意会影响到源数组
numSort.Dump();
string[] num2 = new string[2] { "error", "Umbrella" };
num2.Dump();
int[][] num3 = new int[2][] {new int[2]{ 1, 2 }, new int[3]{ 1, 3, 4 } };
//这是包含2个子数组的锯齿数组(Jagged Array),第一个子数组有2个元素,第二个子数组有3个元素。
num3.Dump();
}
1.2 ArrayList
- 底层有扩容机制,所以是连续可变空间
- 底层类型是object,涉及装箱拆箱操作,对性能有影响
代码演示:
void TestArrayList(){
ArrayList num1 = new ArrayList(2);
num1.Add("你发如雪,凄美了离别");
num1.Add(1);
num1.Dump();
}
1.3 List泛型数组
- 泛型数组实际上是ArrayList一种泛型的表示方法,在实际开发中会大量应用
- 容量长度不确定,有灵活性也避免了装箱拆箱操作
void TestList(){
var num1 = new List<int>();
num1.Add(1);
num1.Add(2);
num1.Add(3);
num1.Add(4);
num1.Dump();
if(num1.Contains(2)){
"数组包含2".Dump();
}
}
1.4 HashTable
- 无序排列
- 使用Object类型
- 键值对类型,key value
void TestHashTable()
{
var num1 = new Hashtable();
num1.Add(1, "给你的爱一直很安静");
num1.Add("2", "来交换你偶尔给的关心");
num1.Dump();
if(num1.ContainsKey("2")){
"为了你的承诺".Dump();
}
if (num1.ContainsValue("给你的爱一直很安静"))
{
"在最绝望的时候".Dump();
}
if (!num1.ContainsValue("漂洋过海来看你"))
{
"都忍住不哭泣".Dump();
}
}
1.5 Dictionary(字典)
- Dictionary是泛型类的hashtable
- 无序排列
- 使用泛型,所以不涉及装拆箱操作
void TestDictionary()
{
var num1 = new Dictionary<int, string>();
num1.Add(1, "不管将会面对什么样的结局");
num1.Add(2, "在漫天风沙里,望着你背影");
num1.Add(3, "我竟悲伤的不能自己");
num1.Dump();
}
1.6 Queue(队列)
-
遵循先进先出原则
-
支持泛型
-
动态调整空间大小
void TestQueue(){
var num1 = new Queue<int>(); //这里不加泛型,默认就是obeject
num1.Enqueue(1); //添加元素
num1.Enqueue(2);
num1.Enqueue(3);
num1.Enqueue(4);
num1.Enqueue(5);
num1.Dump();
num1.Dequeue(); //移除元素
num1.Dump();
}
1.7 Stack(栈)
- 遵循先进后出原则
- 支持泛型
- 动态调整空间大小
void TestStack(){
var num1 = new Stack<int>(); //同队列一样,不加泛型默认object类型
num1.Push(1); //添加元素
num1.Push(2);
num1.Push(3);
num1.Push(4);
num1.Push(5);
num1.Dump();
num1.Pop().Dump(); //打印移除的元素
num1.Dump();
}
2.LinQ语法
- 方便操作集合的语法糖
- 一般会使用到=>匿名写法
2.1 where

-
源码如上,最后返回泛型集合
-
委托条件perdicate决定接下来执行的代码(这里不截全)
-
根据查询条件拿取对应元素最后返回IEnumerable可枚举类型的变量
void Main()
{
var personList = new List<Person>();
personList.Add(new Person()
{
ID = 1,
Name = "张三",
});
personList.Add(new Person()
{
ID = 2,
Name = "张四",
});
personList.Add(new Person()
{
ID = 3,
Name = "张五",
});
personList.Where(l => l.ID > 2).Dump(); //l是数组提出的元素ID大于2就输出对应元素
}
// You can define other methods, fields, classes and namespaces here
class Person{
public int ID { get; set; }
public string Name { get; set; }
}
2.2 OfType
- 筛选自定类型的元素
void Main()
{
var PersonList = new object[] { 1, 2, 3, 4, 5, "Music", "后会无期" };
PersonList.OfType<int>().Dump();
PersonList.OfType<string>().Dump();
}
2.3 Skip、Take、SkipLast、TakeLast
- Skip是跳过第几位并选取后面的所有元素
- Take是挑选几位及之前的元素
- SkipLast是从末尾开始跳过,选取之前的所有元素
- TakeLast是从末尾开始挑选,选取之后的所有元素
void Main()
{
var PersonList = new object[] { 1, 2, 3, 4, 5, "Music", "后会无期" };
PersonList.Skip(3).Dump(); //输出4后面的所有元素,不包含3
PersonList.Take(3).Dump(); //输出3前面的所有元素,包含3
PersonList.SkipLast(3).Dump(); //输出5前面的所有元素,不包含5
PersonList.TakeLast(3).Dump(); //输出5后面的所有元素,包含5
}
2.4 SkipWhile、TakeWhile
- SkipWhile只检查开头连续的元素,遇到第一个不满足条件的就停止检查,然后取后面所有元素
- TakeWhile是从集合开头一直取元素,直到遇到第一个不满足元素的数
void Main()
{
var PersonList = new int[] { 1, 2, 3, 4, 5, 4, 3, 2, 1};
PersonList.SkipWhile(c => c < 3).Dump(); //包含3
PersonList.TakeWhile(c => c < 4).Take(1).Dump(); //不包含4
}
2.5 Select
- select作用是映射,映射怎么理解呢,简单来说把集合里的每个元素,按照写的规则,一对一地转换成另一个样子
- select也算变相改变了元素,如第二个代码块演示
void Main()
{
var PersonList = new int[] { 1, 2, 3, 4, 5, 4, 3, 2, 1 };
PersonList.Select(p => $"我被拼接了 {p}").Dump();
PersonList.Select((p, index) => $"我被拼接了 {index}").Dump(); //重载映射元素所在数组的序号
}
void Main()
{
var personList = new List<Person>();
personList.Add(new Person()
{
ID = 1,
Name = "张三",
});
personList.Add(new Person()
{
ID = 2,
Name = "张四",
});
personList.Add(new Person()
{
ID = 3,
Name = "张五",
});
personList.Select(p => {
p.ID+=1; //所有ID+1
return p;
}).Dump();
}
class Person
{
public int ID { get; set; }
public string Name { get; set; }
}
2.6 SelectMany
- 一句话:把"嵌套集合"拍平成一维集合
void Main()
{
IEnumerable<List<int>> list = new List<List<int>>
{
new List<int>{1, 2, 3},
new List<int>{4, 5, 6},
};
list.SelectMany(l => l.Select(i => $"拼接{i}")).Dump();
}
2.7 Cast、Chunk
- Cast为转换
- Chunk为分组
void Main()
{
IEnumerable<object> list = new object[5]{1, 2, 3, 4, 5};
list.Cast<int>().Dump(); //转换为int数组
list.Chunk(2).Dump(); //两两分组,5单独为一组
}
2.8 Any、All、Contains
- Any检测数组是否有元素符合条件
- All检测数组所有元素都符合条件
- Contains检测数组是否包含某个元素
void Main()
{
IEnumerable<int> list = new int[5] { 1, 2, 3, 4, 5 };
list.Any(l => l > 1).Dump(); //输出True
list.All(l => l > 1).Dump(); //输出False
list.Contains(1).Dump(); //输出True
}
2.9 Append、Prepend
- Append是往数组开头添加元素
- Prepend是往数组结尾添加元素
void Main()
{
IEnumerable<int> list = new int[5] { 1, 2, 3, 4, 5 };
list.Append(0).Dump(); //数组开头添加0
list.Prepend(7).Dump(); //数组结尾添加7
}
2.10 Count、TryGetNonEnumeratedCount、LongCount
- Count统计满足条件的元素数量,LongCount就是返回long类型的Count方法
- Count使用时会用foreach遍历数组,在面对庞大数据时会有灾难级的性能损耗
- TryGetNonEnumeratedCount不遍历集合就能尝试获取元素数量,避免性能损耗
void Main()
{
IEnumerable<int> list = new int[5] { 1, 2, 3, 4, 5 };
list.Count(l => l > 3).Dump();
list.TryGetNonEnumeratedCount(out int result);
result.Dump(); //输出5
}
2.11 Max、Min、MaxBy、MinBy
- Max和Min分别是求最大值和最小值
- 要注意的是,Max和Min求的结果是相对于这个数组而言的,什么意思呢?看代码演示就知道了
- MaxBy和MinBy是根据变换值返回原始对象
void Main()
{
IEnumerable<int> list = new int[5] { 1, 2, 3, 4, 5 };
list.Max().Dump(); //输出5
list.Min().Dump(); //输出1
list.Max(l => l* -1).Dump(); //输出-1,这就是为什么结果是相对数组而言的,操作原数字-5就不是最大的了
list.Min(l => l* -1).Dump(); //输出-5
list.MinBy(l => l* -1).Dump(); //输出5
list.MaxBy(l => l* -1).Dump(); //输出1
}
2.12 Aggregate
- Aggregate意思为聚合,单纯用来添加的话用法则非常像Sum
- 可以添加初始值,也可以对结果进行改变
void Main()
{
IEnumerable<int> list = new int[5] { 1, 2, 3, 4, 5 };
list.Aggregate((x, y) => x + y).Dump(); //输出15
list.Aggregate(10, (x, y) => x + y).Dump(); //10为初始值,输出25
list.Aggregate(10, (x, y) => x + y, x => x / 5).Dump(); //结果/5,输出5
}
2.13 First、Single
- First返回数组的第一个元素,添加条件后则返回满足条件的第一个元素
- Single是保证数组只有一个元素,并返回它
- First和Single尽量要捕获异常,因为数组为空等情况都会终止程序运行
void Main()
{
IEnumerable<int> list = new int[5] { 1, 2, 3, 4, 5 };
list.First().Dump(); //输出1
IEnumerable<int> list2 = new int[] {};
list2.FirstOrDefault().Dump(); //输出0,因为int默认值为0
IEnumerable<int> list3 = new int[1] {1};
list3.Single().Dump(); //输出1
list3.SingleOrDefault().Dump(); //输出1,这里不多举例,知道默认值即可
}
2.14 ElementAt、ElementAtOrDefault
- 二者都是根据索引返回相应元素,后者仍是返回默认值
- ElementAt和First等一样,最好写异常捕获
void Main()
{
IEnumerable<int> list = new int[5] { 1, 2, 3, 4, 5 };
list.ElementAt(3).Dump(); //输出4
}
2.15 DefaultIfEmpty
- 处理空集合时,提供一个默认值代替空集合
void Main()
{
var personList = new List<Person>(); //集合为空
personList.DefaultIfEmpty(new Person {
ID = 4,
Name = "不管将会面对什么样的结局"
}).First().Dump();
}
class Person
{
public int ID { get; set; }
public string Name { get; set; }
}
2.16 ToArray、ToList、ToDictionary、ToHashTable
- 都是一系列的转换,这里只演示ToDictionary
void Main()
{
IEnumerable<int> list = new int[5] { 1, 2, 3, 4, 5 };
list.ToDictionary(key=>key, value=>value.ToString()).Dump(); //注意key值不能重复
}
2.17 Distinct、DistinctBy、Union、Intersect、Except
-
Distinct:去掉集合中的重复元素,只保留唯一的
-
DistinctBy:就是"按某个特征去重",相同特征只留第一个
void Main()
{
IEnumerable<int> list = new int[6] { 1, 1, 2, 3, 4, 5 };
list.Distinct().Dump(); //输出五个不同元素
var personList = new List<Person>();
personList.Add(new Person()
{
ID = 2,
Name = "张三",
});
personList.Add(new Person()
{
ID = 2,
Name = "张四",
});
personList.Add(new Person()
{
ID = 3,
Name = "张五",
});
personList.DistinctBy(p => p.ID = 2).Dump(); //只保留一个Id为2的张三(因为位置靠前)
}
class Person
{
public int ID { get; set; }
public string Name { get; set; }
}
2.18 Union、Intersect、Except
- Union为并集,两个数组元素全部包含
- Intersect为交集,保留两个数组相同的元素
- Except为差集,一个数组减去另一个数组含有相同的元素
void Main()
{
IEnumerable<int> list2 = new int[3] { 1, 2, 3};
IEnumerable<int> list3 = new int[3] { 2, 3, 4};
list2.Union(list3).Dump(); //并集,输出4个元素
list2.Intersect(list3).Dump(); //交集,输出2、3
list2.Except(list3).Dump(); //差集,输出1
}
2.19 SequenceEqual
- 比较两个数组是否相等
void Main()
{
IEnumerable<int> list = new int[3] { 1, 2, 3};
IEnumerable<int> list2 = new int[3] { 1, 2, 3};
IEnumerable<int> list3 = new int[3] { 2, 3, 4};
list.SequenceEqual(list2).Dump(); //True
list.SequenceEqual(list3).Dump(); //False
}
2.20 Zip、Concat、GroupBy
- Zip是组合两个数组,不同类型也可以结合
- Concat也是组合两个数组(同类型),但是会开辟空间存放新的元素,和zip有所区别
- GruopBy是依据条件对数组进行分组,不设条件默认按照值分组
void Main()
{
IEnumerable<int> list = new int[3] { 1, 2, 3 };
IEnumerable<int> list2 = new int[3] { 1, 2, 3 };
IEnumerable<string> list3 = new string[3] { "1", "2", "3" };
IEnumerable<int> list4 = new int[4] { 1, 1, 2, 3 };
list.Zip(list2, (first, second) => first + second).Dump(); //输出2、4、6
list.Zip(list3, (first, second) => first + second).Dump(); //输出11、22、33
list.Concat(list2).Dump(); //输出1,2,3,1,2,3
list4.GroupBy(l => l).Dump(); //分为3组,两个1为一组剩下各为两组
list4.GroupBy(l => l > 1).Dump(); //分为2组,两个1为一组剩下两个大于1的为一组
}
2.21 OrderBy、OrderByDescending、ThenBy、ThenByDescending
- OrderBy就是依据条件对数组的元素进行排序,后面接Descending则是倒序
- ThenBy为后续的附加条件
void Main()
{
var students = new[]{
new {ID = 5, Name = "甲", Age = 22},
new {ID = 4, Name = "乙", Age = 13},
new {ID = 3, Name = "丙", Age = 23} ,
new {ID = 2, Name = "丁", Age = 15} ,
new {ID = 1, Name = "戊", Age = 42} ,
};
students.OrderBy(s => s.ID).Dump(); //按照ID从小到大排序
students.OrderByDescending(s => s.Age).Dump(); //按照年龄倒序排序
students.OrderBy(s => s.Age).ThenByDescending(s => s.Name).Dump(); //ThenBy为额外附加条件
}
3.Parallel LINQ
-
让LINQ查询并行执行,自动利用多核CPU加速处理
-
这章真不行了,讲的很粗糙,目前了解即可
-
Parallel是线程安全的
-
Parallel方法总结:
-
AsParallel:使用并行
-
WithDegreeOfParallelism:控制核心运行数量,设置电脑并行的CPU数
-
WithExecutionMode:强制切换多线程
-
WithMergeOptions:控制并行计算的结果如何返回给你,有三种合并模式
- NotBuffered:不缓冲,直接返回结果
- AutoBuffered(默认):自动缓冲,由机器决定
- FullyBuffered:完全缓冲,等所有计算结果计算完一次性返回
-
WithCancellation:等多线程后这个会仔细说,简单说给PLINQ查询加个"停止按钮",可以随时取消长时间运行的操作
-
AsOrdered:控制并行计算的结果顺序,保持原顺序
-
AsUnOrdered(默认):顺序不重要,怎么快怎么来
-
AsSequential:在并行查询中临时切换回顺序执行
-
ParallelEnumerable:和普通集合类似功能,有三种将输入元素指派到线程的划分策略
- Range :直接生成并行范围的数字序列
- Repeat :生成重复元素的并行序列
- Empty:创建空的并行序列
-
ForAll:并行地对集合中每个元素执行一个操作,不收集结果
-
void Main()
{
TestAsParallelNotUse(); // 测试顺序执行
TestAsParallel(); // 测试并行执行
}
void TestAsParallelNotUse()
{
var stopwatch = Stopwatch.StartNew(); //Stopwatch是监管方法执行时间
var collection = Enumerable.Range(0, 10).Select(HeavyComputation); //执行0-10的运算
// 这里才开始真正计算(因为LINQ延迟执行)
foreach (var element in collection)
{
// 空的,只是触发计算
}
stopwatch.Stop();
stopwatch.ElapsedMilliseconds.Dump("顺序执行时间");
}
void TestAsParallel()
{
var stopwatch = Stopwatch.StartNew();
var collection = Enumerable.Range(0, 10)
.AsParallel() // 关键区别,使用并行
.Select(HeavyComputation);
foreach (var element in collection)
{
// 空的,只是触发计算
}
stopwatch.Stop();
stopwatch.ElapsedMilliseconds.Dump("并行执行时间");
}
int HeavyComputation(int n)
{
long sum = n;
for (int i = 0; i < 100_000_000; i++)
{
sum += i;
}
return n;
}
更多推荐
所有评论(0)