前言

基础不牢地动山摇找不到工作吃不饱饭睡不好觉

JS数组

数组是JS基础数据类型。

  • JS数组是值的有序集合,无类型限制
  • JS数组是动态的,按需增大缩小,最大索引值2^32-2
  • JS数组可以是稀疏的,不一定连续索引
  • JS数组是特殊的对象

JS数组从Array.prototype继承属性买这里面很多数组方法都是泛型(用于参数化类型)的,所以同样可以用于任何类数组对象

1
Array.prototype.__proto__ = Object.prototype // true

ES6增加一类新数组,统称为“定型函数”,固定长度、固定数组元素类型。(这种函数具有极高性能,且支持二进制数据字节级访问)

创建数组

数组字面量

1
2
3
4
let empty = []
let a = 0
let misc = [1,1, 'm', true, undefined, a+1]
let ohno = [,,] // 这个长度是2!因为数组字面量语法允许末尾出现‘,’

new构造函数

1
2
3
let empty = new Array()
let arr = new Array(10) // 定长 数组索引属性和值都没有定义
let arr1 = new Array(1, 2, 'test') // arr1 = [1, 2, 'test'] 远远不及直接用字面量创建简单

奇妙的…可迭代对象(扩展操作符)

也算是打平,有的话,只打平一层。且对于空隙默认为未定义索引的**undefined**。

1
2
3
4
5
6
7
8
9
let a = [1, 2, 3]
let b = [4, ...a, 5] // b = [4, 1, 2, 3, 5]

// 演示数组去重的便携方式
let letters = [..."hello"]
let ans = [...new Set(letters)] // => ['h', 'e', 'l', 'l', 'o']

//演示打平和空隙处理
console.log(...[1,,[2, 3]]) // 1 undefined [2,3]

可迭代对象

  • 数组,定型数组
  • 字符串
  • 类数组对象(每个属性键都是整数,且有length属性)
  • set&map
  • Generator对象
  • 自定义可迭代对象

工厂方法

Array.of()

当使用构造函数创建数组时,如果传入数值参数,且仅为一个,那么他只会创建一个长度为1的空数组,而非包含值1的数组。

ES6中给出的这个工厂方法就可以解决这个问题。

1
2
let arr = Array.of(1) // arr = [1]
let arr1 = Array.of(1, 2, 3) // arr1 = [1, 2, 3]

Array.from()

ES6新增的另一个工厂方法,是创建数组副本的简便方式。

参数

  • iterable 可迭代对象
  • mapFn 映射函数
  • thisArg 指定函数this值

第二个参数为函数,当构建新数组时,原对象的每个元素都会传入该函数,返回值替代原始值加入新数组。(和map类似,但是在构造数组期间进行映射要比构建之后再映射 的效率高–前者效率高)

1
2
3
let copy = Array.from(original)
let a = [1, 2, 3]
let b = Array.from(a, (item) => { return item + 1}) // b = [2, 3, 4]

第三个参数用于设置参数函数的this值,比较少用。即当mapFn非箭头函数,而是一个普通函数时,它的this是由所处上下文环境决定的,但是放置在Array.from()中,没有自己的执行的上下文,因此可以给它指定一个上下文对象。这便使得函数可以调用这个对象内容。

1
2
3
4
5
6
const numbers = [1, 2, 3, 4, 5];
const result = Array.from(numbers, function (num) {
return num * this.multiplier; // this即为第三参数这个对象
}, { multiplier: 2 });

console.log(result); // [2, 4, 6, 8, 10]

真细致!

读写数组元素

你的中规中矩,超乎你的想象

1
2
3
4
5
6
let a = ['qvq']
a[1] = 1
let i = 1
a[i + 1] = 2
a[a[i+1]+1] = 3
a // a = ['qvq', 1, 2, 3]

数组的数组索引

0到2^32-2之间的正整数

以上代码可见数组特殊之处,就在于当使用小于2^32-1非负整数作为属性名时(数值数组索引),数组会自动为我们维护length

需要再次注意的是,数组就是特殊的对象。数组的索引都是数组的属性名。JS会将数值数组索引转换为字符串作为属性名。

当我们使用恰好与这范围内数字值相等的字符串/浮点值的时候,也将属于数组数值索引

数组的对象属性

0到2^32-2之间的正整数的值

数组索引和对象属性的区别

数组中可以用任意名字创建属性。前者后者不冲突,若是前者,会自动维护length,若是后者,不会维护length且输出是看不到的,但是能够正常查看

1
2
3
4
5
6
7
let arr = [1, 2, 3]
arr['a'] = 'abandon'
arr[-1] = -1
console.log(arr.length) // => 3
console.log(arr) // => [1, 2, 3]
console.log(arr[-1]) // => -1
console.log(arr['a']) // => 'abandon'

此外,在查看数组任何不存在属性的时候都不会导致错误,而是但会undefined

稀疏数组

稀疏数组就是其元素没有从0开始的索引的数组

简单介绍

↑书中对于稀疏数组的定义我无法赞同,缝隙存在于中间而不是开头也是稀疏数组啊。

1
2
3
let bookArr = [,,1,2]
let speArr = [1,,,2]
// 被省略的元素是不存在的

稀疏数组指存有空隙(稀疏位置)的数组,某些索引没有对应元素。

其实接下来我在用各种数组方法验证它们对稀疏数组的操作时,发现还是需要绕一个弯的。稀疏数组的空隙是没有定义索引且没有定义元素值的。之所以遍历时候发现置空处对应索引的值是undefined是因为当查看一个数组中不存在的索引值对应值的时候,默认是undefined

所以请理解且牢记上述↑,这对与后续各种数组方法的理解也有所帮助。

稀疏数组的创建

很简单,用new Array创建后加值或者直接给一个大于length的数组所以赋值等,都可以创建。

delete操作符也可以创建↓

1
2
3
4
5
6
const arr = [1, 2, 3, 4, 5];
delete arr[2];

console.log(arr.length); // 5
console.log(arr); // [1, 2, undefined, 4, 5]
console.log(2 in arr); // false 该索引不存在于数组中

足够稀疏的数组通常较于稠密数组(就是常规数组) 慢但内存占用少 的方式实现的。查询稀疏数组与常规数组元素的时间相当。

数组长度

每个数组都有一length属性,这个属性使得数组有别于常规Js对象。

  • 对于稠密数组,长度即为元素个数
  • 对于稀疏数组,长度大于元素个数

无论如何,任何数组元素索引都不会大于等于length

img

为了维护↑这种不变式(invariant),数组有两个特殊行为

  • 行为1:当给索引为i的元素赋值时,如果i>=length,那么length=i+1
  • 行为2:当将length设置为一个小于当前值的非负整数n,那么任何大于等于n的数组元素都会从数组中删除。

添加和删除数组元素

添加

  • 新索引赋值 a[1] = 1
  • push()
  • unshift()

删除

  • delete操作符 delete(a[1]) 这种操作不会修改length属性,而是留下空隙
  • pop()
  • shift()
  • 更改length使得去除末尾的一些元素
  • splice()

迭代数组

  • 老式循环:行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 在嵌套循环等关乎性能的环境中,我们最好不要每次迭代都调一次长度,这个时候就有两种方式
    // 长度保存局部变量
    for (let i = 0, len = letters.length; i < len; i++) {}
    // 由后向前迭代
    for (let i = letters.length; i >= 0; i--) {}

    // 稀疏情况下,可以加一个判断跳过空隙
    for (let i = 0, len = letters.length; i < len; i++) {
    if (letters[i] === undefined)
    continue
    }
  • for/of: let letter of letters && let [index, letter] of letters.entries()

  • forEach()

  • map()

  • filter()

多维数组

Js无多维数组,用数组嵌套模拟。

1
2
3
4
let table = new Array(10)
for (let i = 0; i < 10; i++) {
table[i] = new Array(10)
}

数组方法

迭代器方法

用于遍历数组元素,通常会对每个元素调用指定的函数。便于对数组进行迭代、映射、测试和归并。

接下来的这些数组迭代器方法,必须接收一个函数参数,函数中可传三类参数:数组元素值、数组索引、数组本身;第二参数可选,指定第一个参数内部this值。

一般情况下,函数参数的返回值不重要,但是不同方法对于这个返回值的处理是不同的↓

以下所有方法都不会修改原数组(当然传入三参函数可能会修改原数组)。

forEach()

跳过稀疏数组空隙(空隙不会调用函数)。这个方法并没有提供一种终止迭代的方式。

1
2
3
4
let data = [1, 2, 3, 4, 5], sum = 0
data.forEach(value => { sum += value }) // sum = 15
data.forEach(value => { value++ }) // data = [1, 2, 3, 4, 5]
data.forEach(function(v, i, a) { a[i] = v + 1 }) // data = [2, 3, 4, 5, 6]

map()

该方法把调用它的数组的每个元素进行函数处理。一些列的函数返回值作为新数组元素。会跳过稀疏数组空隙,新数组稀疏程度与原数组稀疏程度相同。这个方法并没有提供一种终止迭代的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = [1, 2, 3]
let b = a.map(x => x*x) // b = [1, 4, 9] a = [1, 2, 3]

// 跳过稀疏数组
let a = [1, 2, , 3]
let b = a.map((item, index) => {
console.log(index, item)
return item ? item++ : item = '?'
})
console.log(b)
// 0 1
// 1 2
// 3 3
// [1,2,,3]

filter()

该方法返回调用它的数组的子数组。传参函数为一个断言函数跳过稀疏数组的空隙,且返回的数组始终稠密。这个方法并没有提供一种终止迭代的方式。

1
2
3
4
5
let a = [1, 2, 3, , 5]
let b = a.filter(x => x > 4) // b = [5]

// 清除空隙以及null和undefined的方法
let c = a.filter(x => x!==undefined && x!==null)

find()和奇妙的findIndex()

寻找第一个满足断言函数条件的元素/索引值。倘若遍历之后找不到,返回undefined/-1

值得注意:find会跳过稀疏数组空隙findIndex不太一样,它是按照数组索引遍历来的,所以走了空隙不存在的索引——没跳过

1
2
3
4
5
let a = [1, 2, 3, , 5]
console.log(a.find(x => x < 3)) // 1
console.log(a.find(x => x === undefined)) // undefined(没有找到等于undefined的值所以才返回undefined)
console.log(a.findIndex(x => x < 3)) // 0
console.log(a.findIndex(x => x === undefined)) // 3(因为索引3没有被定义且啥也没有,所以在这个里面是默认undefined的)

every()和some()

every()在“全称”时返回true;some()在“存在”时返回true。两者都会跳过稀疏数组空隙。牛起来了

1
2
3
4
5
let a = [1, 2, 3, , 5]
let flag = a.some(x => x === undefined)
console.log(flag) // false
let flag1 = a.every(x => x!== undefined)
console.log(flag1) // true

reduce()和reduceRight()

两种方法使用我们指定的函数归并数组元素,最终产生一个值。比较特殊,归并值在其他三个参数(当前值,当前索引,数组本身)的前面,且设置初始值的这个参数取代了指定this的参数。前者从左到右,后者从右到左。

归并 也称 注入inject折叠 fold

1
2
3
4
// 演示一下用法
array.reduce(callback, initialValue) // 一个回调,一个给归并值设置初始值(默认0)
// 西索
array.reduce((accumulator-归并值, curValue-当前值) => { return 处理后的值 }, initialValue-初始归并值)

跳过空隙!

1
2
3
4
5
6
7
8
let sum = a.reduce((sum, cur) => {
console.log(cur)
return sum + cur
}, 0)
console.log(sum) // 11

// 如果是个空数组,且不传初始值,会TypeError
// 比较有趣的是如果是 只包含一个元素的数组+没初始值 或 空数组+有初始值 那么直接返回值,不会调用。

基于多种数据类型、多维数组、嵌套对象、条件逻辑、归并顺序等方面,用普通的循环逻辑会更易读一些,因为数组归并表达的算法容易复杂化。

打平数组

不会修改原始数组

flat()

用于展开数组中的嵌套数组。不传参时默认打平一层(1),传参则将参数作为打平层数。跳过空隙,过滤掉。

1
2
3
4
5
6
7
let a = [1, [2, [3, [4, , 5]]]]
let b = a.flat()
console.log(b) // => [1, 2, [3, [4, 5]]]
let c = a.flat(3)
console.log(c) // => [1, 2, 3, 4, 5]
let d = a.flat(5)
console.log(d) // => [1, 2, 3, 4, 5]

flatMap()

与map()方法类似,但是返回的数组会被自动打平。类似于map(f).flat()但效率远高于这样的组合。

1
2
3
4
let parses = ['helo wld', 'geo rock']
let ans = parses.flatMap(item => item.split(' '))
console.log(ans) // => ["helo","wld","geo","rock"]
// 我试了一下.map().flap(),会报错

允许把元素映射为空数组,打平后空数组元素不会出现在输出数组中。会跳过空隙

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const sparseArray = [, 1, , 2, , 3]; // 稀疏数组

const result = sparseArray.flatMap((num) => {
if (num) {
if (num > 1) {
return [num, num * 2];
} else {
return []
}
} else {
console.log('遇到了空隙') // 完全不会输出
return [num];
}
});

console.log(result); // 输出: [2,4,3,6]

添加数组

不会修改原始数组

concat()

新数组包含调用数组的元素和参数元素。如果参数包含数组,那么会打平一层。因为是合并操作,不会去管空隙。

1
2
3
4
5
6
let a = [1, , 2, 3]
let b = a.concat([1, , 2])
console.log(b) // => [1,,2,3,1,,2]

// 如果有以下自改自的情况,建议使用splice/push就地修改,而不是创建副本赋值
a = a.concat(b) // nono

栈、队列操作

不会打平,修改原数组了

栈操作之push()和奇妙的pop()

  • push():栈尾添加元素,对于空隙,笑死,根本不让你push一个单纯空,如果结合...,能给...打的undefiend

  • pop():去除栈尾元素,对于空隙,pop的机制是按索引排出,所以认为排出的元素是undefined

    1
    2
    3
    let a = [1, 2, 3, , ,]
    console.log(a.pop()) // => undefined
    console.log(a) // => [1, 2, 3, ]

队列操作之unshift()和shift()

理论上讲,这两种方法的操作效率是不及push和pop的,因为每次操作都要重排后面的元素位置。

  • unshift():队列头添加元素。**空隙undefiend**,注意一次性和多次的元素顺序。
  • shift():去除队列头元素。空隙undefiend
1
2
3
4
5
6
let a = [,, 2, 3]
console.log(a.shift())
console.log(a) // => [,2,3]
let b = [1, ,4]
a.unshift(...b)
console.log(a) // => [1,undefined,4,,2,3]

连续区域处理

所谓连续区域,指数组的子数组/‘切片’。以下这几个方法用于提取、替换、填充和复制切片。

奇妙的slice()

该方法用于返回数组切片不修改原数组。会纳入空隙。接收两个参数,范回的元素范围是[参数1,参数2)

  • 如果只有一个参,那么返回从参到后所有元素;
  • 如果任何一个参为负值,那代表的是倒数第几个元素-1 => 数组最后一个元素
1
2
3
4
5
6
7
let a = [1, , 2, 3, 4, 5]
let t1 = a.slice(0)
let t2 = a.slice(0, 2)
let t3 = a.slice(-5)
let t4 = a.slice(-4, -1)
console.log(t1, t2, t3, t4)
// [1,,2,3,4,5] [1,] [,2,3,4,5] [2,3,4]

奇妙的splice()

用于对数组/插入删除。会修改原数组。 纳入空隙。接收多个参数,处理区间是[参数1, 参数1+参数2)。这个方法返回的是被删除元素的数组!!

  • 如果只有一个参数,那么删除这个参数及其后所有元素;
  • 前两个参数是指定要删除哪些参数,后续参数是要插进删除位置的元素,不会打平
1
2
3
4
5
6
7
8
9
10
11
let a = [1, 2, 3, 4, 5]
console.log(a.splice(0)) // => [1, 2, 3, 4, 5]
console.log(a) // => []

let b = [1, , 2, 3, 4, 5]
console.log(b.splice(0, 2)) // => [1, ]
console.log(b) // => [2, 3, 4, 5]

let c = [1, 2, 3, 4, 5]
console.log(c.splice(0, 2, '1', '2', [1, 2])) // => [1, 2]
console.log(c) // => ["1","2",[1,2],3,4,5]

fill()

该方法将数组或切片元素设置为指定值会修改原数组。 纳入空隙,空隙也替换。可传三个参数,第一个是要设置的值,第二个第三个类似slice,是要替换的起始终止值。

  • 如果只有一个参数,则数组全部元素替换为该值;
  • 其实参数的传递规则请参考slice。
1
2
3
let a = [1, 2, 3, , 5]
a.fill(0)
console.log(a) // => [0, 0, 0, 0, 0]

copyWithin()

该方法把数组切片复制到数组中的新位置会修改原数组纳入空隙。数组长度不变,超了就不要了。

  • 第一个参数:指定要把第一个元素复制到目标索引
  • 第二个参数:起始索引。(若省略,默认从0开始)
  • 第三个参数:终止索引。(若省略,默认数组长度)
1
2
3
let a = [, 2, 3, 4, 5]
a.copyWithin(4, 0, 1)
console.log(a) // => [,2,3,4,]

索引、排序

indexOf() 和 lastIndexOf()

前者从左到右返回找到的第一个匹配元素的索引。找不到就返回-1忽略空隙

后者从右到左返回找到的第一个匹配元素的索引。找不到就返回-1

  • 第一个参数是搜索值;
  • 第二个参数是搜索起始索引,可以是类似slice的负值,默认从0开始。
1
2
3
4
5
let a = [, 2, 3, 4, 5]
console.log(a.indexOf(undefined)) // => -1
console.log(a.indexOf(2)) // => 1
console.log(a.indexOf(2, 3)) // => -1
conosle.log(a.indexOf(2, -5)) // => 1

这两个方法使用===操作符比较他们的参数和数组元素。所以如果数组包含对象而非原始值,那么这些方法检查两个引用是否来自同一个对象。如果想查找对象的内容,建议find(自定义func断言)

如何利用idnexOf()找多个符合条件的值?
1
2
3
4
5
6
7
8
9
10
function findall (a, x) {
let results = [], len = a.length, pos = 0
while (pos < len) {
pos = a.indexOf(x, pos)
if (pos === -1) break;
results.push(pos)
pos += 1
}
return results
}

奇妙的includes()

这个方法我个人比较常用到。接收一个参数,如果数组包含这个参数,返回true,否则false。**按照索引视空隙为undefined**。

1
2
3
let a = [, 2, 3, 4, 5]
console.log(a.includes(undefined)) // => true
console.log(a.includes('good')) // => false

与indexOf()不同的地方,后者是使用===判断,导致 非数值 与其他和自身都不相同,因此没法检测到NaN;但是includes()就可。

sort()

会修改原数组。 纳入空隙,未定义的元素会放在末尾。不传参的时候默认都按字母顺序排;传参就按参数给的比较函数规定的排。

比较函数:

  • 返回值大于0:需要调换!
  • 返回值小于0:无需调换。
  • 返回值等于0:两值相等~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 基本比较函数
let a = [33, 444, 123]
a.sort() // a = [123, 33, 444] 默认字符串顺序
a.sort(function(a, b) {
return a - b
}) // a = [33, 123, 444] 值升序-我们假定 起初a,b这个顺序,如果是大于零的值,那么换顺序成b,a
a.sort((a, b) => b - a) // a = [444, 123, 33] 值降序

// 复杂一点
let a = ['ant', 'cat', 'Bug', 'Dog']
a.sort()
console.log(a) // => ["Bug","Dog","ant","cat"] 我去区分大小写!
a.sort(function(a, b) {
let le = a.toLowerCase()
let ri = b.toLowerCase()
if (le < ri) return -1
if (le > ri) return 1
return 0
})
console.log(a) // => ["ant","Bug","cat","Dog"]

reverse()

反转数组纳入空隙修改原数组

1
2
3
let a = [1, 2, 3, ,]
console.log(a.reverse()) // => [, 3, 2, 1]
console.log(a) // => [, 3, 2, 1]

数组字符串转换

join() & split()

不修改原数组纳入空隙打平后用,连接,与无需打平的元素用传参连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let a = new Array(4)
let b = a.join('-')
console.log(b) // => "---"
let c = b.split('-')
console.log(c) // => ["","","",""]

let a = [1, [2, [3, 4]]]
let b = a.join('-')
console.log(b) // => "1-2,3,4"

let a = new Array(4)
let b = a.join() // 不传参默认,分隔
console.log(b) // => ",,,"
let c = b.split()
console.log(c) // => [",,,"]

toString() & 奇妙的toLocalString()

这俩不传参。toString()用起来和不传参的join()一样,toLocalString()是前者的本地化版本。

  • 前者直接连接。不修改原数组纳入空隙打平后用,连接。
  • 后者先将所有元素转化为字符串然后再连接。不修改原数组空隙报错打平后用,连接。
1
2
3
4
console.log([1, 2, ,].toString()) // => "1,2,"
console.log([1, [2, [3, 4]]].toString()) // => "1,2,3,4"

console.log([1, 2, ,].toLocalString()) // => Uncaught TypeError: [1,2,(intermediate value)].toLocalString is not a function

静态数组函数

可以通过构造函数调用的三个静态函数。

  • Array.of()
  • Array.from()
  • Array.isArray():判断一个未知值是否是数组

类数组对象

JS数组具有一些与其他对象不具备的特性(非本质)。

  • length自动更新
  • length为更小的值会截断函数
  • 数组从Array.prototype继承有用的方法 => 类数组对象不能直接使用这些方法
  • Array.isArray()返回true

只要对象有一个数值属性length,而且有相应的非负整数属性,那么就可以完全视同为数组。

常规对象边类数组对象

1
2
3
4
5
6
7
let a = {}
let i = 0
while (i < 10) {
a[i] = i
i += 1
}
a.length = 10

测试对象是都为类数组对象

1
2
3
4
5
6
7
function isArrayLike(o) {
if (o && typeof(o) === 'object' && Number.isFinite(o.length) && o.length >= 0 && Number.isInteger(o.length) && o.length < 4294967295) {
return true
} else {
return false
}
}

类数组怎么才能使用数组方法

Function.call()

1
2
3
4
5
6
7
8
9
10
let a = {'0': '1', '1': '2', '2': '3', length: 3}

let t1 = Array.prototype.join.call(a, '+')
console.log(t1) // => '1+2+3'

let t2 = Array.prototype.map.call(a, x => x+=x)
console.log(t2) // => ['11' '22', '33']

let t3 = Array.from(a)
console.log(t3) // => ['1', '2', '3'] 这比Array.prototype.slice.call(a, 0)更容易!!

作为数组的字符串

字符串也可以像上面这样使用数组方法。字符串不是数组。

1
2
3
let s = 'haha'
s.charAt(0) // 'h'
s[0] // 'h'

字符串是不可修改的值,所以当拿它当数组用的时候,是只读的。所以push sort serverse splice这些对字符串不起作用,静默失败,建议转数组后操作然后转回(麻烦捏)。

小结

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。

稀疏数组你到底是个什么样的庞然大物?我在理解这些数组方法的时候,大部分的时间都在考虑它们如何处理你。