記一次nodejs內存溢出、前端頁面崩潰

受到世界盃的影響,我想寫一個程序,可以手動錄入一場比賽所有盤口的賠率,勝平負3種情況、讓球勝平負3種情況、比分26種情況、進球數8種情況、半全場9種情況,有的場次不能單買勝平負、讓球勝平負,或者直接不開這種盤口,但相互之間串在一起不受限制。

以A、B、C、D爲場次的標識,默認一種情況投入10塊錢,算出能夠贏的錢再排序。當只有一場比賽的時候,很好算。當有A、B兩場時,場次本身的結果有A、B、AB,當有A、B、C、三場的時候,場次本身組合的結果就有A、B、C、AB、AC、BC、ABC。

首先要有得出涉及場次所有組合的算法,其中的規律在於,當處理的場次序號時i,i本身是一種組合,先把i之前的組合和i結合一遍,再把i放進去,依次類推,直到數組的最後一項,那麼算法如下。

const teamList = ["A", "B", "C", "D"];
let teamCombinationList = [];
getTeamCombinationList(0);
function getTeamCombinationList(index) {
  const list = [];
  teamCombinationList.forEach((el) => {
    list.push([...el, teamList[index]]);
  });
  list.push([teamList[index]]);
  teamCombinationList = [...teamCombinationList, ...list];
  if (index < teamList.length - 1) {
    getTeamCombinationList(index + 1);
  }
}

結果如下

[
  [ 'A' ],
  [ 'A', 'B' ],
  [ 'B' ],
  [ 'A', 'C' ],
  [ 'A', 'B', 'C' ],
  [ 'B', 'C' ],
  [ 'C' ],
  [ 'A', 'D' ],
  [ 'A', 'B', 'D' ],
  [ 'B', 'D' ],
  [ 'A', 'C', 'D' ],
  [ 'A', 'B', 'C', 'D' ],
  [ 'B', 'C', 'D' ],
  [ 'C', 'D' ],
  [ 'D' ]
]

此時只需遍歷teamCombinationList,有一個就是單場、有兩個就是2串1、有三個就是3穿1,因爲場次的個數不確定,現在需要一個通用算法實現類似多層for循環的結果。因爲每個場次最多有51種結果,以A0、A1這種方式簡單替代下。

[
    ["A0", "A1", "A2"],
    ["B3", "B4"],
],

舉例,所有的結果是 'A0B3','A0B4', 'A1B3', 'A1B4', 'A2B3','A2B4'

算法核心邏輯是實現一個for循環,如果不是最後一個場次,則當前結果傳給下一個遞歸,再進行for循環,如果是最後一個場次,則組合結果放進公共數組裏。

/**
 * A  'A0','A1','A2'
 * B  'B3','B4'
 * C  'C0','C5'
 */
let oddResultList = [];
let teamCombinationList = [
  [["A0", "A1", "A2"]],
  [
    ["A0", "A1", "A2"],
    ["B3", "B4"],
  ],
  [["B3", "B4"]],
  [
    ["A0", "A1", "A2"],
    ["C0", "C5"],
  ],
  [
    ["A0", "A1", "A2"],
    ["B3", "B4"],
    ["C0", "C5"],
  ],
  [
    ["B3", "B4"],
    ["C0", "C5"],
  ],
  [["C0", "C5"]],
];

for (let i = 0; i < teamCombinationList.length; i++) {
  const list = teamCombinationList[i];
  againForEach([], 0);
  function againForEach(againList, index) {
    list[index].forEach((el) => {
      if (index === list.length - 1) {
        let str = "";
        [...againList, el].forEach((item) => {
          str += item;
        });
        oddResultList.push(str);
      } else {
        againForEach([...againList, el], index + 1);
      }
    });
  }
}

oddResultList 的結果如下

[
  'A0',     'A1',     'A2',     'A0B3',
  'A0B4',   'A1B3',   'A1B4',   'A2B3',
  'A2B4',   'B3',     'B4',     'A0C0',
  'A0C5',   'A1C0',   'A1C5',   'A2C0',
  'A2C5',   'A0B3C0', 'A0B3C5', 'A0B4C0',
  'A0B4C5', 'A1B3C0', 'A1B3C5', 'A1B4C0',
  'A1B4C5', 'A2B3C0', 'A2B3C5', 'A2B4C0',
  'A2B4C5', 'B3C0',   'B3C5',   'B4C0',
  'B4C5',   'C0',     'C5'
]

獲得所有結果後進行排序,我一開始是調接口實現,後臺基於nodejs實現的,當場次少於等於3場返回還是很快,但場次是4的時候,報錯內存溢出,我以爲是自己算法寫的有問題,造成死循環,但是前面也沒啥問題...

因爲實際的數據結構比較複雜,我就使用--max-old-space-size命令加大,打開任務管理器,發現內存一致逐漸攀升道6、7千M的位置就不上去,加了幾個輸出,發現很快就算出結果,因爲數組本身存儲的是引用,其實這一塊是不會內存溢出的,而是卡在了排序上,把排序代碼去掉,還是不行,因爲要把所有數據序列化後再返給前端,那麼這個json是很大的。

其實所有數據都在前端的時候,是沒必要再掉接口的,於是我把代碼移到前端執行,4個場次雖然慢了些,但還是能展示,5個場次的時候也不行了,頁面崩潰。

1個場次的時候最多51種,2個場次最多3015,3個場次最多166359,4個場次最多9150603,5個場次僅僅是計算處所有結果頁面就崩潰了。至於優化的方式,嘗試用簡單的標記代替對象,等用到的時候再解析讀取,然而一頓操作後還是不行,5個場次有503284374種結果,上億級別了,我只是執行Array(503284374).fill(1)就崩潰了。

代碼地址: https://github.com/xiaodun/sf-notes

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章