全局唯一id生成器 各种实现记录

全局唯一id生成器

Redis 生成

前提知识~~ 时间戳

时间戳这个东西我们老是听到,却可能不是特别了解
首先,时间戳是从1970年1月1号0点0分开始的秒数,我查了蛮多资料,理论上来说,时间戳是没有上限的,而我们一般用位数来限制这里的上限,比如32位

我们来实际计算一下
32位的二进制, 2的32次方 - 1 = 4294967296 - 1 = 4294967295
因为时间戳表示的是秒数,所以这里就是32位下,最大的秒数

一天的秒数为 86400
365天的秒数为31536000

那么32位的时间戳是 4294967295 / 31536000 = 136

像现在是2024年,已经过了54年了,那么还有82年就要过期了

搞清楚这里的计算,我们后面就不会突然觉得,诶这里会不会超出上限

如何实现Redis全局id

首先我们要搞清楚为什么要全局id,全局id的作用是什么

第一: 唯一性,我们不能一套系统很多种全局id的生成器把,不能都用mysql自动生成id把,那样会混论,尤其是分布式系统
第二: 安全性,为了不让黑客知道我们生成id的规律,我们要加点佐料进去,例如时间戳
第三: 高可用 + 高性能 + 递增性 高可用就是,一个单点故障了,另外的一个服务器可以顶上,高性能就是生成的快,递增性就是为了我们业务的正常递增

所以我们就有redis生成全局id
这上面都符合,特别是高可用,可以用redis集群来保证,但是安全性,就要用不同的方法来实现了

这里是一种设计方法

设计的详解

在这里插入图片描述
时间戳31位,序列号32位

这里的全局id的意思就是,每一秒内的序列号作为全局id

这里的设计就很不错,这样很大程度上解决了问题,你可能会想要是1s内,超出了2的32次方怎么办,好办,就多写几位,压缩时间戳的位数

我们再来讲讲这里的时间戳的上限,如果是31位的化,那么最大就是2的31次方- 1 = 2147483648 - 1 = 2147483647
一年的秒数(365天) = 2147483647

2147483647 / 31536000 = 68 年 约等于68年, 现在是2024年 离1970年已经54年了,所以按道理来说14年后就过期了 也就是2038年

这里的序列号,就用redis的自增来实现

实际代码

/*** 全局唯一id生成器 Redis实现* @author jjking* @date 2024-02-07 20:27*/
@Component
public class RedisIdWorker {@Autowiredprivate RedisTemplate redisTemplate;/*** 生成id* @param keyPrefix 业务的前缀key* @return*/public long nextId(String keyPrefix) {//生成时间戳LocalDateTime now = LocalDateTime.now();long timestamp = now.toEpochSecond(ZoneOffset.UTC);//生成序列号String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));long count = redisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);return timestamp << 32 | count;}
}

这里比较有意思的点是两个点
第一: 是这里的redis生成序列号的点,特别要加入业务代码的前缀,不然全都用一个不就乱了套了,还有就是redis的value是有位数上限的,好像是2的64次方,所以这里还是会超出上限的,那么为了解决这个问题,就用了这里的时间来做区别,这样基本就不会有问题了

第二: ,这里的返回结果的计算也蛮有意思,首先是时间的位数向左边移动了32位,这里的意思就是腾出32位给序列号,然后再用位运算 或,来加上这里的序列号

特别要注意这里的或,很有意思,0 | 0 还是0 0 | 1 那么就是1,所以这里可以直接加上,这个得想一想才能想明白

UUID生成

UUID就比较耳熟能祥了,我这里写一个生成的范例

@Test
public void test1() {String uuid = UUID.randomUUID().toString();System.out.println(uuid);
}

在这里插入图片描述
可以看出来,他的位数分布是8-4-4-4-12位,一共是32位16进制数

我们来计算一下,总共多少字节,我们先转为为二进制,一位16进制,是4位二进制,那么 总共有32 * 4 = 128位二进制 一个字节是8位二进制

128 / 8 = 16字节

我们上面的redis生成的id是64位的,他的一半8个字节

所以,他的第一个缺点就是太大了,占内存

而且,这个uuid,也不太安全

但是他的优点就是性能还算蛮高的,还没有网络消耗

雪花算法 (重中之重)

先来了解雪花算法生成的id组成

