01、集合基础知识

.Net 中提供了一系列的管理对象集合的类型,数组、可变列表、字典等。从类型安全上集合分为两类,泛型集合非泛型集合,传统的非泛型集合存储为Object,需要类型转。而泛型集合提供了更好的性能、编译时类型安全,推荐使用。

.Net中集合主要集中在下面几个命名空间中:

1.1、集合的起源:接口关系

集合接口 特点/说明
IEnumerator、IEnumerator<T> 枚举器(还不是集合),提供foreach枚举项的能力
IEnumerable、IEnumerable<T> 可枚举集合,几乎所有集合都实现了该接口,属于集合最基础的接口。就一个IEnumerator GetEnumerator() 方法,返回一个枚举器。
ICollection、ICollection<T> 提供了基础集合操作:Count、Add()、Remove()、Clear()、Contains()、CopyTo()
IList、IList<T> 索引器[int index]、IndexOf()、Insert()、RemoveAt()
IDictionary、IDictionary<TKey, TValue> 键值集合操作:Keys、Values、索引器[Key]、Add()、Remove()
IReadOnly*** 只读的集合,包括IReadOnlyCollection、IReadOnlyList、IReadOnlyDictionary等
  • 天赋技能 —— foreach:几乎所有集合都可以用foreach循环操作,是因为他们都继承自IEnumerable接口,由枚举器(IEnumerator)提供枚举操作。
  • 几乎所有集合都提供添加、删除、计数,来自基础接口 ICollectionICollection<T>
  • IListIList<T> 提供了数组的索引器、查找、插入等操作,几乎所有具体的集合类型都实现了该接口。
  • Array 是一个抽象类,是所有数组T[]的基类,她是类型安全的。
  • 推荐尽量使用数组T[]、泛型版的集合,提供了更好的类型安全和性能。

1.2、非泛型集合—— 还有什么存在的价值?

  • 非泛型的Hashtable,Key、Value都是Object类型的,Dictionary 是泛型版本的 Hashtable。
  • ArrayList 是非泛型版本的 List<T>,基本很少使用,也尽量不用。

既然非泛型版本类型不安全,性能还差,为什么还存在呢?

主要是历史原因,泛型是.Net2.0 引入的,因此为了向后兼容,依然保留的非泛型版本集合。在接口实现时,非泛型接口一般都是显示实现的,因此基本不会用到。不过在有些场景下,非泛型接口、集合还是有点用的,如类型不固定的集合,或者用接口作为约束条件或类型判断。

ArrayList arr = new ArrayList();
arr.Add(1);
arr.Add("sam");
arr.Add(new Point());
if (arr is IList) {} class User<T> where T :IList {}

1.3、Collection<T>List<T>有何不同?

两者比较相似,他们到底有什么区别呢?该如何选择?

  • Collection<T> 作为自定义集合基类,内部提供了一些virtual的实现,便于继承实现自己的集合类型。其内部集合用的就是List<T>,如下部分源码 Collection.cs。
  • List<T> 作为集合使用,是最常用的可变长集合类型了,他优化了性能,但是丢失了可扩展性,没有提供任何可以override的成员。
public class Collection<T>
{
public Collection()
{
items = new List<T>();
}
protected virtual void InsertItem(int index, T item)
{
items.Insert(index, item);
}
}

02、枚举器——foreach的秘密!

foreach 用来循环迭代可枚举对象,用一种非常简洁、优雅的姿势访问可枚举元素。常用于数组、集合,当然不仅限于集合,只要符合要求枚举要求的都可以。

foreach 可枚举类型 说明
数组 包括Array数组、List、字典等,他们都实现了IEnumerable接口的。
IEnumerable 可枚举接口
IEnumerable<T> 同上,泛型版本
GetEnumerator()方法 包含公共方法“IEnumerator GetEnumerator();”的任意类型
yield迭代器 yield语句实现的迭代器,实际返回的也是IEnumerable、IEnumerator

2.1、IEnumerator枚举器

枚举可以foreach 枚举的密码是他们都继承自IEnumerable接口,而更重要的是其内部的枚举器 —— IEnumerator。枚举器IEnumerator定义了向前遍历集合元素的基本协议,其申明如下:

public interface IEnumerator
{
object Current { get; } bool MoveNext(); void Reset(); //这个方法是非必须的,用于重置游标,可不实现
}
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
new T Current { get; }
}
  • MoveNext() 移动当前元素到下一个位置,Current获取当前元素,如果没有元素了,则MoveNext()返回false。注意MoveNext()会先调用,因此首次MoveNext()是把位置移动到第一个位置。
  • Reset()用于重置到起点,主要用于COM互操作,使用很少,可不用实现(直接抛出 NotSupportedException)。

该接口不是必须的,只要实现了公共的Current、无参MoveNext()成员就可进行枚举操作。

实现一个获取偶数的枚举器:

void Main()
{
var evenor = new EvenNumbersEnumerator(1, 10);
while (evenor.MoveNext())
{
Console.WriteLine(evenor.Current); //2 4 6 8 10
}
}
//获取偶数的枚举器
public struct EvenNumbersEnumerator : IEnumerator<int> //不继承IEnumerator接口,效果也是一样的
{
private int _start;
private int _end;
private int _position = int.MinValue; public EvenNumbersEnumerator(int start, int end)
{
_start = start;
_end = end;
}
public int Current => _position;
object IEnumerator.Current => Current; //显示实现非泛型接口,然后隐藏起来 public bool MoveNext()
{
if (_position == int.MinValue)
_position = (int.IsEvenInteger(_start) ? _start : _start + 1) - 2;
_position += 2;
return (_position <= _end);
}
public void Reset() => throw new NotSupportedException();
public void Dispose() { } //IEnumerator 是实现了 IDisposable接口的
}

2.2、IEnumerable可枚举集合

IEnumerableIEnumerable<T>是所有集合的基础接口,其核心方法就是 GetEnumerator() 获取一个枚举器。

public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerable<out T> : IEnumerable
{
new IEnumerator<T> GetEnumerator();
}

该接口也不是必须的,只要包含public的“GetEnumerator()”方法也是一样的。

有了 GetEnumerator(),就可以使用foreach来枚举元素了,这里foreach会被编译为 while (evenor.MoveNext()){} 形式的代码。在上面 偶数枚举器的基础上实现 一个偶数类型。

void Main()
{
var evenNumber = new EvenNumbers();
foreach (var n in evenNumber)
{
Console.WriteLine(n); //2 4 6 8 10
}
}
public class EvenNumbers : IEnumerable<int> //不用必须继承接口,只要有GetEnumerator()即可
{
public IEnumerator<int> GetEnumerator()
{
return new EvenNumbersEnumerator(1, 10);
} IEnumerator IEnumerable.GetEnumerator() //显示实现非泛型接口,然后隐藏起来
{
return GetEnumerator();
}
}

foreach 迭代其实就是调用其GetEnumerator()CurrentMoveNext()实现的,因此接口并不是必须的,只要有对应的成员即可。

foreach (var n in evenNumber)
{
Console.WriteLine(n); //2 4 6 8 10
}
/************** 上面代码编译后的效果如下:*****************/
IEnumerator<int> enumerator = evenNumber.GetEnumerator();
try
{
while (enumerator.MoveNext ())
{
int i = enumerator.Current;
Console.WriteLine (i);
}
}
finally
{
if (enumerator != null)
{
enumerator.Dispose ();
}
}

2.3、yield 迭代器

yield return 是一个用于实现迭代器的专用语句,它允许你一次返回一个元素,而不是一次性返回整个集合。常来用来实现自定义的简单迭代器,非常方便,无需实现IEnumerator接口。

惰性执行:元素是按需生成的,这可以提高性能并减少内存占用(当然这个要看具体情况),特别是在处理大型集合或复杂的计算时。迭代器方法在被调用时,不会立即执行,而是在MoveNext()时,才会执行对应yield return的语句,并返回该语句的结果。Linq里的很多操作也是惰性的。

简化代码:使用yield return可以避免手动编写迭代器的繁琐过程。

状态保持yield return自动处理状态保持,使得在每次迭代中保存当前状态变得非常简单。每一条yield return语句执行完后,代码的控制权会交还给调用者,由调用者控制继续。

yield迭代器方法会被会被编译为一个实现了IEnumerator 接口的私有类,可以看做是一个高级的语法糖,有一些限制(要求):

  • 迭代器的返回类型可以是IEnumerableIEnumerator或他们的泛型版本。还可以用 IAsyncEnumerable<T> 来实现异步的迭代器。
  • yield break 语句提前退出迭代器,不可直接用return,是非法的。
  • yield语句不能和try...catch一起使用。
void Main()
{
var us = new User();
foreach (string name in us)
{
Console.WriteLine(name); //sam kwong
}
foreach (string name in us.GetEnumerator1())
{
Console.WriteLine(name); //1 sam 2
}
foreach (string name in us.GetEnumerator2())
{
Console.WriteLine(name);//KWONG
}
}
public class User
{
private string firstName = "sam";
private string lastName = "Kwong";
public IEnumerator GetEnumerator()
{
yield return firstName;
yield return lastName;
}
public IEnumerable GetEnumerator1() //返回IEnumerable
{
Console.WriteLine("1");
yield return firstName; //第一次执行到这里
Console.WriteLine("2");
yield break; //第二次执行到这里,也是最后一次了
yield return lastName;
}
public IEnumerable<string> GetEnumerator2() //返回IEnumerable<string>
{
yield return lastName.ToUpper();
}
}

03、集合!装逼了!

3.1、常用集合类型

集合类型 特点/说明
Array(数组之父) 是一个抽象类,是有所有数组的父类,提供了很多用于数组操作的静态方法,详见下一章节
数组:T[] 定长(内存连续)的数组集合,所有数组都继承自 Arrayint[] arr = {1,2,3};
ArrayList 可变长数组,存放Object对象,内部会自动扩容,很少使用。
List<T> 泛型版的ArrayList,可变长集合,很常用。
HashTable 存储Key、Value结构的哈希表,可根据Key快速获取Value值。Key不可重复,都是Object类型。
Dictionary<TK, TV> 泛型版本的哈希表,代替HashTable
HashSet<T> 只有Key的Dictionary,Key不可重复,适用于与不可重复的集合
SortedSet<T> 支持排序的HashSet,内部用一个红黑树存储,添加删除慢,因为要维护元素的状态
QueueQueue<T> 队列,先进先出(FIFO),Enqueue(后入)、Dequeue(前出)
PriorityQueue<T, TP> 支持优先级顺序的队列,只能保证优先级顺序,相同优先级不保证先进先出
StackStack<T> 栈表,后进先出(LIFO),Push(前入)、Pop(前出)
SortedList<TKey,TValue> 按照Key排序的列表,内部为数组,支持索引器,在插入时按照顺序存储(性能较差)
SortedDictionary<TK,TV> 同SortedList,内部为红黑树存储键值对,大数据量时性能更好,不支持索引器。
LinkedList<T> 双向链表,每个节点包含一个值、前节点指针、后节点指针,不支持索引器。插入删除很快O(1),不会移动元素,查找O(n)较慢,会遍历整个集合。
ListDictionary 单向链表字典,轻量级的字典,它适用于小型集合(小于10个)。
HybridDictionary Hashtable(高查询效率)和ListDictionary(内存少)的杂交集合,根据数量内部切换容器。
ReadOnlyCollection<T>‌ 只读集合,接收一个IList集合,可以看做是对普通集合的只读包装,修改会抛出异常。
Immutable*** 不可变集合,通过静态方法Create()创建并初始化,任何修改都会创建新的集合。
Collection<TItem> 专为自定义扩展(继承)用的集合基类
KeyedCollection<TK, TV> 同上,专为自定义扩展(继承)用的字典集合基类,继承自Collection<TItem>
BitArray 位数组(不是字节,只有一位),用来存储bool值,支持位操作。
Concurrent*** 线程安全的集合,各种类型的集合都有线程安全的版本,可多线程访问
ConcurrentQueue<T> 线程安全的队列 Queue
ConcurrentStack<T> 线程安全的栈表 Stack
ConcurrentDictionary<T,K> 线程安全的字典 Dictionary
BlockingCollection<T> 提供阻塞功能的线程安全集合,适合用于生产者-消费者场景,消费者会自动阻塞等待生产者消息
ConcurrentBag<T> 线程安全的集合List<T>
Channel<T> 专用于生产、消费场景的现代异步消息队列,比 BlockingCollection更强大、灵活。
ArrayList arr2 = new ArrayList();
arr2.Add(null);
arr2.Add("sam");
arr2.Add(1);
Console.WriteLine(arr2[1]);

3.2、数组Array[]

Array 数组是一种有序的集合,通过唯一索引编号进行访问。数组T[]是最常用的数据集合了,几乎支持创建任意类型的数组。Array是所有的数组T[]的(隐式)基类,包括一维、多维数组。CLR会将数组隐式转换为 Array 的子类,生成一个伪类型。

  • 索引从0开始。
  • 定长:数组在申明时必须指定长度,超出长度访问会抛出IndexOutOfRangeException异常。
  • 内存连续:为了高效访问,数组元素在内存中总是连续存储的。如果是值类型数组,值和数组是存储在一起的;如果是引用类型数组,则数组值存储其引用对象的(堆内存)地址。因此数组的访问是非常高效的!
  • 多维数组:矩阵数组 用逗号隔开,int[,] arr = {{1,2},{3,4}};
  • 多维数组:锯齿形数组(数组的数组),int[][] arr =new int[3][];
int[] arr = new int[100];      //申请长度100的int数组
int[] arr2 = new int[]{1,2,3}; //申请并赋值,长度为3
int[] arr3 = {1,2,3}; //同上,前面已制定类型,后面可省略
arr[1] = 1;
Console.WriteLine(arr[2]); //未赋值,默认为0

几乎大部分编程语言的数组索引都是从0开始的,如C、Java、Python、JavaScript等。当然也有从1开始的,如MATLAB、R、Lua。

属性 特点/说明
Length 数组的长度、元素的数量
Rank 获取数组的维度,一维数组就是1
[int index] 索引器,这是方法数组元素的最常用方式,没有之一!
方法 特点/说明
AsReadOnly<T>(T[]) 获取一个只读的数组 ReadOnlyCollection<T>
CopyTo(array, index) 复制数组元素到目标数组array,参数index为目标array的索引位置
object? GetValue(Int32) 获取制定索引位置的值,对应的还有SetValue(obj, index),这两个方法都会装箱,慎用!
静态方法 特点/说明
BinarySearch(Array) 二分查找,返回找到的元素的索引,负数表示没找到。前提是数组必须是有序的。
Clear(Array) 清除数组的内容
Clone() 创建 Array 的浅表副本
Copy(array1, array2) 将一个array1中的元素复制到数组array2
CreateInstance(Type, len) 创建数组,指定类型和长度,Array.CreateInstance(typeof(int),10)
Exists<T>(arr, Predicate) 根据谓词条件判断是否存在的元素,返回bool,Array.Exists(arr,s=>s>5)
Array.Fill(arr, value) 填充数组值为valueArray.Fill(arr,1);
Find<T>(arr, Predicate) 根据条件查找元素,返回第一个匹配的元素,Array.Find(arr,s=>s>5)
FindLast(arr, Predicate) 同上,返回最后一个匹配的元素
FindAll(arr, Predicate) 查找所有匹配的元素,返回数组。
FindIndex(T[], Predicate) 查找第一个匹配元素的索引,对应的还有 FindLastIndex
ForEach(T[], Action<T>) 循环遍历元素执行action
IndexOfLastIndexOf 根据元素查找索引位置,-1表示没找到
Reverse<T>(T[]) 反转元素顺序,Array.Reverse(arr),不会创建新数组。️Linq的Reverse会创建新对象
Resize<T>(ref T[], Int) 更改数组长度,会创建一个新数组,所以用了ref
Sort(Array) 对数组元素排序,Array.Sort(arr),不会创建新数组。
TrueForAll(T[], Predicate) 判断是否所有元素都符合维词条件,返回bool,Array.TrueForAll(arr,s=>s>1)
扩展方法 特点/说明
IEnumerable Cast<T>() 强制转换为指定类型的数组,延迟实现+会装箱,var arr2 = arr.Cast<uint>()
AsSpan() 创建数组的Span<T>对象
AsMemory() 创建数组的Memory<T>对象

