本文将涉及到的三方面的内容,如下:
1,一个10万次的for循环,4种实现的性能对比
2,直接将For循环改为1亿次,遇到的问题
3,拓展
00. 需求
原计划是生成1亿条模拟数据,详细的需求如下:
创建1亿条Insert SQL语句,例如:
INSERT INTO products (`id`,`code`) value (1, '000000000');
其中,
id 类型为INT(11)
code 类型为VARCHAR(9),值区间00000000-99999999,长度不足9位的在前面补充0使其长度满足9位。
01. 一个10万次的for循环,4种实现的性能对比
最终目的是1亿,但是会涉及到时间消耗问题,所以计划先从10万行数据开始查看一下实现方式上的效率对别。决定使用方式之后将数据量升级到1亿行最终实现需求。
选择了日常代码中经常见到的4种方式实现方式:
1, 使用 “+” 来拼接字符串;
2,使用StringBuffer | StringBuilder来拼接字符串;
3,使用String.formate() 来格式化字符串,并用 “+” 拼接字符串;
4,使用String.formate() 来格式化字符串,并用 StringBuffer | StringBuilder 拼接字符串;
在10万数据时候当前场景它们各自的执行效率如何呢,下面是统计后的对比
01四种实现方式的效率对比.png 02四种实现方式的效率对比图.png代码结构如下:
03类图.png
对代码感兴趣同学可以看 这里;
整体性能的对比结果很显然:
StringBuilder > "+"拼接 > String.formate()
使用 “+” 拼接字符串,虽然底层的实现使用StringBuilder做了优化,并不是直接用 “+” 拼接 直接代替StringBuilder那么简单。
一次 “+”字符串拼接,相当于执行
new StringBuilder(str)
.append(newStr)
.toString();
例如:
String name = "P" + "a" + "g" + "e";
相当于
String name = new StringBuilder(
new StringBuilder(
new StringBuilder("P")
.append("a")
.toString()
).append("g")
.toString()
).append("e")
.toString();
使用上面的方法测试不同数量级的运行时间得到如下参考数据:
05simulate_string_+.png整个过程中会多次创建新的对象,并频繁调用toString()方法,最终导致了其性能的下降。
通过上面的结果我们可以得到如下结果:
1,在上面需求的复杂度的情况下,小于1000条数据时,选择哪种实现均可,可以优先考虑可读性,所以可以优先考虑String.formate();
2,在上面需求的复杂度的情况下,大于1000条数据时,优先考虑StringBuilder或String.formate()。String.formate()通常能够带来更好的可读性,但是如果性能上造成了很大的困扰时,请考虑使用StringBuilder;
3,如果场景不是足够简单,尽量避免使用“+”拼接字符串,因为它既没有带来很好的可读性,也没带来很好的性能。
02. 目标1个亿
上面数据量只到了100,000(即:10万)条,而我们的目标是100,000,000(即:1亿)条。
通过100条 - 10万条数据的过程,我们字符串拼接的性能并不是线程增长的,在10W的时候:
(1)“+”已经达到38s,一次可以推断 1亿条记录至少 38s x 1000 ≈ 10.5h。
(2)StringBuilder只用了43ms,所以它可能带给我们惊喜。
(3)String.formate + StringBuilder消耗了532ms,1亿条记录至少 0.532s x 1000 ≈ 532s(9分多钟)
所以直接使用方案2来StringBuffer生成SQL语句是个好选择,直接将循环测试设为为1亿次。
当将循环次数设定为循环1亿次时,却出了出了问题。
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
JVM可以通过-Xms和-Xmx来设定堆内存。在经过几次测试之后得到如下参考数据:
04堆内存设置.png很显然一台内存是16GB的MacBook Pro是无法满足直接在内存中创建1亿行Insert SQL语句的。那么如何完成上述需求?
既然无法通过1个1一次的For循环来生成,那么可以通过多个多次的For循环,比如执行10次1千万的For循环。
按照上面的思路,执行了两次操作:
1,执行10次,每次生成1000万行数据,并将生成数据持久化的同一个文件。
2,执行10次,每次生成1000万行数据,并将生成数据持久化的一个独立的文件。
3,执行100次,每次生成100万行数据,并将生成数据持久化的一个独立的文件。
执行结果如下:
06生成的文件大小.png
到此为止需求实现。
03. 拓展
1,如果一开始选择实现方式是每生成一条SQL就append到文件中,那么上面的部分问题你不会遇到。
2,在执行效率上StringBuilder的性能确实好。但是在生成测试数据时,如果数据比1亿条更大,那么需要注意StringBuilder内部有一个capacity,capacity的类型为int,因此存在最大capacity的限制。
3,StringBuilder内部会有byte[]实现,处理大数据量是还需要关注数组越界的问题。
4,最终生成的文件大小可以在实际应用中需要进行判断,使用何种体积的文件,建议使用每个文件100万行数据体积64.9MB的文件,因为打开这个体积的文本文件速度较快(无论是通过Vim还是文本工具)。
5,在处理的大量数据时,除了关注可读性可读性,同时还需要关注效率以及,需要时还需要关注JVM参数设置。
网友评论