Electron 时间数据的本地化存储方案效率测试

2,789 阅读4分钟

本地存储方式

Electron的本地数据存储方式有很多种,可以直接读写文件,也可以用相关的库,方便数据管理。

经过对比选择lowdb,基于json文件数据储存方式

点击查看对比详情

时间数据的存储方案

数据结构

最初的错误思路是按年月日的嵌套索引来存储如下,一是害怕数据量太大影响效率(后面有效率的测试结果),二方便按年月日查询数据,三是方便数据的显示和阅读;

"timeData": {
  "2019": {  //年
    "11": {  //月
      "12": [   //日
        {
          "start": "21:14",
          "end": "21:25",
          "duration": "0:11"
        },
        {
          "start": "23:34",
          "end": "23:45",
          "duration": "0:11"
        }
      ],
      "13": [
        {
          "start": "23:34",
          "end": "23:45",
          "duration": "0:11"
        }
      ]
    }
  }
}

为了实现上面数据的读取和添加,想了很多方法,结果发现很复杂,详见: JavaScript条件嵌套代码的可读性优化

正确的方式

后来又发现日期以字符方式存储,计算很不方便,而且完全没考虑时区的转换问题。经Google查询,正确的方式应该是存储时间的绝对值,即相对1970-00-00 00:00:00 UTC的毫秒数的时间戳,这样有以下好处:

  1. 与所有系统的时间处理方式一致
  2. 不管在全球任何地区,时间都是正确的,不用考虑时区的转换
  3. 遵循数据存储与显示分离的原则
  4. 基于整数的数据查询和计算都很方便
  5. 很容易计算时间差

数据结构如下

{
  "timeData": [
    {
      "start": 1573865223246,
      "duration": 120390
    },
    {
      "start": 1573866723246,
      "duration": 1500000
    },
  ]
}

数据处理方法

查找给定日期的的数据

参数可以是一个日期,如:2019/11/16 查该日, 2019/11 查该月,2019/11/11 12 查该时之内的数据。

也可以传入一个时间范围数组,如:['2019/11/16 14:30', '2019/11/19']

function getData(start: string | string[]): any[] {
  let [b, e] = [0, 0]
  if (typeof start === 'string') {
    //* 传入为日期字符
    if (start.length === 4) start = start + '/' //末尾加 /,否则只Date.parse('2019')会按本地时间转换而多出8小时
    if (start.length === 13) start = start + ':' //末尾加 :,否则Date.parse('2019/11/11 12')会报错

    let startTime = new Date(start)
    let add1: any = {
      19: () => +startTime + 1000, //加1秒
      16: () => +startTime + 60 * 1000, //加1分钟
      14: () => +startTime + 60 * 60 * 1000, //加1小时
      10: () => +startTime + 24 * 3600 * 1000, //加1天
      7: () => +startTime.setMonth(startTime.getMonth() + 1), //加1月
      5: () => +startTime.setFullYear(startTime.getFullYear() + 1), //加1年
    }
    ;[b, e] = [+startTime, add1[start.length]() - 1] //得到给定的年/月/日的首尾时间,尾时间通过+1个单位时间, 再-1毫秒得到
  } else {
    //* 传入为时间段数组
    if (start[0].length === 4) start[0] = start[0] + '/'
    if (start[1].length === 4) start[1] = start[1] + '/'
    ;[b, e] = [Date.parse(start[0]), Date.parse(start[1])]
  }
  log('getData start', dayjs(b).format('YYYY-MM-DD HH:mm:ss'), b, 'end', dayjs(e).format('YYYY-MM-DD HH:mm:ss'), e)

  return get('timeData').filter((item: any) => item.start >= b && item.start <= e)
}

添加数据

/**
 * 添加数据
 *
 * @param {number} start 格式必须为时间戳,如:1577808000000
 * @param {number} end 同上
 */
function addTime(start: number, end: number) {
  log('addTime start', start, 'end', end)
  get('timeData').push({
    start: start,
    duration: end - start,
  })
  save()
}

时间显示插件

这里推荐一个查看unix时间戳的具体时间的插件:Time Converter

大量数据效率测试

不想看过程的可以直接跳到最后看结论

测试条件

系统:MacBook pro MacOS 14

内存:8G

json数据库文件大小:74MB

数据形式如下,数据量为100万个对象。

{
  "timeData": [
    {
      "start": 1573864067907,
      "duration": 0
    },
    {
      "start": 1573865567907,
      "duration": 1500000
    },
    .
    .
    .
    {
      "start": 1573867067907,
      "duration": 3000000
    },
    {
      "start": 1573868567907,
      "duration": 4500000
    }
  ]
}

数据生成和写入测试

生成100万条数据,代码如下:

  let d = db.get('timeData')
  log('开始')
  for (let index = 0; index < 1000000; index++) {
    let a = index * 25 * 60000
    d.push({
      start: Date.now() + a,
      duration: a,
    })
  }
  db.save()
  log('结束')

结果如下,过程耗时1730毫秒

数据读取和处理测试

遍历100万条数据计算某一天的时段之和,代码如下:

db.loadDb(Path.join(DB_PATH, '/db.json'))
let [b, e] = [Date.parse('2019/11/16'), Date.parse('2019/11/17') - 1],
  wTime = 0
log('1')
db.get('timeData1').forEach((item: any) => {
  if (item.start > b && item.start < e) {
    log('start', new Date(item.start).toLocaleString('zh', { hour12: false }), 'duration', item.duration, 'wTime', wTime)

    wTime += item.duration
  }
})
log(new Date(wTime).toLocaleString('zh', { hour12: false }))

结果如下,遍历查询和处理的过程只花了83毫秒,还是相当快的

结论:

lowdb的本地读写效率较高,可以轻松应对百万量级的数据,一百万的数据量,74MB的json文件数据,写入时间为1730毫秒,遍历读取时间仅需83毫秒。