通过上表发现,Array 的很多方法都是静态方法,而不是实例方法,这一点有点困惑,造成了使用不便。而且大部分方法都可以用Linq的扩展来代替。

3.3、Linq扩展

LINQ to Objects (C#) 提供了大量的对集合操作的扩展,可以使用 LINQ 来查询任何可枚举的集合(IEnumerable)。扩展实现主要集中在 代码 Enumerable 类(源码 Enumerable.cs),涵盖了查询、排序、分组、统计等各种功能,非常强大。

  • 简洁、易读,可以链式操作,简单的代码即可实现丰富的筛选、排序和分组功能。
  • 延迟执行,只有在ToList、ToArray时才会正式执行,和yeild一样的效果。
var arr = Enumerable.Range(1, 100).ToArray(); //生成一个数组
var evens = arr.Where(n => int.IsEvenInteger(n)); //并没有执行
var arr2 = arr.GroupBy(n => n % 10).ToArray();

04、集合的一些小技巧

技巧 说明
集合初始化器{} 省略new和类型,用{}初始化值,int[] arr = {1,2,3};
集合表达式[] C#12,简化集合赋值,比上面更简化,int[] arr = [1,2,3]
范围运算符 a..b C#8,表示a到b的范围(不含b),可获取集合中指定范围的子集var sub =arr[1..3]
^n倒数 C#8,索引倒数,arr[^1]//倒数第一个
..展开运算符 支持集合、可枚举表达式,展开每个枚举元素到数组,配合集合表达式使用比较方便。

4.1、集合初始化器{}

同类的初始化器类似,用{}来初始化设置集合值,支持数组、字典。

//数组
int[] arr1 = new int[3] { 1, 2, 3 };
int[] arr2 = new int[] { 1, 2, 3 };
int[] arr4 = { 1, 2, 3 };
//字典
Dictionary<int, string> dict1 = new() { { 1, "sam" }, { 2, "william" } };
Dictionary<int, string> dict2 = new() { [5] = "sam", [6] = "zhangsan" }; //索引器写法
var dict3 = new Dictionary<int, string> { { 1, "sam" }, { 2, "william" } };

4.2、集合表达式[]

集合表达式 简化了集合的申明和赋值,直接用[]赋值,比初始化器更简洁,语法形式和JavaScript差不多了。可用于数组、Sapn、List,还可以自定义集合生成器

int[] iarr1 = new int[] { 1, 2, 3, 4 }; //完整的申明方式
int[] iarr2 = { 1, 2, 3, 4 }; //前面声明有类型int[],可省略new
int[] iarr3 = [1, 2, 3, 4]; //简化版的集合表达式 List<string> list = ["a1", "b1", "c1"];
Span<char> sc = ['a', 'b', 'c'];
HashSet<string> set = ["a2", "b2", "c2"]; //..展开运算符,把集合中的元素展开
List<string> list2 = [.. list,..set, "ccc"]; //a1 b1 c1 a2 b2 c2 ccc

4.3、范围运算符..

a..b表示a到b的范围(不含b),其本质是 System.Range 类型数据,表示一个索引范围,常用与集合操作。

  • 可省略ab,缺省则表示到边界。
  • 可结合倒数^使用。
int[] arr =  new[] { 0, 1, 2, 3, 4, 5 };
Console.WriteLine(arr[1..3]); //1 2 //索引1、2
Console.WriteLine(arr[3..]); //3 4 5 //索引3到结尾
Console.WriteLine(arr[..]); //全部
Console.WriteLine(arr[^2..]); //4 5 //倒数到2到结尾 var r = 1..3;
Console.WriteLine(r.GetType()); //System.Range

自定义的索引器也可用用范围Range作为范围参数。

05、提高集合性能的一些实践

技巧 说明
使用泛型版本集合 尽量不使用非泛型版本的集合(如ArrayList、Hashtable),避免装箱。
初始化合适的容量 创建集合时,根据实际情况尽量给定一个合适的初始容量,避免频繁的扩容
使用Span 使用Span<T>Memory<T>进行高效内存访问,更多参考《高性能的Span、Memory
使用ArrayPool<T> 对于频繁获取集合数据的场景,采用池化技术复用数组对象,减少数组对象的创建

尽量给集合一个合适的“容量”( capacity),几乎所有可变长集合的“动态变长”其实都是有代价的。他们内部会有一个定长的“数组”,当添加元素较多(大于容量)时,就会自动扩容(如倍增),然后把原有“数组”数据拷贝(搬运)到新“数组“中。

  • 因此在使用可变长集合时,尽量给一个合适的大小,可减少频繁扩容带来的性能影响。当然也不可盲目设置一个比较大的容量,这就很浪费内存空间了。stringBuilder也是一样的道理。
  • 可变长集合的插入、删除效率都不高,因为会移动其后续元素。

下面测试一下List<T>,当创建一个长度为1000的List时,设置容量(1000)和不设置容量(默认4)的对比。

int max = 10000;
public void List_AutoLength(){
List<int> arr = new List<int>();
for (int i = 0; i < max; i++)
{
arr.Add(i);
}
}
public void List_FixedLength()
{
List<int> arr = new List<int>(max);
for (int i = 0; i < max; i++)
{
arr.Add(i);
}
}

很明显,自动长度的List速度更慢,也消耗了更多的内存。

尽量不创建新数组,使用一些数组方法时需要注意尽量不要创建新的数组,如下面示例代码:

var arr = Enumerable.Range(1, 100).ToArray();
// 需求:对arr进行反序操作
var arr2 = arr.Reverse().ToArray(); //用Linq,创建了新数组
Array.Reverse(arr); //使用Array的静态方法,原地反序,没有创建新对象

比较一下上面两种反序的性能:


参考资料


️版权申明:版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处!原文编辑地址-语雀

C#.Net筑基-集合知识全解的更多相关文章

  1. Java集合框架全解

    Collection 集合 集合接口有2个基本方法: public interface Collection<E> { //向集合中添加元素.如果添加元素确实改变了集合就返回 true, ...

  2. Js基础知识1-数组操作全解

    数组操作全解 js变量类型 var string; var name = "student",age=12; //underfined.null.boolean.string.nu ...

  3. PureStudy:学科知识分享——个人网站开发全解

    PureStudy:学科知识分享--个人网站开发全解 项目描述 PureStudy,学科知识分享网站. 学生可以使用这个网站,来浏览相应学科的知识点.学习总结,获取相关的资料.此外,他们可以选择上传文 ...

  4. RabbitMQ,Apache的ActiveMQ,阿里RocketMQ,Kafka,ZeroMQ,MetaMQ,Redis也可实现消息队列,RabbitMQ的应用场景以及基本原理介绍,RabbitMQ基础知识详解,RabbitMQ布曙

    消息队列及常见消息队列介绍 2017-10-10 09:35操作系统/客户端/人脸识别 一.消息队列(MQ)概述 消息队列(Message Queue),是分布式系统中重要的组件,其通用的使用场景可以 ...

  5. Python基础知识详解 从入门到精通(七)类与对象

    本篇主要是介绍python,内容可先看目录其他基础知识详解,欢迎查看本人的其他文章Python基础知识详解 从入门到精通(一)介绍Python基础知识详解 从入门到精通(二)基础Python基础知识详 ...

  6. Mybatis系列全解(七):全息视角看Dao层两种实现方式之传统方式与代理方式

    封面:洛小汐 作者:潘潘 一直以来 他们都说为了生活 便追求所谓成功 顶级薪水.名牌包包 还有学区房 · 不过 总有人丢了生活 仍一无所获 · 我比较随遇而安 有些事懒得明白 平日里问心无愧 感兴趣的 ...

  7. Mybatis系列全解(六):Mybatis最硬核的API你知道几个?

    封面:洛小汐 作者:潘潘 2020 年的大疫情,把世界撕成几片. 时至今日,依旧人心惶惶. 很庆幸,身处这安稳国, 兼得一份安稳工. · 东家常讲的一个词:深秋心态 . 大势时,不跟风.起哄, 萧条时 ...

  8. Mybatis系列全解(五):全网最全!详解Mybatis的Mapper映射文件

    封面:洛小汐 作者:潘潘 若不是生活所迫,谁愿意背负一身才华. 前言 上节我们介绍了 < Mybatis系列全解(四):全网最全!Mybatis配置文件 XML 全貌详解 >,内容很详细( ...

  9. Mybatis系列全解(二):Mybatis简介与环境搭建

    封面:洛小汐 作者:潘潘 Mybatis 是一套持久层框架,灵活易用,特别流行. 前言 Mybatis系列全解,我们预计准备10+篇文章,让我们了解到 Mybatis 的基本全貌,真正从入门到上手,从 ...

  10. js系列教程1-数组操作全解

    全栈工程师开发手册 (作者:栾鹏) 快捷链接: js系列教程1-数组操作全解 js系列教程2-对象和属性全解 js系列教程3-字符串和正则全解 js系列教程4-函数与参数全解 js系列教程5-容器和算 ...

随机推荐

  1. Flink 在 58 同城的应用与实践

    ​简介: 58 同城的实时 SQL 建设以及如何从 Storm 迁移至 Flink. 本文整理自 58 同城实时计算平台负责人冯海涛在 Flink Forward Asia 2020 分享的议题< ...

  2. [FAQ] uni-app 不支持 v-cloak 情况下如何处理 v-if 页面闪烁问题

    在 Vue 中存在使用 v-if 决定元素显示隐藏的时候,会出现页面闪烁,那么当然 uni-app 中也存在了. 如果编译完后,需要满足 js 的某个条件才隐藏,页面元素必然会有闪烁的情况. 所以解决 ...

  3. PostMan接口测试实用小点

    PostMan接口测试实用小点 1. 接口测试变量存取操作 在Postman中有很多地方可以存储一些变量,这里只介绍经常使用的环境变量.变量设置后,在UI界面可以通过{{变量名}}获取到对应值. 在环 ...

  4. 路由跳转报错:Uncaught (in promise) NavigationDuplicated {_name: "NavigationDuplicated", name: "NavigationDuplicated",...

    记录一个路由跳转的报错: 将换成

  5. SAP Adobe Form 教程二 表

    本文将介绍一些进阶内容,前文:SAP Adobe Form 教程一 简单示例 方法和对比 使用表对象(Table Object)创建表 优点: 它简单易行. 当我们只有很少的字段单行时,我们可以使用它 ...

  6. Linux系统命令-目录命令

    1.ls命令:主要作用是显示目录下的内容 基本格式 [root@localhost ~]# ls [选项] [参数是文件名或目录名] 常用选项 -a:显示所有文件 --color=when:支持颜色输 ...

  7. vim 使用clang-format 格化C/C++/Java/JavaScript

    vim 使用clang-format 格化C/C++/Java/JavaScript 参考信息 官方参考https://clang.llvm.org/docs/ClangFormat.html 安装 ...

  8. Typora+免费图床,构建随处可用的Markdown文档

    Typora+PicGo+Gitee自动上传图片 视频教程: https://www.bilibili.com/video/BV1hT4y1f7Mf?from=search&seid=1546 ...

  9. 在 WPF 中集成 ASP.NET Core 和 WebView2 用于集成 SPA 应用

    背景 我们有些工具在 Web 版中已经有了很好的实践,而在 WPF 中重新开发也是一种费时费力的操作,那么直接集成则是最省事省力的方法了. 修改项目文件 我们首先修改项目文件,让 WPF 项目可以包含 ...

  10. 如何将 iPad 用作 Windows 计算机的第二台显示器?

    安装第二台显示器是扩大屏幕空间的一种常用方法.如果您偶尔只需要第二个显示器(例如您偶尔在家中工作),但又不想购买整个第二个显示器,则可以将 iPad 用作廉价的替代品. 要将 iPad 用作Windo ...