  • 最高位 固定为 0 ,符号位,因为生成的id都为正数,固定为0
  • 41位 时间戳 单位 毫秒 经过计算最多可以使用69年
  • 10 位机器码 = 5位 数据中心id + 5位 工作机器id
  • 12 位序列号

这个样子有点类似于我们redis生成的id,不过序列号少了,并且是毫秒级的,还有一个机器码

我这里摘的是糊涂工具包中的雪花算法id,并且简略了一些无关辅助代码

代码

package com.hmdp.utils;import cn.hutool.core.date.SystemClock;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;import java.io.Serializable;
import java.util.Date;/*** Twitter的Snowflake 算法<br>* 分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。** <p>* snowflake的结构如下(每部分用-分开):<br>** <pre>* 符号位(1bit)- 时间戳相对值(41bit)- 数据中心标志(5bit)- 机器标志(5bit)- 递增序号(12bit)* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000* </pre>* <p>* 第一位为未使用(符号位表示正数),接下来的41位为毫秒级时间(41位的长度可以使用69年)<br>* 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)<br>* 最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)* <p>* 并且可以通过生成的id反推出生成时间,datacenterId和workerId* <p>* 参考:http://www.cnblogs.com/relucent/p/4955340.html<br>* 关于长度是18还是19的问题见:https://blog.csdn.net/unifirst/article/details/80408050** @author Looly* @since 3.0.1*/
public class Snowflake implements Serializable {private static final long serialVersionUID = 1L;/*** 默认的起始时间,为Thu, 04 Nov 2010 01:42:54 GMT*/public static long DEFAULT_TWEPOCH = 1288834974657L;/*** 默认回拨时间,2S*/public static long DEFAULT_TIME_OFFSET = 2000L;private static final long WORKER_ID_BITS = 5L;// 最大支持机器节点数0~31,一共32个@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})//-1L 为 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 (1L的补码)//左移5为  1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1110 0000//-1L为   1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111// ^ 异或是不同为1,相同为0// 结果为  0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1111private static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);private static final long DATA_CENTER_ID_BITS = 5L;// 最大支持数据中心节点数0~31,一共32个@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})//和上面的最大工作id一样的道理private static final long MAX_DATA_CENTER_ID = -1L ^ (-1L << DATA_CENTER_ID_BITS);// 序列号12位(表示只允许workId的范围为:0-4095)private static final long SEQUENCE_BITS = 12L;// 机器节点左移12位private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;// 数据中心节点左移17位private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;// 时间毫秒数左移22位private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;// 序列掩码,用于限定序列最大值不能超过4095//计算机的负数是用补码表示的//1L     0000000000000000000000000000000000000000000000000000000000000001//1L 反码 1111111111111111111111111111111111111111111111111111111111111110//1L 补码 1111111111111111111111111111111111111111111111111111111111111111      补码 = 反码 + 1//这里是 -1L(64位1) 往左移动12位 111111111111111111111111111111111111111111111111 0000 0000 0000 0000// ~ 取反 					  000000000000000000000000000000000000000000000000 1111 1111 1111 1111//结果为                       2的12次方 - 1 = 4095@SuppressWarnings("FieldCanBeLocal")private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);// 4095//起始时间private final long twepoch;private final long workerId;private final long dataCenterId;private final boolean useSystemClock;// 允许的时钟回拨数private final long timeOffset;private long sequence = 0L;private long lastTimestamp = -1L;/*** @param epochDate        初始化时间起点(null表示默认起始日期),后期修改会导致id重复,如果要修改连workerId dataCenterId,慎用* @param workerId         工作机器节点id* @param dataCenterId     数据中心id* @param isUseSystemClock 是否使用{@link SystemClock} 获取当前时间戳* @param timeOffset 允许时间回拨的毫秒数* @since 5.7.3*/public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset) {//如果没有给起始的时间就用默认的起始时间if (null != epochDate) {this.twepoch = epochDate.getTime();} else{// Thu, 04 Nov 2010 01:42:54 GMTthis.twepoch = DEFAULT_TWEPOCH;}//工作机器id <= 31if (workerId > MAX_WORKER_ID || workerId < 0) {throw new IllegalArgumentException(StrUtil.format("worker Id can't be greater than {} or less than 0", MAX_WORKER_ID));}if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {throw new IllegalArgumentException(StrUtil.format("datacenter Id can't be greater than {} or less than 0", MAX_DATA_CENTER_ID));}this.workerId = workerId;this.dataCenterId = dataCenterId;this.useSystemClock = isUseSystemClock;this.timeOffset = timeOffset;}/*** 根据Snowflake的ID,获取机器id** @param id snowflake算法生成的id* @return 所属机器的id*/public long getWorkerId(long id) {return id >> WORKER_ID_SHIFT & ~(-1L << WORKER_ID_BITS);}/*** 根据Snowflake的ID,获取数据中心id** @param id snowflake算法生成的id* @return 所属数据中心*/public long getDataCenterId(long id) {return id >> DATA_CENTER_ID_SHIFT & ~(-1L << DATA_CENTER_ID_BITS);}/*** 根据Snowflake的ID,获取生成时间** @param id snowflake算法生成的id* @return 生成的时间*/public long getGenerateDateTime(long id) {return (id >> TIMESTAMP_LEFT_SHIFT & ~(-1L << 41L)) + twepoch;}/*** 下一个ID** @return ID*/public synchronized long nextId() {//获取当前时间戳long timestamp = genTime();//如果小于上次的时间,这里有问题,时间回拨!if (timestamp < this.lastTimestamp) {if(this.lastTimestamp - timestamp < timeOffset){// 容忍指定的回拨,避免NTP校时造成的异常timestamp = lastTimestamp;} else{// 如果服务器时间有问题(时钟后退) 报错。throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));}}//如果等于上次的时间,说明,此时是同一毫秒,递增序列号if (timestamp == this.lastTimestamp) {//SEQUENCE_MASK为4095,这里的运算看上面的解释,这个相当于最大值//SEQUENCE_MASK 为                             00000000000000000000000000000000000000000000 0000 1111 1111 1111 1111//假设此时的序列号为4095(sequence) 那么前面是4096   00000000000000000000000000000000000000000000 0001 0000 0000 0000 0000//这样子与,0 & 0 = 0 ----- 0 & 1 = 0 ----- 1 & 1 = 1//所以最后结果为 								   00000000000000000000000000000000000000000000 0000 0000 0000 0000 0000final long sequence = (this.sequence + 1) & SEQUENCE_MASK;//如果此时为0说明,已经到了4095了,到达上限,应该等待下一个毫秒if (sequence == 0) {timestamp = tilNextMillis(lastTimestamp);}this.sequence = sequence;} else {sequence = 0L;}//赋值此时的上一次时间戳(毫秒)lastTimestamp = timestamp;return ((timestamp - twepoch) << TIMESTAMP_LEFT_SHIFT)| (dataCenterId << DATA_CENTER_ID_SHIFT)| (workerId << WORKER_ID_SHIFT)| sequence;}/*** 循环等待下一个时间** @param lastTimestamp 上次记录的时间* @return 下一个时间*/private long tilNextMillis(long lastTimestamp) {long timestamp = genTime();// 循环直到操作系统时间戳变化while (timestamp == lastTimestamp) {timestamp = genTime();}if (timestamp < lastTimestamp) {// 如果发现新的时间戳比上次记录的时间戳数值小,说明操作系统时间发生了倒退,报错throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));}return timestamp;}/*** 生成时间戳** @return 时间戳*/private long genTime() {return this.useSystemClock ? SystemClock.now() : System.currentTimeMillis();}// ------------------------------------------------------------------------------------------------------------------------------------ Private method end
}

会有点长,但是核心的东西就一段

我们直接来看这一段

	/*** 下一个ID** @return ID*/public synchronized long nextId() {//获取当前时间戳long timestamp = genTime();//如果小于上次的时间,这里有问题,时间回拨!if (timestamp < this.lastTimestamp) {if(this.lastTimestamp - timestamp < timeOffset){// 容忍指定的回拨,避免NTP校时造成的异常timestamp = lastTimestamp;} else{// 如果服务器时间有问题(时钟后退) 报错。throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));}}//如果等于上次的时间,说明,此时是同一毫秒,递增序列号if (timestamp == this.lastTimestamp) {//SEQUENCE_MASK为4095,这里的运算看上面的解释,这个相当于最大值//SEQUENCE_MASK 为                             00000000000000000000000000000000000000000000 0000 1111 1111 1111 1111//假设此时的序列号为4095(sequence) 那么前面是4096   00000000000000000000000000000000000000000000 0001 0000 0000 0000 0000//这样子与,0 & 0 = 0 ----- 0 & 1 = 0 ----- 1 & 1 = 1//所以最后结果为 								   00000000000000000000000000000000000000000000 0000 0000 0000 0000 0000final long sequence = (this.sequence + 1) & SEQUENCE_MASK;//如果此时为0说明,已经到了4095了,到达上限,应该等待下一个毫秒if (sequence == 0) {timestamp = tilNextMillis(lastTimestamp);}this.sequence = sequence;} else {sequence = 0L;}//赋值此时的上一次时间戳(毫秒)lastTimestamp = timestamp;return ((timestamp - twepoch) << TIMESTAMP_LEFT_SHIFT)| (dataCenterId << DATA_CENTER_ID_SHIFT)| (workerId << WORKER_ID_SHIFT)| sequence;}

我们来总结一下,这个核心代码的代码逻辑

我们要生成id的化,需要几部分 时间戳 + 机器码 + 序列号
机器码也就是我们服务器的标识,一般是我们字节写的,所以不用考虑这个

重点在于时间戳 + 序列号


时间戳的生成: 当前时间戳,并且是毫秒级的
时间戳的生成,代码很简单,所以也不要终点考虑


序列号的生成(重点): 第一: 我们需要校验这里的时间戳,是否有问题,也就是当前时间比上一次的时间还早,出现时间回拨问题

第二: 我们得校验此时的序列号是否超过上限,如果超过上限,那么置此时的序列号为0,并且等待下一毫秒,将此时的时间戳更新

最重要的问题就是这两,相比较,比较简单的问题是这里的超过上限问题,这里也很简单,就是循环等待下一毫秒,到达下一毫秒更新此时的时间戳,序列号也已经设置好了为0

最难也是最重要的问题,时间回拨问题,这里的位运算问题,还是很好理解的,只要会位运算,都能解决

但是我这里特别不能搞懂,为啥这里要用位运算

类似于如下代码

	// 序列掩码,用于限定序列最大值不能超过4095//计算机的负数是用补码表示的//1L     0000000000000000000000000000000000000000000000000000000000000001//1L 反码 1111111111111111111111111111111111111111111111111111111111111110//1L 补码 1111111111111111111111111111111111111111111111111111111111111111      补码 = 反码 + 1//这里是 -1L(64位1) 往左移动12位 111111111111111111111111111111111111111111111111 0000 0000 0000 0000// ~ 取反 					  000000000000000000000000000000000000000000000000 1111 1111 1111 1111//结果为                       2的12次方 - 1 = 4095@SuppressWarnings("FieldCanBeLocal")private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);// 4095

这里的mask就是,相当于最大值,我不能明白的是,为什么不直接写4095L 或者写2的12次方 - 1,这里的12次方的12 一样也可以写成这里的 SEQUENCE_BITS 为啥要搞这个位运算???,我查了一下,都没有这方面的问题,如果你懂的化,可以私信我,谢谢了

时钟回拨问题

我也是看别人说,会有这个时钟回拨问题,问题的出现在于,有可能运维人员手动的更改了服务器的时间,或者两个服务器时间不同,需要同步时间,就会导致这里的时钟回拨问题

解决方案:

第一种方案: 是如果是时间回拨只是一两次,并且时间跨度不是很大的化,例如1 到 3秒,那么就直接等,那么几秒,这样子相当于有冗余,但是影响也不是很大,但这种操作,不能再并发量很高的时候操作,不然肯定出问题

第二种方案: 就是美团 和百度的方案
这两的方案我就先不研究了,到时候我真的懂了,就来更新这里的博客,我估计我也看不懂

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/673943.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

巴尔加瓦算法图解:算法运用。

树 如果能将用户名插入到数组的正确位置就好了&#xff0c;这样就无需在插入后再排序。为此&#xff0c;有人设计了一种名为二叉查找树(binary search tree)的数据结构。 每个node的children 都不大于两个。对于其中的每个节点&#xff0c;左子节点的值都比它小&#xff0c;…

MySQL用心总结

大家好&#xff0c;好久不见&#xff0c;今天笔者用心一步步写一份mysql的基础操作指南&#xff0c;欢迎各位点赞收藏 -- 启动MySQL net start mysql-- 创建Windows服务 sc create mysql binPath mysqld_bin_path(注意&#xff1a;等号与值之间有空格) mysql -h 地址 -…

Android Studio安装过程遇到SDK无法安装问题解决

首次打开studio遇到该类问题&#xff0c;需要下载SDK文件&#xff0c;后又发现SDK由于是Google源&#xff0c;无法进行正常安装&#xff0c;故转而进行SDK的镜像安装。 一、下载SDK Tools 地址&#xff1a;AndroidDevTools - Android开发工具 Android SDK下载 Android Studio…

华为配置访客接入WLAN网络示例(MAC优先的Portal认证)

配置访客接入WLAN网络示例&#xff08;MAC优先的Portal认证&#xff09; 组网图形 图1 配置WLAN MAC优先的Portal认证示例组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件 业务需求 某企业为了提高WLAN网络的安全性&#xff0c;采用MAC优先的外置Portal认…

新型RedAlert勒索病毒针对VMWare ESXi服务器

前言 RedAlert勒索病毒又称为N13V勒索病毒&#xff0c;是一款2022年新型的勒索病毒&#xff0c;最早于2022年7月被首次曝光&#xff0c;主要针对Windows和Linux VMWare ESXi服务器进行加密攻击&#xff0c;到目前为止该勒索病毒黑客组织在其暗网网站上公布了一名受害者&#x…

HCIA-HarmonyOS设备开发认证V2.0-3.2.轻量系统内核基础-任务管理

目录 一、任务管理1.1、任务状态1.2、任务基本概念1.3、任务管理使用说明1.4、任务开发流程1.5、任务管理接口 一、任务管理 从系统角度看&#xff0c;任务是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源&#xff0c;并独立于其它任务运行。 O…

C语言操作符详解

操作符的分类 • 算数操作符 &#xff1a; 、 - 、 * 、 / 、 % • 移位操作符 &#xff1a; << 、 >> • 位操作符 &#xff1a; & 、 | 、 ^ • 赋值操作符 &#xff1a; 、 、 - 、 * 、 / 、 % 、 << 、 >> 、 & 、 |…

音视频色彩:RGB/YUV

目录 1.RGB 1.1介绍 1.2分类 1.2.1RGB16 1)RGB565 2)RGB555 1.2.2RGB24 1.2.3RGB222 2.YUV 2.1介绍 2.2分类 2.2.1 YUV444 2.2.2 YUV 422 2.2.3 YUV 420 2.3存储格式 2.3.1 YUYV 2.3.2 UYVY 2.3.3 YUV 422P 2.3.4 YUV420P/YUV420SP 2.3.5 YU12 和…

IS-IS 接口认证密码平滑更换

拓扑图 配置 AR1、AR2建立ISIS level-2邻居关系&#xff0c;并配置接口认证密码为huawei sysname AR1 # isis 1is-level level-2network-entity 49.0000.0000.0000.0001.00 # interface GigabitEthernet0/0/0ip address 12.1.1.1 255.255.255.0 isis enable 1isis authentica…

Spark安装(Yarn模式)

一、解压 链接&#xff1a;https://pan.baidu.com/s/1O8u1SEuLOQv2Yietea_Uxg 提取码&#xff1a;mb4h tar -zxvf /opt/software/spark-3.0.3-bin-hadoop3.2.tgz -C /opt/module/spark-yarn mv spark-3.0.3-bin-hadoop3.2/ spark-yarn 二、配置环境变量 vim /etc/profile…

【typescript】特殊符号用法(?:)(??)(?.)(!)(!!)

一. 问号冒号&#xff08;?:&#xff09; 1.可以作为对象类型的可选属性&#xff0c;如&#xff1a; interface Person{name : string;age?: number; }const person1 : Person {name:"zien"} const person2 : Person {name:"sad", age:18} console.l…

MacBook有必要装清理软件吗?CleanMyMac的一些主要特点

MacBook是苹果公司的一款高端笔记本电脑&#xff0c;但是&#xff0c;随着使用时间的增长&#xff0c;MacBook也会出现一些问题&#xff0c;比如运行缓慢、卡顿、垃圾文件堆积、磁盘空间不足等。这些问题不仅影响了用户的使用体验&#xff0c;也可能对MacBook的寿命和安全性造成…

如何将国风与品牌信息相结合?

随着人们消费观念的转型升级。「国风」成为深受品牌欢迎的营销元素&#xff0c;它能够通过东方美学引起用户。然而有许多品牌在国风营销中稍不注意就会踩雷&#xff0c;今天媒介盒子就来和大家聊聊&#xff1a;国风营销怎么做才能吸引用户。 一、 与用户生活结合 要找到传统文…

ChatGPT高效提问—prompt常见用法(续篇四)

ChatGPT高效提问—prompt常见用法&#xff08;续篇四&#xff09; 1.1 知识生成 ​ 知识生成是指使用自然语言处理技术&#xff0c;通过ChatGPT等AI模型生成与特定主题相关的知识、文本或回答。在知识生成过程中&#xff0c;模型接收prompt输入的问题、指令或上下文信息&…

Vue3中路由配置Catch all routes (“*“) must .....问题

Vue3中路由配置Catch all routes (“*”) must …问题 文章目录 Vue3中路由配置Catch all routes ("*") must .....问题1. 业务场景描述1. 加载并添加异步路由场景2. vue2中加载并添加异步路由(OK)3. 转vue3后不好使(Error)1. 代码2. 错误 2. 处理方式1. 修改前2. 修…

8_姿态的其他描述及一般坐标系映射

1.机器人姿态的其他表示方法 前面说的用33矩阵矩阵描述姿态&#xff0c;9个元素&#xff0c;6个约束条件&#xff0c;实际上只有3个独立元素。即用3个独立元素即可描述机器人姿态。常用的有RPY角&#xff0c;欧拉角和四元数。 1.1 RPY角 RPY角是船舶在海上航行时常用的一种姿态…

基于java+springboot+vue实现的高校物品捐赠管理系统(文末源码+Lw)23-151

第1章 绪论 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔记本的广泛运用&#xff0c;以及各种计算机硬件的完善和升级&#xf…

不到1s生成mesh! 高效文生3D框架AToM

论文题目&#xff1a; AToM: Amortized Text-to-Mesh using 2D Diffusion 论文链接&#xff1a; https://arxiv.org/abs/2402.00867 项目主页&#xff1a; AToM: Amortized Text-to-Mesh using 2D Diffusion 随着AIGC的爆火&#xff0c;生成式人工智能在3D领域也实现了非常显著…

信钰证券:2024年最新创业板开通条件?

创业板是深圳证券买卖所建立的一个专门为创新型、成长型企业服务的板块&#xff0c;受到了不少投资者的关注。对于2024年最新创业板注册条件&#xff0c;信钰证券下面就为我们详细介绍一下。 2024年最新创业板注册条件&#xff1a; 1、投资者的财物要求&#xff1a;投资者申请…

JavaScript基础(28)_获取元素的其他样式

其他样式操作的属性 clientWidth(只读)&#xff1a;获取元素的"可见宽度"&#xff0c;包括内容区和内边距(返回的是一个数字&#xff0c;不带px&#xff0c;可直接进行计算)。 clientHeight(只读)&#xff1a;获取元素的"可见高度"&#xff0c;包括内容区…