Java中的SimpeDateFormatter优化

背景

遇到要在本地解析一套文件系统,其中有以下特征:

  • 每个文件预计20M,每个文件约10W行数据
  • 日志按日期进行分类,同时按时间顺序增加
  • 每行都会有[2021-05-14 12:01:19.195]这种时间

原来是通过正则表达式以及SimpleDateFormatter.parse()进行解析,从而导致解析一个文件耗时非常长.

优化前每个文件预计在80s左右,在优化完后,每个文件只需要花费9s左右即可完成。

优化手段

1. 尽量不要使用Pattern

由于正则表达式会在遍历字符串的时候进行回溯导致匹配之间过长。

优化方案:

  • 如果有标准格式或者分隔符,例如[...]....#....则尽量使用字符遍历以及StringBuilder.append来组合字符串。

2. 尽量少使用SimpleDateFormatter.parse()计算时间

如果一个文件中如果出现大量的日期需要转换成时间戳,例如:[2021-05-14 12:01:19.195]转换成1620964879195的话,尽量不要直接通过SimpleDateFormatter.parse().time来转时间。

优化方案:

  • 通过SimpleDateFormatter.parse("yyy-mm-dd")将当天的时间戳计算,并且将转换结果缓存起来
  • 通过字符匹配,以及char - '0'来转换成数字,通过乘法来得到具体的数值。例如12的数值可通过:('1'-'0')*10 + ('2'-'0') = 12来获得
  • 最后通过解析的日期、十分秒、毫秒相加,得到当前时间戳。

由于每年的日期都不确定,所以需要通过SimpleDateFormatter.parse("yyy-mm-dd").time来解析某一天的时间戳。

        // 时间解析阶段
        const val PARSE_STAGE_HOUR = 0
        const val PARSE_STAGE_MINUTE = 1
        const val PARSE_STAGE_SECONDS = 2
        const val MILESECONDS_HOUR = 3600000
        const val MILESECONDS_MINUTE = 60000
        const val MILESECONDS_SECOND = 1000
        const val SYMBOL_ASCII_0 = '0'
        const val SYMBOL_COLON = ':'
        const val SYMBOL_DOT = '.'
        // 用于计算当前日期的时间戳
        val dayFormatter = SimpleDateFormat("yyyy-MM-dd")
        // 用于保存日期对应的时间戳
        val dayMap = mutableMapOf<String, Long>()

private fun parseLogTime(timeStr: String) {
        // 解析[2021-05-07 +80 16:08:07.155]的时间戳
        val array = timeStr.toCharArray()
        // 解析到2021-05-07的时间戳
        val day = String(array, 0, PARSE_OFFSET_DAY)
        var dayTimeStamp = dayMap[day] ?: 0L
        if (dayTimeStamp == 0L) {
            // 如果没有缓存,则需要解析并且保存
            dayTimeStamp = dayFormatter.parse(day).time
            dayMap[day] = dayTimeStamp
        }
        // 开始解析16:08:07.155的时间戳
        val length = array.size
        val index = length - PARSE_OFFSET_HOUR
        var hour = 0
        var minutes = 0
        var seconds = 0
        // 解析HOUR阶段
        var stage = PARSE_STAGE_HOUR
        var num = 0
        for (i in index until length) {
            // 从16:08:07.155开始遍历字符
            val c = array[i]
            if (c == SYMBOL_COLON || c == SYMBOL_DOT) {
                // 如果遇到:或者.,则进入下一个阶段,保存后将num清0
                when (stage) {
                    PARSE_STAGE_HOUR -> hour = num
                    PARSE_STAGE_MINUTE -> minutes = num
                    PARSE_STAGE_SECONDS -> seconds = num
                }
                stage++
                num = 0
            } else {
                num = (num * 10) + (c - SYMBOL_ASCII_0)
            }
        }
        // 最后解析完的num就是mileseconds
        timeStamp = dayTimeStamp + hour * MILESECONDS_HOUR +
                minutes * MILESECONDS_MINUTE + seconds * MILESECONDS_SECOND + num
    }

3. 尽量不要使用subString等函数进行字符串分割

subString来截取1240等等数值效率非常低下。

优化方案:

  • 尽量使用字符串遍历,得到起始与结束的偏移量,通过String(charArray,offset , length)来构建字符串,效率更高

4. SimpleDateFormatter是非线程安全的

SimpleDateFormatter是非线程安全的,需要自己做同步

优化方案 :

  • 尽量使用ThreadLocal保存SimpleDateFormatter对象
  • 创建SimpleDateFormatter非常耗时,尽量在单个线程中初始化一个。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容

  • 编写和优化Go代码 本文档概述了编写高性能Go代码的最佳实践。 虽然有些讨论会提高单个服务的速度(通过缓存等),但...
    freelang阅读 2,226评论 0 4
  • 点赞+收藏 就学会系列,文章收录在 GitHub JavaEgg ,N线互联网开发必备技能兵器谱 Java8早在2...
    JavaKeeper_海星阅读 311评论 0 0
  • session与cookie的区别和联系1.存放位置:Session保存在服务器,Cookie保存在客户端。2.存...
    _fhs阅读 899评论 0 1
  • 1Java 基础知识面向对象→ 什么是面向对象面向对象、面向过程 略面向对象的三大基本特征和五大基本原则答java...
    晴天M雨天阅读 437评论 0 0
  • 彩排完,天已黑
    刘凯书法阅读 4,206评论 1 3