前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >优雅的避坑-从验证码功能代码优化到JVM栈和堆

优雅的避坑-从验证码功能代码优化到JVM栈和堆

作者头像
行百里er
发布2020-12-02 15:10:12
4020
发布2020-12-02 15:10:12
举报
文章被收录于专栏:JavaJourney

验证码功能

实际业务中用验证码进行登录、注册等场景非常普遍,基本上现在的应用都会有这个功能,Java中已为我们提供了Math.random()以及Random类。

Math.random():

代码语言:javascript
复制
public static double random()

返回大于或等于0.0且小于1.0的double类型的整数。返回值的选择是伪随机的,在这个范围内(近似)均匀分布。

Random类:

代码语言:javascript
复制
public class Random
extends Object
implements Serializable

//创建一个新的随机数生成器
Random() 
//使用一个long类型的种子数创建一个新的随机数生成器
Random(long seed) 

//返回从这个随机数生成器的序列中提取的在0(含)和指定值(不含)之间均匀分布的伪随机int值。
int nextInt(int bound) 

假如业务上要求我们生成一个6位数字的验证码,相信大家都能搞出来,用随机数函数,加上一些手段很容易就能构造出一个验证码。

方式1:

代码语言:javascript
复制
String code = String.valueOf(new Random().nextInt(1000000));
System.out.println("random code---------" + code);
代码语言:javascript
复制
> Task :RandomCodeTest.main()
random code---------950499

想一下这种方法有什么问题没有?

乍一看好像没什么问题,但是看我们的要求,是生成6位验证码,而new Random().nextInt(1000000)返回的是0 <= code < 1000000的随机数,也就是说有可能生成的数不够6位,样本量设置大一点验证一下:

代码语言:javascript
复制
int count = 100;
for (int i = 0; i < count; i++) {
    String code = String.valueOf(new Random().nextInt(1000000));
    System.out.println("random code---------" + code);
}

random结果错误示例

100个样本量就出现了多个错误,要是大型高并发项目,肯定会有验证码不是6位的情况,因此这种生成验证码的方式首先排除掉!

方式2:

我们已经知道Math.random()可以生成0 ~ 1之间的double类型的随机数,因此可以通过截取字符串的方式,获取验证码。

先来看一下Math.random()的结果:

代码语言:javascript
复制
> Task :RandomCodeTest.main()
Math.random()-------0.8806639430958753

从2 ~ 8 位置上截取 0.8806639430958753 字符串就能得到6位数字的验证码:

代码语言:javascript
复制
System.out.println("Math.random()-------" + (Math.random() + "").substring(2, 8));

//运行结果
Math.random()-------304719

样本量设置为100万,也能正确生成:

代码语言:javascript
复制
int count = 1000000;
int wrongNum = 0;
for (int i = 0; i < count; i++) {
    String code = (Math.random() + "").substring(2, 8);
    if (code.length() < 6) {
        wrongNum++;
    }
}
System.out.println("wrongNum-----" + wrongNum);

//运行结果
> Task :RandomCodeTest.main()
wrongNum-----0

一般情况下这种方式就够了,但是在分布式、高并发场景下,这样做的效率并不是最高的。

优化验证码的生成

为什么说上面的方式2不是最好的呢?

图都模糊了

我们分析一下就知道,这种方式是通过先通过 + "" 变成字符串,然后截取字符串的操作完成的;而我们生成验证码只要满足6位数字就行,我要是把生成验证码的方式变成纯数字运算是不是就快一点呢?

验证一下:

代码语言:javascript
复制
int count = 10000000;
long start = System.currentTimeMillis();
//int wrongNum = 0;
for (int i = 0; i < count; i++) {
    String code = (Math.random() + "").substring(2, 8);
}
long end = System.currentTimeMillis();
System.out.println("depends:" + (end - start));
//System.out.println("wrongNum-----" + wrongNum);

start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
    String code = String.valueOf((int) (Math.random() * 9 + 1) * Math.pow(10, 5));
}
end = System.currentTimeMillis();
System.out.println("depends:" + (end - start));

运行结果:

效率提升明显

count样本量1000万,可以看到,运算结果提升了好几倍!

为什么用纯数字运算优化后能提升效率?

前文说过,我是用数字运算代替字符串操作而达到优化目的的,这是因为这些数字都是在JVM上进行操作,而String类对象在里。

JVM栈和堆

运行Java程序时,JVM自己管理着一块内存区域-运行时数据区,运行时数据区根据用途可分为:

  • JVM栈(栈区)
  • 本地方法栈
  • Java堆(堆区)
  • 方法区
  • 程序计数器

其中JVM栈,栈区或者栈内存,主要是存储Java方法执行时的局部变量-以栈帧的形式存储,包括基本数据类型、对象的引用都在栈区,方法执行结束后释放。

栈帧是每个线程锁私有的,线程执行完了,占内存就释放了。一个Java方法被调用了,就会有栈帧压入虚拟机栈,当方法执行完毕,出栈。

而堆内存,是垃圾收集器管理的主要区域,该内存区域主要存放Java的对象实例,JVM只有一个堆区,它是线程中共享的。堆中不存放基本数据类型和对象引用,只存放对象本身和数组本身。

基于以上分析,可以得出结论:处于栈区的数据操作比在堆区中的快,因为栈区的东西用完了栈空间立刻就被回收了,而堆空间则需要等待GC回收。

小结

  • 能用纯数据运算解决问题的尽量不要用字符串
  • 因为基本数据类型存在于栈区,字符串常量池存在于堆区
  • 栈的存取速度比堆快
  • 平常工作中注意细节,你的一次优化有可能带来程序上成倍效率的提升
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 行百里er 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 验证码功能
  • 优化验证码的生成
  • JVM栈和堆
  • 小结
相关产品与服务
验证码
腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档