測試JS數組去重6種不一樣方法的性能

1、測試模板

爲了比較這些不一樣解法的性能,先寫了一個測試模板,用來計算它們的耗時,測試環境是在谷歌瀏覽器。前端

// 先生成一個長度足夠長的數組
let arr1 = Array.from(Array(100000), (item, index) => index);
let arr2 = Array.from(Array(50000), (item, index) => index * 2);
let arr = [...arr1, ...arr2];

// 方法所用時長
console.log(`去重前數組的長度: ${arr.length}`);
let startTime = +new Date();

// unique方法返回去重後的數組
let result = unique(arr);
let endtTime = +new Date();
console.log(`去重後數組的長度: ${result.length}`);
console.log(`耗時:${endtTime - startTime}毫秒`);

// 測試方法
function unique(arr){
    if (!Array.isArray(arr)) {
        console.log('arr 不是數組');
        return;
    }
    return unique1(arr); // 具體方法
}
複製代碼

2、測試方法

一、雙重for循環

這個是最容易理解的方法,外層循環遍歷元素,內層循環檢查是否重複。數組

定義一個數組res保存結果,遍歷須要去重的數組,若是該元素已經存在在res中,則說明是重複的元素;若是沒有,則放入 res中。瀏覽器

function unique1(arr){
    var res = [];
    var isRepeat;
    for (let ai = 1, alen = arr.length; ai < alen; ai++) {
        isRepeat = false;
        for (let ri = 0, rlen = res.length; ri < rlen; ri++) {
            if (res[ri] == arr[ai]) {
                isRepeat = true;
                break;
            }
        }
        if (!isRepeat) res.push(arr[ai]);
    }
    return res;
}
// 輸出
// 去重前數組的長度: 150000
// 去重後數組的長度: 100000
// 耗時:3967毫秒
// 重複試驗輸出:395四、3990、394二、397五、3964
複製代碼

嗯嗯,這個方法處理一個15w長度的數組要4秒左右。這個寫法是將原數組中的元素和結果數組中的元素一一做比較。數據結構

下面這個寫法是比較兩個原數組,而後將重複元素的最後一個元素放入結果數組中。性能

function unique1(arr) {
    var res = [];
    var alen = arr.length;
    for (let ai = 1; ai < alen; ai++) {
        for (let ri = ai + 1; ri < alen; ri++){
            if (arr[ai] === arr[ri]) ri = ++ai;
        }
        res.push(arr[ai]);
    }
    return res;
}
// 耗時:931四、923二、912二、9220、9175
複製代碼

可是這個寫法處理15W長度的數組耗時是九千多毫秒。緣由是第一個寫法是將原數組arr和結果數組res比較,第二個寫法是將兩個原數組本身做比較(這個時間複雜度沒得說,就是O(n^2))。結果數組res一開始的長度是0,和原數組比較至關於一個巨人揹着一個小小孩走路,雖然這個小小孩會慢慢長大,但老是比巨人要小不少,天然會走快些。第二個寫法是原數組跟原數組比較,至關於一個巨人揹着另外一個一樣重的巨人走路,這樣天然也走不快。測試

因此用雙重for循環比較時,不要比較兩個原數組,要將原數組和結果數組比較。ui

另外,不要使用splice去除原數組重複的這種方法,這種更加耗時。它不只是要作`len--`和`i--`的操做,這個方法自己是這樣的,你每在中間移除一個值,後面的所有就要往前面移一個位置,這無疑是很耗時的。因此,在數據量大的時候是很是很是不建議用這種方法去去重。 spa

二、循環方法 + indexOf()

在實驗循環方法:for循環、for...offilter()map()forEach()reduce()等方法分別搭配indexOf()時,獲得的結果都相差不大,結構也都差很少,都是一次循環加indexOf()。 因此便將它們歸爲一類。code

一、for循環 + indexOf()
function unique2(arr){
    var res = arr.length > 0 ? [arr[0]] : [];
    for (let ai = 1, alen = arr.length; ai < alen; ai++) {
        if (res.indexOf(arr[ai]) === -1) res.push(arr[ai]);
    }
    return res;
}
// 耗時:776一、773五、784五、777五、7828
複製代碼
二、for...of + indexOf()
function unique2(arr) {
    var res = [];
    for (let item of arr) {
        if (res.indexOf(item) === -1) res.push(item);
    }
    return res;
}
// 耗時:780五、777一、783五、784一、7787
複製代碼
三、filter() + indexOf()
function unique2(arr) {
    var res = arr.filter((item, index)=> {
        return arr.indexOf(item) === index;
    })
    return res;
}
// 耗時:783三、780九、7820、774九、7778
複製代碼
四、map() + indexOf()
function unique2(arr) {
    var res = arr.map((item, index)=> {
        return arr.indexOf(item) === index;
    })
    return res;
}
// 耗時:777三、784二、788四、781五、776三、7855
複製代碼
五、forEach() + indexOf()
function unique2(arr) {
    var res = [];
    arr.forEach((item, index, array) => {
        if (res.indexOf(item) == -1) res.push(item);
    })
    return res;
}
// 耗時:780五、778九、773七、777一、7780
複製代碼
六、循環方法 + includes()

也能夠是循環方法搭配includes()使用,includes()方法是用來判斷一個數組是否包含一個指定的值,若是是返回 true,不然false。includes()indexOf()相似,耗時也都差很少。對象

function unique2(arr) {
    var res = [];
    for (let item of arr) {
        if (res.includes(item) === false) res.push(item);
    }
    return res;
}
// 耗時:778五、786九、792五、787二、7972
複製代碼
小結

啊啊!!!這種一次循環加一個indexOf()或者includes()(還有一個lastIndexOf (),誰想試的話就本身試哈,再見!!!)的方法終於試完了,之後誰跟我說這種方法好用我跟他急,簡潔是簡潔了,可是性能真的是不敢恭維(時間複雜度是O(n^2))。雙重for循環寫好了性能都比這個方法要好!!!

三、sort()排序後遍歷數組

先使用 sort()對數組進行排序,而後比較相鄰元素是否相等,從而排除重複項

function unique3(arr) {
    arr = arr.sort();
    var res = arr.length > 0 ? [arr[0]] : [];
    for (let ai = 1, alen = arr.length; ai < alen; ai++) {
        if (arr[ai] !== arr[ai-1]) res.push(arr[ai]);
    }
    return res;
}
// 耗時:1三、1三、1四、1三、12
複製代碼

喵喵喵!!我沒有看錯吧!這種方法只要13毫秒?只走了一次排序和一次循環,這個方法的時間複雜度是O(n),嗯嗯,效率果真高不少。

來來,把數組長度乘以10,咱們來看看要多長時間。

// 耗時:10七、11六、130、11四、120
複製代碼

把數組長度乘以10之後,耗時也差很少是乘以10。不過仍是很快嘛!

四、new Set()

ES6新增了Set這一數據結構,相似於數組,Set的成員具備惟一性,基於這一特性,就很是適合用來作數組去重。

function unique4(arr) {
    return Array.from(new Set(arr)); // 耗時:1四、1三、十一、十、11
    // return [...new Set(arr)]; // 耗時:1五、1四、十一、十、10
}
複製代碼

關於Set的使用,還能夠有兩種搭配,一個是Array.from(),一個是...(展開運算符),耗時都差很少。那一種均可以。

來,乘10。(「你的月薪多少?」,"3w。","請你誠實!",「我乘10了呀!」)

// 耗時:22一、25九、22三、22四、221
// 耗時:20九、21六、21五、22五、227
複製代碼

150w長度的數組,性能仍是OK的。

五、new Map()

ES6新增的還有Map(),這種方法和Set()相似,也是具備惟一性。

function unique5(arr) {
    var res = [];
    var myMap = new Map();
    for (var ai = 0, alen = arr.length; ai < alen; ai++) {
        if(!myMap.has(arr[ai])){
            myMap.set(arr[ai], 1);
            res.push(arr[ai]);
        }
    }
    return res;
}
// 耗時: 1九、2一、20、1八、21
複製代碼

這個就不乘10了,這種類型的數組去重用Set()就好,代碼簡潔又快捷。

六、Object惟一屬性

利用對象的屬性不會重複這一特性,校驗數組元素是否重複。

function unique6(arr){
    var obj = {};
    var res = [];
    for (let item of arr) {
        if (!obj[item]) {
            res.push(item);
            obj[item] = 1;
        }
    }
    return res;
}
// 耗時: 十一、1七、1五、十、10
複製代碼

這種法子也是能夠的嘛,可是由於對象的Key是字符串,仍是有一些限制的,可是在這裏咱們就不討論了,這裏測試比較的是性能。

3、總結

在這裏我要爲雙重for循環正名,它並無想象中的那麼耗性能,關鍵在於怎麼寫,並且這種方法是兼容性最好的,哈哈。不過在ES6新出的語法面前,它依然是個弟弟。對於這幾種去重的方法,我最喜歡的就是Set()了,真是好用。我大前端真是發展得愈來愈快愈來愈好了,哈哈,你們一塊兒加油吧!

若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。

相關文章
相關標籤/搜索