java实现qq抢红包_微信抢红包到底是怎么抢到的?
“ 微信抢红包功能,一下子拉近了长辈与我们之间的距离,每年都是盼望着亲人群抢红包,今天来给大家分享一下,为啥抢红包有人多,有人少,什么时候抢最合适?”
01
—
微信抢红包
场景描述:
抢红包方式:点对点红包、普通红包(固定值)、拼手气红包
点对点红包是一个微信用户将红包发送给一个微信用户,如果在24小时之内没有领取则会直接退回发送红包用户账户。
普通红包是在微信群红包发送的的一种方式,通过设置单个红包数和红包数就行,同样有24小时未被领取退回发送用户账户的功能。
拼手气红包同样是微信群红包发送的一种方式,通过设置红包个数和总金额,同样有24小时未被领取退回发送用户账户的功能。
解决方案:
参考了一些网上对微信红包的架构描述,只是介绍了一下微信红包是每次抢的时候才进行计算的,不是预先分好的方式,没有找到具体的方案,我这里自己按照自己理解整了一个抢红包的时序图,有不合理的地方,可以留言一起讨论,如下图所示:
本文重点介绍其中第10步的拼手气红包算法,通过一个DEMO程序来猜测微信红包内部可能的实现方式。
红包算法DEMO:
public class RedPackageUtils { /** * 余额(元) */ private static volatile double redPacketMoney; /** * 剩余红包数量 */ private static volatile int redPacketSize; /** * 红包最小值(分) */ private static double min; /** * 定义公平锁 */ private static final Lock LOCK = new ReentrantLock(true); public static void main(String[] args) { //初始化红包人数与金额、每个人抢到的最小值 initRedPackage(8,10, 0.01); //执行多线程抢红包 ExecutorService redPackagePool = getRedPackagePool(8); for (int i = 0; i < 8; i++) { redPackagePool.execute(RedPackageUtils::grabRedPackage); } redPackagePool.shutdown(); } /** * * 实际抢红包逻辑 * */ private static void grabRedPackage() { try { LOCK.lock(); Random random = new Random(); //如果是最后一个抢红包,则返回所有余额 if(redPacketSize == 1){ System.out.println(Thread.currentThread().getName()+" 分得最大值:"+redPacketMoney); System.out.println(Thread.currentThread().getName()+" 实际分得红包:"+redPacketMoney); redPacketMoney = 0; redPacketSize --; System.out.println(Thread.currentThread().getName()+" 分完剩余金额:"+redPacketMoney+" 剩余个数:"+redPacketSize); return; } //保证每个用户至少分到一分钱,所以先减去这部分 double minMoney = ArithmeticUtils.mul(redPacketSize,min,2); double resultMoney = ArithmeticUtils.sub(redPacketMoney,minMoney); //如果扣除最低一分之后为0则不再进行随机分配直接发一分 if(resultMoney <= 0){ System.out.println(Thread.currentThread().getName()+" 分得最大值:"+min); System.out.println(Thread.currentThread().getName()+" 实际分得红包:"+min); redPacketMoney =ArithmeticUtils.sub(redPacketMoney,min); redPacketSize --; System.out.println(Thread.currentThread().getName()+" 分完剩余金额:"+redPacketMoney+" 剩余个数:"+redPacketSize); return; } //为了让前面开红包的人随机范围更大一些,猜测微信红包在这里用了一些策略控制每次抽到红包的上线 double avg = redPacketSize/2; //保证被除数不能为0 if(avg <=0){ avg =0.5; } double max = ArithmeticUtils.div(resultMoney,avg,2); System.out.println(Thread.currentThread().getName()+" 分得最大值:"+max); double money = ArithmeticUtils.mul(random.nextDouble(),max,2); money = money <= min ? 0.01: money; money = Math.floor(money * 100) / 100; System.out.println(Thread.currentThread().getName()+" 实际分得红包:"+money); //处理还剩下的红包数量与金额 redPacketSize --; redPacketMoney = ArithmeticUtils.sub(redPacketMoney,money); System.out.println(Thread.currentThread().getName()+" 分完剩余金额:"+redPacketMoney+" 剩余个数:"+redPacketSize); } catch (Exception e){ System.out.println("出现异常,进行回滚出处理"); } finally { LOCK.unlock(); } } /** * 自定义线程池 * @param people 初始化线程大小 */ private static ExecutorService getRedPackagePool(int people) { ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("redPackage-pool-%d").build(); return new ThreadPoolExecutor(people, people*2, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); } /** * 初始化待抢红包的值 * @param inputSize * @param inputMoney * @param inputMin */ private static void initRedPackage(int inputSize, double inputMoney, double inputMin) { redPacketSize = inputSize; redPacketMoney = inputMoney; min = inputMin; }}
输出结果:
redPackage-pool-0 分得最大值:2.48redPackage-pool-0 实际分得红包:1.65redPackage-pool-0 分完剩余金额:8.35 剩余个数:7redPackage-pool-1 分得最大值:2.76redPackage-pool-1 实际分得红包:2.52redPackage-pool-1 分完剩余金额:5.83 剩余个数:6redPackage-pool-2 分得最大值:1.92redPackage-pool-2 实际分得红包:0.06redPackage-pool-2 分完剩余金额:5.77 剩余个数:5redPackage-pool-3 分得最大值:2.86redPackage-pool-3 实际分得红包:1.37redPackage-pool-3 分完剩余金额:4.4 剩余个数:4redPackage-pool-4 分得最大值:2.18redPackage-pool-4 实际分得红包:0.67redPackage-pool-4 分完剩余金额:3.73 剩余个数:3redPackage-pool-5 分得最大值:3.7redPackage-pool-5 实际分得红包:3.37redPackage-pool-5 分完剩余金额:0.36 剩余个数:2redPackage-pool-6 分得最大值:0.34redPackage-pool-6 实际分得红包:0.18redPackage-pool-6 分完剩余金额:0.18 剩余个数:1redPackage-pool-7 分得最大值:0.18redPackage-pool-7 实际分得红包:0.18redPackage-pool-7 分完剩余金额:0.0 剩余个数:0
根据本地多次模拟红包的情况,如果微信红包的确是按照我上述demo猜测的方式来拼手气的话,那就是根据红包的数量,在三分之二的位置的红包金额容易是最大的,但是也是要看前面抢红包的运气怎么样,如果前面的运气特别好,那就后面的人抢到剩余的红包就往往都比较少了。实际情况是往往红包发出来的几秒钟就抢完了,所以也不好预测你抢的位置,还是建议大家看到红包就马上点,说不定给你排队就在最好的位置呢。注意:java在进行double高精度计算的时候需要使用java.math.BigDecimal进行加减乘除以及比较大小,这里在网上找到了一个现成的工具类,作为搬运工分享给大家
public class ArithmeticUtils { /** 默认除法运算精度*/ private static final int DEF_DIV_SCALE = 10; /** * 提供精确的加法运算 * * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */ public static double add(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.add(b2).doubleValue(); } /** * 提供精确的加法运算 * * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */ public static BigDecimal add(String v1, String v2) { BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.add(b2); } /** * 提供精确的加法运算 * * @param v1 被加数 * @param v2 加数 * @param scale 保留scale 位小数 * @return 两个参数的和 */ public static String add(String v1, String v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); } /** * 提供精确的减法运算 * * @param v1 被减数 * @param v2 减数 * @return 两个参数的差 */ public static double sub(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.subtract(b2).doubleValue(); } /** * 提供精确的减法运算。 * * @param v1 被减数 * @param v2 减数 * @return 两个参数的差 */ public static BigDecimal sub(String v1, String v2) { BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.subtract(b2); } /** * 提供精确的减法运算 * * @param v1 被减数 * @param v2 减数 * @param scale 保留scale 位小数 * @return 两个参数的差 */ public static String sub(String v1, String v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); } /** * 提供精确的乘法运算 * * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */ public static double mul(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.multiply(b2).doubleValue(); } /** * 提供精确的乘法运算 * * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */ public static BigDecimal mul(String v1, String v2) { BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.multiply(b2); } /** * 提供精确的乘法运算 * * @param v1 被乘数 * @param v2 乘数 * @param scale 保留scale 位小数 * @return 两个参数的积 */ public static double mul(double v1, double v2, int scale) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return round(b1.multiply(b2).doubleValue(), scale); } /** * 提供精确的乘法运算 * * @param v1 被乘数 * @param v2 乘数 * @param scale 保留scale 位小数 * @return 两个参数的积 */ public static String mul(String v1, String v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); } /** * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 * 小数点以后10位,以后的数字四舍五入 * * @param v1 被除数 * @param v2 除数 * @return 两个参数的商 */ public static double div(double v1, double v2) { return div(v1, v2, DEF_DIV_SCALE); } /** * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 * 定精度,以后的数字四舍五入 * * @param v1 被除数 * @param v2 除数 * @param scale 表示表示需要精确到小数点以后几位。 * @return 两个参数的商 */ public static double div(double v1, double v2, int scale) { if (scale < 0) { throw new IllegalArgumentException("The scale must be a positive integer or zero"); } BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 * 定精度,以后的数字四舍五入 * * @param v1 被除数 * @param v2 除数 * @param scale 表示需要精确到小数点以后几位 * @return 两个参数的商 */ public static String div(String v1, String v2, int scale) { if (scale < 0) { throw new IllegalArgumentException("The scale must be a positive integer or zero"); } BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v1); return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString(); } /** * 提供精确的小数位四舍五入处理 * * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */ public static double round(double v, int scale) { if (scale < 0) { throw new IllegalArgumentException("The scale must be a positive integer or zero"); } BigDecimal b = new BigDecimal(Double.toString(v)); return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * 提供精确的小数位四舍五入处理 * * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */ public static String round(String v, int scale) { if (scale < 0) { throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b = new BigDecimal(v); return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); } /** * 取余数 * * @param v1 被除数 * @param v2 除数 * @param scale 小数点后保留几位 * @return 余数 */ public static String remainder(String v1, String v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); } /** * 取余数 BigDecimal * * @param v1 被除数 * @param v2 除数 * @param scale 小数点后保留几位 * @return 余数 */ public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP); } /** * 比较大小 * * @param v1 被比较数 * @param v2 比较数 * @return 如果v1 大于v2 则 返回true 否则false */ public static boolean compare(String v1, String v2) { BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); int bj = b1.compareTo(b2); boolean res; if (bj > 0) res = true; else res = false; return res; }
周星星 互联网从业人员 资深峡谷战士
本文内容为原创内容 首发公众号:码农周星星(微信号:mnzxx1989)
未经授权,禁止转载
java实现qq抢红包_微信抢红包到底是怎么抢到的?相关推荐
- 【安卓学习之微信抢红包】 微信抢红包 1 - 知识点归纳
█ [安卓学习之微信抢红包] 微信抢红包 1 - 知识点归纳 █ 相关文章: ● [安卓学习之微信抢红包] 微信抢红包 1 - 知识点归纳 ● [安卓学习之微信抢红包] 微信抢红包 2 - 通知服务( ...
- 【安卓学习之微信抢红包】 微信抢红包 5 - 工具Android Monitor
█ [安卓学习之微信抢红包] 微信抢红包 5 - 工具Android Device Monitor █ 相关文章: - ● [安卓学习之微信抢红包] 微信抢红包 1 - 知识点归纳 ● [安卓学习之微 ...
- 数据库并发抢红包_微信高并发抢红包秒杀实战案例
前言 群里有小伙伴咨询微信红包的架构,对于我来说,显然是不知道的,但是写一个相对高并发的抢红包案例还是完全可以的. 架构设计 业务流程老板发红包,此时缓存初始化红包个数,红包金额(单位分),并异步入库 ...
- java 仿qq空间_仿QQ空间和微信朋友圈,高解耦高复用高灵活
先看看效果: 用极少的代码实现了 动态详情 及 二级评论 的 数据获取与处理 和 UI显示与交互,并且高解耦.高复用.高灵活. 动态列表界面MomentListFragment支持 下拉刷新与上拉加载 ...
- 微信抢红包的方案_微信抢红包方法图文详解
微信抢红包不知道大家是否都熟悉,照顾点新人,很多朋友只是听说微信红包却不知道怎么抢?微信抢红包一夜爆红,受到众多朋友的喜爱,功能上可以实现发红包.查收发记录和提现,一起看看微信抢红包技巧详解吧! 一. ...
- java如何获取手机号码_微信小程序+Java获取用户授权手机号码
前言 小程序内可以直接通过授权获取用户微信号绑定的手机号码或用户添加的其他手机号码,这样可以使得小程序在进行账户的身份可控上又提高了一步,那么应该如何来获取手机号码呢?这篇文章就和大家一起来研究一下. ...
- 微信抢红包的方案_微信抢红包怎样才能抢到最大的告诉你一个方法
抢红包眼疾手快, 发红包精打细算, 可算起账来为什么还是亏?! 抢到大红包就是好事吗? 元旦节的时候,一个壕同学在群里发了一个大红包,刚洗完脚的我凭借一个唯美的睡姿抢到了最大的一个,50多块. 正当我 ...
- Redis案例实战_微信抢红包
目录 需求分析 架构设计 编码实现 拓展 需求分析 首先想到发红包的流程 1.发红包 2.抢红包 3.记录红包(记录谁抢了多少+防止重复抢+如果红包到齐没有抢完,需要退回) 4.红包算法,保证每个红包 ...
- 小程序向java后台发送图片_微信小程序在后台如何将二进制流转换成图片
我在前端请求了小程序码返回的是一堆乱码, java不太熟网上找了一个方法可以将二进制流和图片互转,但是从微信小程序码接口获取的数据用这个方法无法获取正确的图片,不知道哪里有问题: 有没有只在前端就能获 ...
最新文章
- 体育与科技丨清华之友体育产业主题论坛成功举行
- Python的逻辑判断和循环 || 打印九九乘法表
- 【机器学习算法-python实现】扫黄神器-朴素贝叶斯分类器的实现
- node mysql limit_node中mysql连接池的connectionLimit指什么,它和mysql的最小连接数和最大连接数的关系是什么?...
- Redis windows学习(二)——Redis持久化的AOF模式和RDB模式
- c++椭圆最小二乘法原理_c++ 椭圆拟合之最小二乘法(图像处理)
- 粒子群优化算法(2)离散粒子群算法
- SAP License:COPA计划
- perl DBI高级编程
- java中两个源文件_两个java源文件同属一个包,其中一个类引用了另一个,在用javac编译时,报错找不到引用的类,import无作用...
- 中国1km分辨率的DEM数据以及合并后的中国行政区划数据
- EPLAN入门学习笔记(一)——项目创建与基本使用方法
- axure如何竖立文字_Axure pr 如何让文字显示为竖着的?
- 如何评价一个好系统?
- sas中数据输入输出格式
- 胸部CT影像分析(笔记)
- cache在计算机中代表什么,cache是什么意思,计算机中cache是什么意思
- 洛谷 1016 [NOIP1999] 旅行家的预算 贪心
- html分享标题描述图片尺寸,微信分享网页时自定义标题、描述和图片
- TortoiseGit 连接 git服务器免输入用户名和密码的方法
热门文章
- IntelliJ IDEA 2018.1正式发布!什么?还能这么玩?
- hbuilderx代码自动补全_DL时代的代码补全利器,北大出品,效果远超语言模型
- 启停系统错误_关掉自动启停系统,就可以用普通蓄电池代替启停蓄电池吗?
- C#机房重构-总结(二)
- caffe转caffe2
- skimage 学习笔记
- 终止代码driver_irql_not_less_or_equal
- Invalid packet stream index
- Ubuntu创建新用户并增加管理员权限
- python数组中变化最大的值