背景
遇到要在本地解析一套文件系统,其中有以下特征:
- 每个文件预计
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
来截取12
、40
等等数值效率非常低下。
优化方案:
- 尽量使用字符串遍历,得到起始与结束的偏移量,通过
String(charArray,offset , length)
来构建字符串,效率更高
4. SimpleDateFormatter是非线程安全的
SimpleDateFormatter是非线程安全的,需要自己做同步
优化方案 :
- 尽量使用
ThreadLocal
保存SimpleDateFormatter对象 - 创建
SimpleDateFormatter
非常耗时,尽量在单个线程中初始化一个。
网友评论