最新要闻
- 世界速讯:电影《龙马精神》上映!刘德华清唱生日快乐歌祝福成龙
- 全球微动态丨连休5天!五一劳动节期间高速公路免费通行
- 口感自然 富含矿物质:依能天然苏打水2.3元(京东4元)
- 每日热议!博主带秤在淄博连逛10家店无缺斤少两:他很佩服
- 天天速递!投资者为何对苹果AR/VR设备没有信心?原因揭开
- 常州市天宁区人民检察院对一起拟不起诉案件公开听证
- 【天天报资讯】装机成本降了! 微星A620M-E主板到手799元:锐龙7 7800X3D绝配
- 关注:国宝熊猫回家记:丫丫要回来了
- 四川一麦田成网红打卡地:遭游客踩踏
- 4799元 AOC新款27英寸电竞显示器上架:360Hz高刷、1ms响应
- 环球热议:没用的知识增加了!一图了解劳斯莱斯全部车型:最贵1.8亿
- 【天天速看料】王者荣耀镜取什么名字(王者镜的名字好听)
- 贵了2块钱 海底捞回应火锅小料涨价:门店自主定价
- 天天短讯!撤档一年半后 《超能一家人》定档7月21日:沈腾爆笑喜剧大片
- 时讯:科大讯飞刘聪:中国造大模型或在某些领域超越ChatGPT
- 今热点:郑州这些户籍室“周末无休”,周六日也能办理业务→
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
世界热点!【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(下)
承接上文
承接上一篇文章【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(上)】我们基本上对层级时间轮算法的基本原理有了一定的认识,本章节就从落地的角度进行分析和介绍如何通过Java进行实现一个属于我们自己的时间轮服务组件,最后,在告诉大家一下,其实时间轮的技术是来源于生活中的时钟。
时间轮演示结构总览
无序列表时间轮
【无序列表时间轮】主要是由LinkedList链表和启动线程、终止线程实现。
【资料图】
遍历定时器中所有节点,将剩余时间为 0s 的任务进行过期处理,在执行一个周期。
- 无序链表:每一个延时任务都存储在该链表当中(无序存储)。
- 启动线程: 直接在链表后面push ,时间复杂度 O(1)。
- 终止线程: 直接在链表中删除节点,时间复杂度 O(1) 。
遍历周期:需要遍历链表中所有节点,时间复杂度 O(n),所以伴随着链表中的元素越来越多,速度也会越来越慢!
无序列表时间轮的长度限制了其适用场景,这里对此进行优化。因此引入了有序列表时间轮。
有序列表时间轮
与无序列表时间轮一样,同样使用链表进行实现和设计,但存储的是绝对延时时间点。
- 启动线程:有序插入,比较时间按照时间大小有序插入,时间复杂度O(n),主要耗时在插入操作。
- 终止线程:链表中查找任务,删除节点,时间复杂度O(n),主要耗时在插入操作。
找到执行最后一个过期任务即可,无需遍历整个链表,时间复杂度 O(1),从上面的描述「有序列表定时器」的性能瓶颈在于插入时的任务排序,但是换来的就是缩短了遍历周期。
所以我们如果要提高性,就必须要提升一下插入和删除以及检索的性能,因此引入了「树形有序列表时间轮」在「有序列表定时器」的基础上进行优化,以有序树的形式进行任务存储。
树形有序列表时间轮
- 启动定时器: 有序插入,比较时间按照时间大小有序插入,时间复杂度 O(logn)
- 终止定时器: 在链表中查找任务,删除节点,时间复杂度 O(logn)
- 周期清算: 找到执行最后一个过期任务即可,无需遍历整个链表,时间复杂度 O(1)
层级时间轮
整体流程架构图,如下所示。
对应的原理,在这里就不进行赘述了,之前本人已经有两篇文章对层级式时间轮进行了较为详细的介绍了,有需要的小伙伴,可以直接去前几篇文章去学习,接下来我们进行相关的实现。
时间轮数据模型
时间轮(TimingWheel)是一个存储定时任务的环形队列,数组中的每个元素可以存放一个定时任务列表,其中存放了真正的定时任务,如下图所示。
时间轮的最基本逻辑模型,由多个时间格组成,每个时间格代表当前时间轮的基本时间跨度(tickMs),所以我们先来设计和定义开发对应的时间轮的轮盘模型。命名为Roulette类。
轮盘抽象类-Roulette
之所以定义这个抽象类
public abstract class Roulette {// 链表数据-主要用于存储每个延时任务节点 List tasks = null; // 游标指针索引 protected int index;// 时间轮轮盘的容量大小,如果是分钟级别,一般就是60个格 protected int capacity;// 时间轮轮盘的层级,如果是一级,它的上级就是二级 protected Integer level; private AtomicInteger num = new AtomicInteger(0); // 构造器 public Roulette(int capacity, Integer level) { this.capacity = capacity; this.level = level; this.tasks = new ArrayList<>(capacity); this.index = 0; } // 获取当前下表的索引对应的时间轮的任务 public TimewheelTask getTask() { return tasks.get(index); } // init初始化操作机制 public List init() { long interval = MathTool.power((capacity + 1), level); long add = 0; TimewheelTask delayTask = null; for (int i = 0; i < capacity; i++) { add += interval; if (level == 0) { delayTask = new DefaultDelayTask(level); } else { delayTask = new SplitDelayTask(level); } //已经转换为最小的时间间隔 delayTask.setDelay(add, TimeUnitProvider.getTimeUnit()); tasks.add(delayTask); } return tasks; } // 索引下标移动 public void indexAdd() { this.index++; if (this.index >= capacity) { this.index = 0; } } // 添加对应的任务到对应的队列里面 public void addTask(TimewheelTask task) { tasks.add(task); }// 给子类提供的方法进行实现对应的任务添加功能 public abstract void addTask(int interval, MyTask task);}
时间轮盘的熟悉信息介绍
链表数据-主要用于存储每个延时任务节点。
List tasks = null;
tasks也可以改成双向链表+ 数组的结构:即节点存贮的对象中有指针,组成环形,可以通过数组的下标灵活访问每个节点,类似 LinkedHashMap。
游标指针索引
protected int index;
时间轮轮盘的容量大小,如果是分钟级别,一般就是60个格
protected int capacity;
时间轮轮盘的层级,如果是一级,它的上级就是二级
protected Integer level;
init初始化时间轮轮盘对象模型,主要用于分配分配每一个轮盘上面元素的TimewheelTask,用于延时队列的执行任务线程,已经分配对应的每一个节点的延时时间节点数据。
public List init() { // 那么整个时间轮的总体时间跨度(interval) long interval = MathTool.power((capacity + 1), level); long add = 0; TimewheelTask delayTask = null; for (int i = 0; i < capacity; i++) { add += interval; if (level == 0) { delayTask = new ExecuteTimewheelTask(level); } else { delayTask = new MoveTimewheelTask(level); } //已经转换为最小的时间间隔 delayTask.setDelay(add, TimeUnitProvider.getTimeUnit()); tasks.add(delayTask); } return tasks;}
- 整数a的n次幂:interval,计算跨度,主要是各级别之间属于平方倍数
例如,第一层:20 ,第二层:20^2 ......
//例如 n=7 二进制 0 1 1 1 //a的n次幂 = a的2次幂×a的2次幂 × a的1次幂×a的1次幂 ×a public static long power(long a, int n) { int rtn = 1; while (n >= 1) { if((n & 1) == 1){ rtn *= a; } a *= a; n = n >> 1; } return rtn; }
TimeUnitProvider工具类
主要用于计算时间单位操作的转换
public class TimeUnitProvider { private static TimeUnit unit = TimeUnit.SECONDS; public static TimeUnit getTimeUnit() { return unit; }}
代码简介:
- interval:代表着初始化的延时时间数据值,主要用于不同的层次的出发时间数据
- for (int i = 0; i < capacity; i++) :代表着进行for循环进行添加对应的延时队列任务到集合中
- add += interval,主要用于添加对应的延时队列的延时数据值!并且分配给当前轮盘得到所有数据节点。
获取当前下标的索引对应的时间轮的任务节点
public TimewheelTask getTask() { return tasks.get(index);}
层级时间轮的Bucket数据桶
在这里我们建立了一个TimewheelBucket类实现了Roulette轮盘模型,从而进行建立对应的我们的层级时间轮的数据模型,并且覆盖了addTask方法。
public class TimewheelBucket extends Roulette { public TimewheelBucket(int capacity, Integer level) { super(capacity, level); } public synchronized void addTask(int interval, MyTask task) { interval -= 1; int curIndex = interval + this.index; if (curIndex >= capacity) { curIndex = curIndex - capacity; } tasks.get(curIndex).addTask(task); }}
添加addTask方法,进行获取计算对应的下标,并且此方法add操作才是对外开发调用的,在这里,我们主要实现了根据层级计算出对应的下标进行获取对应的任务执行调度点,将我们外界BizTask,真正的业务操作封装到这个BizTask模型,交由我们的系统框架进行执行。
public synchronized void addTask(int interval, BizTask task) { interval -= 1; int curIndex = interval + this.index; if (curIndex >= capacity) { curIndex = curIndex - capacity; } tasks.get(curIndex).addTask(task); }
时间轮轮盘上的任务点
我们针对于时间轮轮盘的任务点进行设计和定义对应的调度执行任务模型。一个调度任务点,可以帮到关系到多个BizTask,也就是用户提交上来的业务任务线程对象,为了方便采用延时队列的延时处理模式,再次实现了Delayed这个接口,对应的实现代码如下所示:
Delayed接口
public interface Delayed extends Comparable { /** * Returns the remaining delay associated with this object, in the * given time unit. * * @param unit the time unit * @return the remaining delay; zero or negative values indicate * that the delay has already elapsed */ long getDelay(TimeUnit unit);}
TimewheelTask时间轮刻度点
@Getterpublic abstract class TimewheelTask implements Delayed { private List tasks = new ArrayList(); private int level; private Long delay; private long calDelay; private TimeUnit calUnit; public TimewheelTask(int level) { this.level = level; } public void setDelay(Long delay, TimeUnit unit) { this.calDelay=delay; this.calUnit=unit; } public void calDelay() { this.delay = TimeUnit.NANOSECONDS.convert(this.calDelay, this.calUnit) + System.nanoTime(); } public long getDelay(TimeUnit unit) { return this.delay - System.nanoTime(); } public int compareTo(Delayed o) { long d = (getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS)); return (d == 0) ? 0 : ((d < 0) ? -1 : 1); } public void addTask(BizTask task) { synchronized (this) { tasks.add(task); } } public void clear() { tasks.clear(); } public abstract void run();}
业务任务集合:
private List
tasks = new ArrayList (); - 层级
private int level;
- 延时时间
private Long delay;
- 实际用于延时计算的时间,就是底层是统一化所有的延时时间到对应的延时队列
private long calDelay;
- 实际用于延时计算的时间,就是底层是统一化所有的延时时间到对应的延时队列(用于统一化的时间单位)
private TimeUnit calUnit;
添加对应的业务延时任务到轮盘刻度点
public void addTask(BizTask task) { synchronized (this) { tasks.add(task); } }
刻度点的实现类
因为对应的任务可能会需要将下游的业务任务进行升级或者降级,所以我们会针对于执行任务点分为,执行任务刻度点和跃迁任务刻度点两种类型。
- 执行任务延时队列刻度点
public class ExecuteTimewheelTask extends TimewheelTask { public ExecuteTimewheelTask(int level) { super(level); } //到时间执行所有的任务 public void run() { List tasks = getTasks(); if (CollectionUtils.isNotEmpty(tasks)) { tasks.forEach(task -> ThreadPool.submit(task)); } }}
再次我们就定义执行这些任务的线程池为:
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(20, 100, 3, TimeUnit.MINUTES, new LinkedBlockingQueue(10000), new MyThreadFactory("executor"), new ThreadPoolExecutor.CallerRunsPolicy());
- 跃迁任务延时队列刻度点
public class MoveTimewheelTask extends TimewheelTask { public MoveTimewheelTask(int level) { super(level); } //跃迁到其他轮盘,将对应的任务 public void run() { List tasks = getTasks(); if (CollectionUtils.isNotEmpty(tasks)) { tasks.forEach(task -> { long delay = task.getDelay(); TimerWheel.adddTask(task,delay, TimeUnitProvider.getTimeUnit()); }); } }}
致辞整个时间轮轮盘的数据模型就定义的差不多了,接下来我们需要定义运行在时间轮盘上面的任务模型,BizTask基础模型。
BizTask基础模型
public abstract class BizTask implements Runnable { protected long interval; protected int index; protected long executeTime; public BizTask(long interval, TimeUnit unit, int index) { this.interval = interval; this.index = index; this.executeTime= TimeUnitProvider.getTimeUnit().convert(interval,unit)+TimeUnitProvider.getTimeUnit().convert(System.nanoTime(),TimeUnit.NANOSECONDS); } public long getDelay() { return this.executeTime - TimeUnitProvider.getTimeUnit().convert(System.nanoTime(), TimeUnit.NANOSECONDS); }}
主要针对于任务执行,需要交给线程池去执行,故此,实现了Runnable接口。
- protected long interval;:跨度操作
- protected int index;:索引下表,在整个队列里面的下表处理
- protected long executeTime;:对应的执行时间
其中最重要的便是获取延时时间的操作,主要提供给框架的Delayed接口进行判断是否到执行时间了。
public long getDelay() { return this.executeTime - TimeUnitProvider.getTimeUnit().convert(System.nanoTime(), TimeUnit.NANOSECONDS); }
层级时间轮的门面TimerWheel
最后我们要进行定义和设计开发对应的整体的时间轮层级模型。
public class TimerWheel { private static Map cache = new ConcurrentHashMap<>(); //一个轮表示三十秒 private static int interval = 30; private static wheelThread wheelThread; public static void adddTask(BizTask task, Long time, TimeUnit unit) { if(task == null){ return; } long intervalTime = TimeUnitProvider.getTimeUnit().convert(time, unit); if(intervalTime < 1){ ThreadPool.submit(task); return; } Integer[] wheel = getWheel(intervalTime,interval); TimewheelBucket taskList = cache.get(wheel[0]); if (taskList != null) { taskList.addTask(wheel[1], task); } else { synchronized (cache) { if (cache.get(wheel[0]) == null) { taskList = new TimewheelBucket(interval-1, wheel[0]); wheelThread.add(taskList.init()); cache.putIfAbsent(wheel[0],taskList); } } taskList.addTask(wheel[1], task); } } static{ interval = 30; wheelThread = new wheelThread(); wheelThread.setDaemon(false); wheelThread.start(); } private static Integer[] getWheel(long intervalTime,long baseInterval) { //转换后的延时时间 if (intervalTime < baseInterval) { return new Integer[]{0, Integer.valueOf(String.valueOf((intervalTime % 30)))}; } else { return getWheel(intervalTime,baseInterval,baseInterval, 1); } } private static Integer[] getWheel(long intervalTime,long baseInterval,long interval, int p) { long nextInterval = baseInterval * interval; if (intervalTime < nextInterval) { return new Integer[]{p, Integer.valueOf(String.valueOf(intervalTime / interval))}; } else { return getWheel(intervalTime,baseInterval,nextInterval, (p+1)); } } static class wheelThread extends Thread { DelayQueue queue = new DelayQueue(); public DelayQueue getQueue() { return queue; } public void add(List tasks) { if (CollectionUtils.isNotEmpty(tasks)) { tasks.forEach(task -> add(task)); } } public void add(TimewheelTask task) { task.calDelay(); queue.add(task); } @Override public void run() { while (true) { try { TimewheelTask task = queue.take(); int p = task.getLevel(); long nextInterval = MathTool.power(interval, Integer.valueOf(String.valueOf(MathTool.power(2, p)))); TimewheelBucket timewheelBucket = cache.get(p); synchronized (timewheelBucket) { timewheelBucket.indexAdd(); task.run(); task.clear(); } task.setDelay(nextInterval, TimeUnitProvider.getTimeUnit()); task.calDelay(); queue.add(task); } catch (InterruptedException e) { } } } }}
TimerWheel的模型定义
private static Map cache = new ConcurrentHashMap<>();
一个轮表示30秒的整体跨度。
private static int interval = 30;
创建整体驱动的执行线程
private static wheelThread wheelThread; static{ interval = 30; wheelThread = new wheelThread(); wheelThread.setDaemon(false); wheelThread.start();} static class wheelThread extends Thread { DelayQueue queue = new DelayQueue(); public DelayQueue getQueue() { return queue; } public void add(List tasks) { if (CollectionUtils.isNotEmpty(tasks)) { tasks.forEach(task -> add(task)); } } public void add(TimewheelTask task) { task.calDelay(); queue.add(task); } @Override public void run() { while (true) { try { TimewheelTask task = queue.take(); int p = task.getLevel(); long nextInterval = MathTool.power(interval, Integer.valueOf(String.valueOf(MathTool.power(2, p)))); TimewheelBucket timewheelBucket = cache.get(p); synchronized (timewheelBucket) { timewheelBucket.indexAdd(); task.run(); task.clear(); } task.setDelay(nextInterval, TimeUnitProvider.getTimeUnit()); task.calDelay(); queue.add(task); } catch (InterruptedException e) { } } }
获取对应的时间轮轮盘模型体系
private static Integer[] getWheel(long intervalTime,long baseInterval) { //转换后的延时时间 if (intervalTime < baseInterval) { return new Integer[]{0, Integer.valueOf(String.valueOf((intervalTime % 30)))}; } else { return getWheel(intervalTime,baseInterval,baseInterval, 1); } } private static Integer[] getWheel(long intervalTime,long baseInterval,long interval, int p) { long nextInterval = baseInterval * interval; if (intervalTime < nextInterval) { return new Integer[]{p, Integer.valueOf(String.valueOf(intervalTime / interval))}; } else { return getWheel(intervalTime,baseInterval,nextInterval, (p+1)); } }
到这里相信大家,基本上应该是了解了如何去实现对应的时间轮盘的技术实现过程,有兴趣希望整个完整源码的,可以联系我哦。谢谢大家!
关键词:
-
世界热点!【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(下)
承接上文承接上一篇文章【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWhe...
来源: 世界热点!【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(下)
世界速讯:电影《龙马精神》上映!刘德华清唱生日快乐歌祝福成龙
全球微动态丨连休5天!五一劳动节期间高速公路免费通行
口感自然 富含矿物质:依能天然苏打水2.3元(京东4元)
每日热议!博主带秤在淄博连逛10家店无缺斤少两:他很佩服
天天速递!投资者为何对苹果AR/VR设备没有信心?原因揭开
常州市天宁区人民检察院对一起拟不起诉案件公开听证
java -- 异常处理、Collection、Iterator迭代器、泛型
CentOS7-实现全网备份脚本
【天天报资讯】装机成本降了! 微星A620M-E主板到手799元:锐龙7 7800X3D绝配
关注:国宝熊猫回家记:丫丫要回来了
四川一麦田成网红打卡地:遭游客踩踏
windows提权
4799元 AOC新款27英寸电竞显示器上架:360Hz高刷、1ms响应
环球热议:没用的知识增加了!一图了解劳斯莱斯全部车型:最贵1.8亿
【天天速看料】王者荣耀镜取什么名字(王者镜的名字好听)
贵了2块钱 海底捞回应火锅小料涨价:门店自主定价
天天短讯!撤档一年半后 《超能一家人》定档7月21日:沈腾爆笑喜剧大片
时讯:科大讯飞刘聪:中国造大模型或在某些领域超越ChatGPT
世界视讯!pWnOS2
当前热文:python中shutil和shutil库的用法
Spring源码阅读系列--全局目录
今热点:郑州这些户籍室“周末无休”,周六日也能办理业务→
高校凌晨发录取通知要求半小时回复引热议 专家:不合理 好比半夜鸡叫
一口鲜气!春光0糖椰汁狂促:优惠30元到手仅需19.9
热资讯!定制纪念礼品瓷盘
MQ——消息积压如何处理
全球观点:Python 元编程
C++ 并发编程实战 第二章 线程管控
全球微速讯:gazebo小车模型(附带仿真环境)
今日快讯:哪吒纯电GT跑车遭贬低 CEO回应:就喜欢看不惯又干不掉我的样子
快资讯丨歪风盛行:日本将严惩暗拍空姐行为 违者将判处5年以下监禁或26万
999元起 中兴远航40上架:紫光展锐T760、10W快充
今日要闻!花枝招展
世界头条:浙江宁波空中突发巨响 官方回应:超高速飞行产生的音爆
当前观察:中兴Axon Pad官宣:四等宽窄边 支持一触互联
前沿资讯!百度:目前文心一言无官方App!已起诉苹果公司
世界百事通!海南航空恢复伦敦至长沙直航航线
啥事不干年薪130万:科技公司为何愿意“养闲人”?
今日最新!马斯克回应特斯拉降价:很多人有需求但买不起 只有降价才能满足需求
环球观速讯丨男子路边违停被查!司机竟掏出“联合国机动车驾驶证”
全球即时看!“云溪学子看云溪 云腾溪涌产业新”云溪区举行首届工业研学活动
环球观热点:《系列一》-- 4、xml配置文件解析之[默认]命名空间[标签]的解析
天天热推荐:面试题百日百刷-HBase中HTable API有没有线程安全问题,在程序是单例还是多例?
IPX6级防水防刮:宏碁墨尔本背包169元抄底(三种款式)
焦点快播:博主发薅高铁商务座羊毛攻略 20元享超VIP服务网友效仿:12306回应怒赞
环球快消息!平台回应机票一分钟三次变价:实时价格变动正常
英雄之光|陈旧的记录本 是他牵挂群众最温暖的见证
世界即时看!你的工资不能低于这数!31省份最低工资公布:时薪达标没
世界最资讯丨山东推出399元高铁环游套票:有效期5天、全省免费换乘
每日热议!DIY万能钥匙!主板检测灯你不得不懂
【焦点热闻】09款奥迪A4L标准型改装泪眼/导航作业
最新资讯:Docker-compose 到 Kubernetes 的迁移工具!
世界速看:全新深红配色亮相!iPhone 15 Pro超高清外观渲染图首曝:钛金属边框、Type-C接口
世界资讯:诈骗网红梅尼耶的MCN游良文化被申请破产:小刚学长等多位网红受骗
焦点速读:Java性能权威指南(第2版)读后总结与感想
动态:大学招聘体育老师 要求得过奥运冠军引热议:官方回应专业技能很重要
复星旅文2022年收入137.78亿元 徐晓亮:要尽快追回过去三年失去的业绩
【天天报资讯】K8S圣经12:SpringCloud+Jenkins+ K8s Ingress 自动化灰度发布
回顾、信号、flask-script、sqlalchemy介绍和快速使用、创建操作数据表
今日快看!ASP.NET Core MVC 从入门到精通之初窥门径
女主不够性感吗?《生化危机4重制版》发售两周狂卖超400万份:Steam好评如潮
Intel中国特供i5-13490F/i7-13790F闪电降价:性价比更神了!
天天看热讯:男人多次失恋后和娃娃订婚 还“生”了三个孩子
当年找抖音赔30亿的腾讯视频 这会儿怎么来世纪大和解了?
电视接口盘点 HDMI 2.1真的是刚需吗?
天天观点:罪恶都市任务攻略_侠盗飞车罪恶都市任务做完了怎么办
环球短讯!HRB500钢筋符号_HRB500的钢筋符号是什么 属于几级钢筋
热头条丨【调试】ftrace(三)trace-cmd和kernelshark
全球观天下!集成Unity3D到iOS应用程序中
环球视点!OpenCV获取相机旋转矩阵和平移矩阵
海信电视精简系统
【全球聚看点】试验设计课程作业
比亚迪海豹、长安深蓝SL03获央视第三届《中国汽车风云盛典》评委会大奖
诈骗网红梅尼耶的MCN被申请破产引热议 多位女网红等都受害:网友吐槽行业乱
世界即时看!女子开宝马占用商场特斯拉专用车位 被特斯拉怒堵2天
世界速递!2023.4.7【模板】快速沃尔什变换FWT
今日报丨面试题百日百刷-HBase HRegionServer宕机如何处理
第135篇:Three.js基础入门
每日消息!交易商协会评估更新定向债务融资工具专项机构投资人名单
美国3月非农就业人口增幅降至23.6万 失业率为3.5%
热文:构造柱的作用是什么_构造柱的作用
焦点热讯:网友称余额宝页面显示乱码 支付宝回应:正在修复 不影响资金安全
《龙马精神》电影中真打实摔 成龙:我69岁动作比你还快
热点!说十个需要送老婆礼物的节日
焦点观察:火热出炉 秘汁全鸡的数字新“味”
大阳睿能全新动力首款车型H12下线:电机1700W 续航150公里
249元 小米米家自动真空封口机发布:-70KPa大吸力
全球快资讯丨世界各地小孩的玩具对比:不止文化与财富的差距
当前动态:马斯克开源推特算法反被指责:隐藏重要细节、与承诺不符
【当前独家】《王者荣耀》S31赛季4月13日上线 新英雄姬小满来了
深圳开放大学优秀学生李德炎:保持学习状态,争做行业模范
天天快看点丨自动旋转ROS小车(rviz+urdf+xacro)(附加python操作键盘控制小车运动)
每日资讯:java -- Math、BigInteger、BigDecimal类和基本类型的包装类、正则表达式
【快播报】黑田东彦“卸任”言论释放宽松信号 日债收益率曲线平坦化下移
速递!定价全球最低!国产科幻FPS《边境》国区售价68元起
天天观热点:孟羽童已不是董明珠秘书引热议 本人回应:很享受格力市场营销工作
今日看点:米粉换上Redmi Note 12 Turbo:陪伴他5年的小米6正式退役
天天即时看!网友看电影觉得难看成功退一半费用 影城:散场20分钟内可办理
电动自行车调速器网上公开售卖!专家:私改限速或引发燃爆事故