【译】Array与Set的异同及使用场景

4,089 阅读7分钟

Set vs Array
原文链接:戳这里
PS:原文只介绍了Array和Set的一些简单操作,详细API放在文章底部。

Array和Set是什么

每一个使用JavaScript编程的人都应该对Array非常熟悉。 通常,我们可以这样形容它:数组是表示一种储存在连续空间中的结构类型(内容可以为number,object等)。 例如:[1, 2, 3, 4, 5]
(原文这里说法存在歧义,具体数组在内存中的存储方式可以参考链接一链接二

而Set更像是一种抽象的数据类型。它只包含不同的元素/对象,不需要连续分配存储空间。 例如:{1, 2, 3}

它们之间最大的差别就是Array中的元素是可以重复的,而Set中的元素不可重复 。除此之外,Array被认为是一种索引集合,而Set是一种键的集合。

索引集合是指按下标作为索引值排序的数据结构
键的集合使用key访问元素,访问顺序与元素插入顺序一致。

很简单对吧?现在可能有人会问,既然这两者如此不同,为什么还要将它们进行比较?

在编程时,我们可以使用Array或Set来存储相同的数据集。但根据使用情况,我们应该选用正确的数据结构以助于提供最佳的解决方案。为了实现这个目标,首先我们需要了解它们分别是什么,有什么特点和能力。前面的文章中我们对这两种数据结构已经有了初步的了解,接下来就来看看它们的构建方式。

如何构建

Array

数组的构建非常简单直接,在JS中声明一个数组你可以直接使用字面上的语法:

var arr = []; //空数组
var arr = [1,2,3]; //元素为1,2,3的数组

或者使用构造函数:

var arr = new Array(); //空数组
var arr = new Array(1,2,3);//元素为1,2,3的数组

甚至这种更酷的方法:

var arr = Array.from("123"); //["1","2","3"]

注: 一条建议——除了非用不可的情况尽量不要使用new Array(),原因如下:

  • new Array()的性能比[]慢很多
  • []节约更多输入的时间
  • 你可能会犯一些经典的错误,比如:
var arr1 = new Array(10); //长度为10,每个元素都为undefined
var arr2 = [10]; // arr2[0] = 10 且 arr2.length = 1;
var arr3 = new Array(1,2,3); //[1,2,3]
var arr4 = [1,2,3];//[1,2,3] 

所以,尽量保持一个原则——简单化

Set

Set使用内置的构造函数初始化,没有其它简便的方法。

Set([iterable])

想要创建一个Set,需要使用new方法,例如:

var emptySet = new Set(); 
var exampleSet = new Set([1,2,3]);

不能使用如下方式:

new Set(1);

Set接收一个可遍历的对象作为其输入参数,并将遍历元素依次作为Set中的元素生成对象。因此,我们可以通过数组作为参数去创建Set——但创建的Set中只包含数组中的非重复元素,如上文提到,Set中元素不可重复。

当然,我们也能通过Array.from()方法将Set还原为数组:

var set = new Set([1,2,3]); // {1,2,3}
var arr = Array.from(set);//[1,2,3]

现在,我们知道了如何构建这两种数据结构,接下来让我们对Array / Set提供的最基本方法进行比较。

访问元素

  • 首先Set不支持像Array一样通过索引随机访问元素
console.log(set[0]); //undefined
console.log(arr[0]); //1
  • 更重要的是,因为Array中的数据存储在连续的空间中,所以CPU能够通过预处理操作更快地进行数据访问。因此,与其他类型的抽象数据类型进行比较,访问Array中的元素(按顺序,例如for循环)通常会更快更有效。
  • 判断一个元素是否在Set中的语法比判断是否在Array中更简洁。Set:Set.prototype.has(value)。Array:Array.prototype.indexOf(value)
console.log(set.has(0)); // boolean - false
console.log(arr.indexOf(0)); // -1
console.log(set.has(1)); //true
console.log(arr.indexOf(1)); //0

这意味着在Array中,我们需要额外创建检查元素是否在Array中的条件:

var isExist = arr.indexOf(1) !== -1;

:ES6提供了与Set.prototype.has()相似的方法Array.prototype.includes()来判断元素是否在数组中,但这个方法并没有广泛地被浏览器支持——比如IE..

插入元素

Array

  • 向数组中插入元素可以使用时间复杂度为O(1)的Array.prototype.push()方法快速完成——元素将会被插入到数组的末尾。
arr.push(4); //[1,2,3,4]
  • 或者可以使用时间复杂度为O(n)的Array.prototype.unshift()方法实现——元素被插入到数组的头部。
arr.unshift(3); //[3,1,2,3]
arr.unshift(5, 6); //[5,6,3,1,2,3]

Set

  • 只有一个方法可以在Set中插入元素——Set.prototype.add()。由于Set需要保持其“元素不可重复”的特性,所以每次调用add()方法,Set都需要便利所有元素以确保无重复元素,通常情况下add()方法的时间复杂度应为O(n)。不过Set的内部采用哈希表的实现方式,因此可以达到O(1)的时间复杂度。
set.add(3); //{1,2,3} 
set.add(4); //{1,2,3,4}

删除元素

Array

  • 使数组如此受欢迎的原因之一是它提供了许多不同的方法去删除元素,例如:
var arr = [5, 6, 1, 2, 3, 4]

pop()——删除并返回最后一个元素,时间复杂度O(1)

arr.pop();//return 4, [5,6,1,2,3]

shift()——删除并返回第一个元素,时间复杂度O(n)

arr.shift(); //return 5; [6,1,2,3]

splice(index, delectCount)——删除从index开始的delectCount个元素。时间复杂度为O(n)

arr.splice(0,1); //[1,2,3]

Set

var set = new Set([1, 2, 3, 4]);

delete(element)——删除指定的element

set.delete(4); //{1,2,3}

clear()——清空Set中所有元素

set.clear(); //{}

Array并未提供原生方法删除指定元素,我们需要借助其它方法找到元素对应下标,通过splice()删除,而Set可以直接使用delete()进行删除。

另外,对比Set只提供了一些基本的操作数据的方法,Array提供了更多实用的原生方法(reduce(),map(),sort()等),所以有些人可能会想,为什么我们比起Array会更喜欢Set?

Array和Set的应用场景

  • 首先,Set与Array是不同的两种数据结构,它并不是要完全替换Array,而是提供额外的数据类型来完成Array缺少的一些功能。
  • 由于Set只包含不同的元素,如果我们只希望存储不同的元素,使用Set会更好。
  • 可以利用Set的一些原生方法轻松的完成数组去重,查找数组公共元素及不同元素等操作。Set的delete()方法,使求两个Set的交/并集的操作比求两个数组的交/并集的操作更简单。
  • 数组适用于希望保留重复元素,可能进行大量修改(增、删操作),或是希望通过索引对元素进行快速访问的场景。(试想对一个Set进行二分查找——如何获取到Set的中间元素?)

结论

总之,就我而言对比Array,Set并没有明显优势,除了在特定场景中,例如当我们想要以最小成本维护不重复的数据,或者使用到大量不同的数据集时只需要使用最基本的集合操作而无需直接访问元素。

否则,Array通常是更好的选择。因为在需要时获取元素更节省CPU工作量。

欢迎讨论。

其他资料链接: