JS前端千萬級彈幕數據循環優化示例

引言

最近做瞭直播相關的業務,然後對於大數據相關的優化做瞭一下復盤。

為瞭瞭解我是怎麼做這個優化的,我們先從如何按照特定的條件刪除一個數組說起。

1、如何刪除數組中的元素

場景:有一個數組,需要刪除滿足條件的數組。

示例

const arr = [1,2,3,4,5,6,7,8]

刪除小於5的元素,刪除後的元素為

const arr2 = [5, 6, 7, 8]

代碼實現:

const arr = [1,2,3,4,5,6,7,8]
for(let i = 0, len = arr.length; i < len; i++) {
	if(arr[i] < 5) {
		arr.splice(i, 1)
	}
}

結果如下

arr = [2, 4, 5, 6, 7, 8

不是我們預期的結果

分析原因:刪除操作會使得對應索引值位上的元素清空,整個數組中的元素向前移動一位,補位的元素會填充到執行刪除操作的索引值位置上,移位之後如果不進行任何操作繼續下一個循環,會導致補位元素跳過遍歷,為瞭防止這種補位元素跳過遍歷現象,應該在刪除操作後將索引值減1,對執行刪除操作的索引值位置再進行一次遍歷 。

改進:

const arr = [1,2,3,4,5,6,7,8]
for(let i = 0, len = arr.length; i < len; i++) {
	if(arr[i] < 5) {
		arr.splice(i, 1)
    i--;
	}
}
// arr = [5, 6, 7, 8] 符合預期

這個是做瞭正序循環刪除,也可以使用倒序循環刪除:

const arr = [1,2,3,4,5,6,7,8]
for(let i = arr.length - 1; i >= 0; i--) {
	if(arr[i] < 5) {
		arr.splice(i, 1)
	}
}
// arr = [5, 6, 7, 8] 符合預期

2、10000,000條消息如何優化?

場景

彈幕消息發送場景模擬(偽直播形式,沒有進度條):假設我們有10000,000條消息,根據視頻播放的進度展示對應的消息,不展示歷史消息。

常規思路:

循環遍歷整個消息列表,時刻監聽視頻播放的進度,根據視頻播放的時間戳和消息發送的時間戳先相等,然後展示消息,依次循環。

產生的問題

每次視頻進度變化都會循環整個消息列表,當循環還沒完成,下一個播放進度監聽觸發瞭,又開始下一個循環,這樣就會造成性能的損耗。

優化策略

我們從上面的分析可以看出,當消息發送瞭一條,就可以從原始數據刪除這條消息,然後跳出循環,這樣循環的次數始終控制在幾次(或者幾十次)的范圍(有可能同一個時間段同時有幾條消息甚至幾十條消息)等下一個播放進度監聽觸發,開始循環原始數據,這是之前以後發送過得數據刪除瞭,就不會再循環刪除過的數據,始終循環需要發送的那幾條,找到瞭就直接跳出循環。

代碼實現

// 模擬原始消息列表,
const newList = new Array(10000000).fill(1).map((item, index) => {
  return {
    time: (index + 1) * 1000,       // 消息發送的時間,一秒一個
    content: `這是第${index + 1}s發送的消息` // 消息發送的內容
  }
})
// 發送的消息列表
const sendList = [];
function getMessage(time) {
  let j = 0; // 循環次數
  for(let i = 0, len = newList.length; i < len; i++) {
    const item = newList[i];
    j++;
    // 這裡的time如果不是1000、2000,而是1234、1214這種,就需要取一個浮動范圍
    // 我這裡就是簡單用瞭定時器,所以比較簡單
    if(item.time === time) {
      sendList.push(newList[i])
      newList.splice(i, 1)
      i--;
    } else if(sendList.length > 0) {
        break;
    }
  }
  console.log('播放進度', time)
  console.log('循環的次數', j);
  console.log('接收的消息的長度', sendList.length, sendList);
  console.log('原始消息的長度', newList.length);
}
let time = 0;
// 定時器,1s觸發一次
setInterval(() => {
  time += 1000;
  getMessage(time);
}, 1000)
// 消息格式
newList = [
  {time: 1000, content: '這是第1s發送的消息'},
  {time: 2000, content: '這是第2s發送的消息'},
  ...
]

效果展示

小結

上面優化策略隻有兩條

發送過的消息刪除,下次少循環。

當找到滿足條件的數據,直接跳出循環,後面的數據不再循環。

缺點:使用slice也會消耗性能,不可取,並且操作繁瑣。

遊標法代替splice

我們這裡不再使用slice的方案,設置一個遊標,記錄循環的初始位置,下次循環直接從遊標記錄的位置開始循環,然後滿足查找的條件就break,這樣既不破壞原來的數組,也能有效的減少循環的次數。

  let index = 0, sendList =[];
  function getMessage(time) {
    for(let i = 0, len = newList.length; i < len; i++) {
        const item = newList[i];
        // 這裡的time如果不是1000、2000,而是1234、1214這種,就需要取一個浮動范圍
        // 我這裡就是簡單用瞭定時器,所以比較簡單
        if(item.time === time) {
          index = i;
          sendList.push(newList[i])
        } else if(sendList.length > 0) {
            // 這裡的查詢結束條件為,對應的時間范圍之外沒有消息瞭,並且需要發送的消息列表有消息,才break
        // 這裡的結束條件想不到什麼更好的方案瞭
        break;
    }
  }
}

上面我們隻對視頻播放的時候做瞭優化,如果下次用戶進來進度直接接近尾聲瞭,這時候首次查找尾部消息的時候,就需要把前面所有的消息都循環一遍,所以還需要繼續優化。

二分查找

當首次加載的時候,采用二分法查找到消息開始的位置,當視頻播放的時候再根據查找到的index去循環消息體。

function binarySearch(arr, time) {
    let upperBound = arr.length - 1; // 記錄長度
    let lowerBound = 0; // 記錄上次二分的位置
    let mid;
    // 切半分的位置 小於或等於 1就停止循環瞭
    while (lowerBound <= upperBound) {
      // (當前總長度 + 當前中間點位置長度) / 2 = 實際的中間點位置
      mid = Math.floor((upperBound + lowerBound) / 2);
      const item = arr[mid];
      const maxTime = time + 500;
      const minTime = time + 500;
      // 當輸入的值大於中間值時,向後移動一位
      if (time > maxTime) {
        lowerBound = mid + 1;
      } else if (time < minTime) {
        // 當輸入值小於中間值時,向前移動一位
        upperBound = mid - 1;
      } else {
        return mid; // 找到指定數據位置
      }
    }
    return -1;
  }
function findIndex(startPlayTime: number) {
    const searchIndex = binarySearch(this.messageList, time);
    // 賦值索引,用於快速發送消息
    if (searchIndex !== -1) {
      index = searchIndex;
    }
  }

完結

寫到這裡本篇文章就不再會更新瞭,從最開始的splice方法,然後到後面的遊標法和二分法,做瞭逐漸的優化。

這個也是在項目中每次迭代去做的優化(前提是給你的排期你能有時間去做)。本文涉及的知識點可能並不是很重要,在這裡我要跟大傢說的是,我們平時在寫代碼的時候,要善於發現代碼的可優化空間,如果你發現瞭並且實事求是的去做瞭,你的能力就會有更大的提高,而且這個發現的過程你可以找同事,找leader去給你review代碼,在業務中沉淀出來的代碼比你自己平時寫個小demo寫的代碼更能讓你成長。

更多關於JS前端數據循環優化的資料請關註WalkonNet其它相關文章!

推薦閱讀: