拼接“1亿行字符串”你会遇到什么问题?

在这里插入图片描述

本文将涉及到的三方面的内容,如下:

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万数据时候当前场景它们各自的执行效率如何呢,下面是统计后的对比

在这里插入图片描述
在这里插入图片描述

代码结构如下:
在这里插入图片描述

对代码感兴趣同学可以看 这里;

整体性能的对比结果很显然:

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();

使用上面的方法测试不同数量级的运行时间得到如下参考数据:

在这里插入图片描述

整个过程中会多次创建新的对象,并频繁调用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来设定堆内存。在经过几次测试之后得到如下参考数据:

在这里插入图片描述

很显然一台内存是16GB的MacBook Pro是无法满足直接在内存中创建1亿行Insert SQL语句的。那么如何完成上述需求?

既然无法通过1个1一次的For循环来生成,那么可以通过多个多次的For循环,比如执行10次1千万的For循环。

按照上面的思路,执行了两次操作:
1,执行10次,每次生成1000万行数据,并将生成数据持久化的同一个文件。
2,执行10次,每次生成1000万行数据,并将生成数据持久化的一个独立的文件。
3,执行100次,每次生成100万行数据,并将生成数据持久化的一个独立的文件。

执行结果如下:
在这里插入图片描述

到此为止需求实现。

03. 拓展

1,如果一开始选择实现方式是每生成一条SQL就append到文件中,那么上面的部分问题你不会遇到。

2,在执行效率上StringBuilder的性能确实好。但是在生成测试数据时,如果数据比1亿条更大,那么需要注意StringBuilder内部有一个capacity,capacity的类型为int,因此存在最大capacity的限制。

3,StringBuilder内部会有byte[]实现,处理大数据量是还需要关注数组越界的问题。

4,最终生成的文件大小可以在实际应用中需要进行判断,使用何种体积的文件,建议使用每个文件100万行数据体积64.9MB的文件,因为打开这个体积的文本文件速度较快(无论是通过Vim还是文本工具)。

5,在处理的大量数据时,除了关注可读性可读性,同时还需要关注效率以及,需要时还需要关注JVM参数设置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值