文章目录

  • 前言
  • MySQL 数据类型
    • 数值类型
    • 日期和时间类型
    • 字符串类型
  • 自定义id实现思路
    • 时间戳加随机数
    • 时间戳加递增数定长增长id
    • 年份+毫秒值时间戳+三位数递增的定长方法
    • UUID
    • 雪花算法,类雪花算法

前言

先说一下id吧,ID(Identity document),即身份信息,无论是生活中还是软件设计中,id都是用于辨识身份的,id在数据库的设计中也往往是主键。那么为什么要自定义id呢?

通常因为业务上常有以下需求:

  1. 全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
  2. 趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
  3. 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
  4. 信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。

mysql免费常见,市场占有率广,我们往往在练习中使用mysql,而id都是使用整数Int型自增id,但实际上在生成中不应该使用,不安全,容易被推测出id,特别是前面的id的数据,容易被攻击。数据库自身的主键id自增已经不能满足需求了,所以就会出现我们使用自定义id插入。

还有我们知道在mysql数据库中,如果主键使用的整数型,速度会比字符串快,但是以字符串为代表的主键大多数是通过精密算法得出的,可靠性和唯一性更高,典型代表就是UUID啦,扛把子没得说,那接下来就看一下几种思路的自定义id啦

那么要自定义id,也要自定义id类型和数据库类型能对上才行,那我们先了解以下mysql有哪些数据类型,以及其默认长度吧

MySQL 数据类型

数值类型

MySQL支持所有标准SQL数值数据类型。

这些类型包括严格数值数据类型(INTEGER、SMALLINT、DECIMAL和NUMERIC),以及近似数值数据类型(FLOAT、REAL和DOUBLE PRECISION)。

关键字INT是INTEGER的同义词,关键字DEC是DECIMAL的同义词。

BIT数据类型保存位字段值,并且支持MyISAM、MEMORY、InnoDB和BDB表。

作为SQL标准的扩展,MySQL也支持整数类型TINYINT、MEDIUMINT和BIGINT。下面的表显示了需要的每个整数类型的存储和范围。

类型 大小 范围(有符号) 范围(无符号) 用途
TINYINT 1 byte (-128,127) (0,255) 小整数值
SMALLINT 2 bytes (-32 768,32 767) (0,65 535) 大整数值
MEDIUMINT 3 bytes (-8 388 608,8 388 607) (0,16 777 215) 大整数值
INT或INTEGER 4 bytes (-2 147 483 648,2 147 483 647) (0,4 294 967 295) 大整数值
BIGINT 8 bytes (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) (0,18 446 744 073 709 551 615) 极大整数值
FLOAT 4 bytes (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) 0,(1.175 494 351 E-38,3.402 823 466 E+38) 单精度 浮点数值
DOUBLE 8 bytes (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) 双精度 浮点数值
DECIMAL 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 依赖于M和D的值 依赖于M和D的值 小数值

日期和时间类型

表示时间值的日期和时间类型为DATETIME、DATE、TIMESTAMP、TIME和YEAR。

每个时间类型有一个有效值范围和一个"零"值,当指定不合法的MySQL不能表示的值时使用"零"值。

TIMESTAMP类型有专有的自动更新特性,将在后面描述。

类型 大小 ( bytes) 范围 格式 用途
DATE 3 1000-01-01/9999-12-31 YYYY-MM-DD 日期值
TIME 3 ‘-838:59:59’/‘838:59:59’ HH:MM:SS 时间值或持续时间
YEAR 1 1901/2155 YYYY 年份值
DATETIME 8 1000-01-01 00:00:00/9999-12-31 23:59:59 YYYY-MM-DD HH:MM:SS 混合日期和时间值
TIMESTAMP 4 1970-01-01 00:00:00/2038结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07 YYYYMMDD HHMMSS 混合日期和时间值,时间戳

字符串类型

字符串类型指CHAR、VARCHAR、BINARY、VARBINARY、BLOB、TEXT、ENUM和SET。该节描述了这些类型如何工作以及如何在查询中使用这些类型。

类型 大小 用途
CHAR 0-255 bytes 定长字符串
VARCHAR 0-65535 bytes 变长字符串
TINYBLOB 0-255 bytes 不超过 255 个字符的二进制字符串
TINYTEXT 0-255 bytes 短文本字符串
BLOB 0-65 535 bytes 二进制形式的长文本数据
TEXT 0-65 535 bytes 长文本数据
MEDIUMBLOB 0-16 777 215 bytes 二进制形式的中等长度文本数据
MEDIUMTEXT 0-16 777 215 bytes 中等长度文本数据
LONGBLOB 0-4 294 967 295 bytes 二进制形式的极大文本数据
LONGTEXT 0-4 294 967 295 bytes 极大文本数据

注意:char(n) 和 varchar(n) 中括号中 n 代表字符的个数,并不代表字节个数,比如 CHAR(30) 就可以存储 30 个字符。

CHAR 和 VARCHAR 类型类似,但它们保存和检索的方式不同。它们的最大长度和是否尾部空格被保留等方面也不同。在存储或检索过程中不进行大小写转换。

BINARY 和 VARBINARY 类似于 CHAR 和 VARCHAR,不同的是它们包含二进制字符串而不要非二进制字符串。也就是说,它们包含字节字符串而不是字符字符串。这说明它们没有字符集,并且排序和比较基于列值字节的数值值。

BLOB 是一个二进制大对象,可以容纳可变数量的数据。有 4 种 BLOB 类型:TINYBLOB、BLOB、MEDIUMBLOB 和 LONGBLOB。它们区别在于可容纳存储范围不同。

有 4 种 TEXT 类型:TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。对应的这 4 种 BLOB 类型,可存储的最大长度不同,可根据实际情况选择。


自定义id实现思路

时间戳加随机数

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ThreadLocalRandom;public class IdGenerator {//选择日期格式static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");//封装时间戳加随机数方法public static String timestamp() {//获取当前时间LocalDateTime now = LocalDateTime.now();//使用指定日期格式String format = now.format(formatter);//返回当前时间戳+三位随机数策略return format + (ThreadLocalRandom.current().nextInt(999) + 1);}//遍历,测试(效果同下)public static void main(String[] args) {LocalDateTime now = LocalDateTime.now();String format = now.format(formatter);for (int i = 0; i < 10; i++) {System.out.println(format + (ThreadLocalRandom.current().nextInt(999) + 1));}System.out.println("=======================================");//使用静态方法调用封装方法for (int j=0;j<10;j++){System.out.println(IdGenerator.timestamp());}}}

测试结果



多测几次可见,其特点是可以生成20位以内的id,而且基本趋于增势,而且是整数id,安全性一般,但是在性能上还是有一定保证,出现不足20位的原因是秒值和毫秒值有可能出现为0的情况,所以只能说基本趋于增势,在并发量不是超高的情况下还是可以考虑的,但是如果要求id位数固定,就要自行改造了,就要补0或者换一种时间戳实现,特别是如果是税务要求固定20位的时候。其数据库字段可以对应bigint。可修改为以下方法可满足:

时间戳加递增数定长增长id

import java.text.SimpleDateFormat;
import java.util.Date;
/*** 获取主键:返回17位时间戳+3位递增数(同一时间递增)*/
public class IdCreator {private static int addPart = 1;private static String result = "";private static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");private static String lastDate = "";/*** 获取主键* @param length 长度* @return 返回17位时间戳+3位递增数*/public synchronized static String getId(int length) {//获取时间部分字符串Date now = new Date();String nowStr = sdf.format(now);//获取数字后缀值部分if (IdCreator.lastDate.equals(nowStr)) {addPart += 1;} else {addPart = 1;lastDate = nowStr;}if (length > 17) {length -= 17;for (int i = 0; i < length - ((addPart + "").length()); i++) {nowStr += "0";}nowStr += addPart;result = nowStr;} else {result = nowStr;}//20200827152917060001return result;}public static void main(String[] args) {//模拟测试功能for (int i = 0; i <10 ; i++) {System.out.println(IdCreator.getId(20));}System.out.println("================================");//静态调用演示for (int i = 0; i <10 ; i++) {System.out.println(IdCreator.getId(20));}}
}


此处输出的是18位,java种可以使用long装配为主键,要满足税务发票编号要求,20位,特点是单向递增,可有序排列,性能尚可,但是安全性偏低。上面20位可以就可以在return那里不截取了,就完美实现啦,是不是也是棒棒哒。

年份+毫秒值时间戳+三位数递增的定长方法

有人说你这些都太长了,我就要long直接可以接受的id怎么办?那我们就使用截取将毫秒值的开头截取两位,就变4+11+3了。

import java.text.SimpleDateFormat;public class GuidUtils {/*** 获取18位随机数* 4位年份+11位时间戳(毫秒值截取2位)+3位随机数* @author*/public static void main(String[] args) {for (int i = 0; i <10 ; i++) {//调用生成id方法System.out.println(GuidUtils.getGuid());}System.out.println("======");System.out.println(System.currentTimeMillis());}/*** 时间戳后的末尾的数字id*/public static int Guid=100;//多线程下使用volitale修饰//public static volitale int Guid=100;public static String getGuid() {GuidUtils.Guid+=1;long now = System.currentTimeMillis();//获取4位年份数字  SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy");//获取时间戳  String time=dateFormat.format(now);String info=now+"";//获取三位随机数  //int ran=(int) ((Math.random()*9+1)*100); //要是一段时间内的数据连过大会有重复的情况,所以做以下修改int ran=0;if(GuidUtils.Guid>999){GuidUtils.Guid=100;}ran=GuidUtils.Guid;return time+info.substring(2, info.length())+ran;}}


这个方案总的来说还是感觉挺不错的,方便,也不会太长,long类型即可接收,而且是定长,也有递增,当然觉得递增还是不够安全,就改成随机数也是可以的。

UUID

UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:550e8400-e29b-41d4-a716-446655440000,到目前为止业界一共有5种方式生成UUID,详情见IETF发布的UUID规范。
优点:

  • 性能非常高:本地生成,没有网络消耗。

缺点:

  • 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。

  • 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。

  • ID作为主键时在特定的环境会存在一些问题,比如做DB主键的场景下,UUID就非常不适用:

  • ① MySQL官方有明确的建议主键要尽量越短越好[4],36个字符长度的UUID不符合要求。

All indexes other than the clustered index are known as secondary indexes. In InnoDB, each record in a secondary index contains the primary key columns for the row, as well as the columns specified for the secondary index. InnoDB uses this primary key value to search for the row in the clustered index.*** If the primary key is long, the secondary indexes use more space, so it is advantageous to have a short primary key***.

② 对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。

所以直接用uuid作为主键其实还是比较一般的,如果是普通的设计可以直接使用uuid作为主键,至少保证了全局唯一性。

import java.util.UUID;public class UUIDUtils {/*** 带-的UUID** @return 36位的字符串*/public static String getUUID() {return UUID.randomUUID().toString();}/*** 去掉-的UUID** @return 32位的字符串*/public static String getUUID2() {return UUID.randomUUID().toString().replace("-", "");}public static void main(String[] args) {for (int i = 0; i <5; i++) {System.out.println(UUIDUtils.getUUID());}System.out.println("================================");for (int i = 0; i <5 ; i++) {System.out.println(UUIDUtils.getUUID2());}}
}


测试结果如上。

那如果觉得这样会造成InnoDB无序性怎么办?可以考虑ID物理主键+UUID逻辑主键

  • 优点:

InnoDB会对主键进行物理排序,这对auto_increment_int类型有好处,因为后一次插入的主键位置总是在最后。但是对uuid来说则有缺点,因为uuid是杂乱无章的,每次插入的主键位置是不确定的,可能在开头,也可能在中间,在进行主键物理排序的时候,势必会造成大量的IO操作影响效率。
缺点:

  • 同自增ID的缺点:全局值加锁解锁以保证增量的唯一性带来的性能问题。还有一个就是对于数据库有相关性单独用uuid和上面的时间戳结合方法其实可以避免数据库相关性,再更换数据库的时候可以平滑过渡。

自增id+UUID的方法在分布式里面还是挺常见的设计模式。

雪花算法,类雪花算法

/*** Twitter_Snowflake<br>* SnowFlake的结构如下(每部分用-分开):<br>* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>* 41位时间戳(毫秒级),注意,41位时间戳不是存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 - 开始时间戳)* 得到的值),这里的的开始时间戳,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间戳,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间戳)产生4096个ID序号<br>* 加起来刚好64位,为一个Long型。<br>* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。*/public class SnowflakeIdWorker {// ==============================Fields===========================================/** 开始时间截 (201-01-01) */private final long twepoch = 1514736000000L;/** 机器id所占的位数 */private final long workerIdBits = 5L;/** 数据标识id所占的位数 */private final long datacenterIdBits = 5L;/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */private final long maxWorkerId = -1L ^ (-1L << workerIdBits);/** 支持的最大数据标识id,结果是31 */private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);/** 序列在id中占的位数 */private final long sequenceBits = 12L;/** 机器ID向左移12位 */private final long workerIdShift = sequenceBits;/** 数据标识id向左移17位(12+5) */private final long datacenterIdShift = sequenceBits + workerIdBits;/** 时间截向左移22位(5+5+12) */private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */private final long sequenceMask = -1L ^ (-1L << sequenceBits);/** 工作机器ID(0~31) */private long workerId;/** 数据中心ID(0~31) */private long datacenterId;/** 毫秒内序列(0~4095) */private long sequence = 0L;/** 上次生成ID的时间截 */private long lastTimestamp = -1L;//==============================Constructors=====================================/*** 构造函数* @param workerId 工作ID (0~31)* @param datacenterId 数据中心ID (0~31)*/public SnowflakeIdWorker(long workerId, long datacenterId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));}if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));}this.workerId = workerId;this.datacenterId = datacenterId;}// ==============================Methods==========================================/*** 获得下一个ID (该方法是线程安全的)* @return SnowflakeId*/public synchronized long nextId() {long timestamp = timeGen();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;//毫秒内序列溢出if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - twepoch) << timestampLeftShift) //| (datacenterId << datacenterIdShift) //| (workerId << workerIdShift) //| sequence;}/*** 阻塞到下一个毫秒,直到获得新的时间戳* @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间* @return 当前时间(毫秒)*/protected long timeGen() {return System.currentTimeMillis();}//==============================Test=============================================public static void main(String[] args) {SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);for (int i = 0; i < 10; i++) {long id = idWorker.nextId();System.out.println(Long.toBinaryString(id));System.out.println(id);}}}

运行结果如下:

就这样18位的id就闪亮登场啦,很赞,建议使用。

  • 优点:
  1. 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
    不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
  2. 可以根据自身业务特性分配bit位,非常灵活。
  • 缺点:

强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

值得注意的是使用时间戳的都要注意避免机器的时钟回调!!

所以最好或者说强制做一个判断,时间戳小于正常时间戳的时候抛异常,等待恢复正常再重新复配,可以参考以下代码:

 //发生了回拨,此刻时间小于上次发号时间if (timestamp < lastTimestamp) {long offset = lastTimestamp - timestamp;if (offset <= 5) {try {//时间偏差大小小于5ms,则等待两倍时间wait(offset << 1);//waittimestamp = timeGen();if (timestamp < lastTimestamp) {//还是小于,抛异常并上报throwClockBackwardsEx(timestamp);}    } catch (InterruptedException e) {  throw  e;}} else {//throwthrowClockBackwardsEx(timestamp);}}//分配ID

以下附上本文参考博客:
https://tech.meituan.com/2017/04/21/mt-leaf.html
https://www.jianshu.com/p/89dfe990295c
https://www.cnblogs.com/cs99lzzs/p/9869414.html
https://blog.csdn.net/xpf_user/article/details/79193052
https://www.cnblogs.com/qlqwjy/p/7530602.html
如觉得本文不错,想要转载,请注意备注本文出处。尤其是转载的公众号或是其他资讯网站

源码托管于gitee,可去此处克隆或下载:https://gitee.com/calmtho/idtest

自定义id的几种思路分享以及税务单据编号实现相关推荐

  1. 兼职,副业的另一种思路分享:想在网上赚钱应该从哪里开始?

    现在很多人都想下班后做点兼职和副业,来增加收入,补贴家用.所以很多人纷纷加入了兼职和副业的行列当中,有人喜欢下了班出去送送外卖,开开滴滴,做做代驾,还有的喜欢去餐厅里做做临时服务员.我觉得这样做是能赚 ...

  2. 知识分享之Golang——使用gorm时进行执行自定义SQL的几种方式

    知识分享之Golang--使用gorm时进行执行自定义SQL的几种方式 背景 知识分享之Golang篇是我在日常使用Golang时学习到的各种各样的知识的记录,将其整理出来以文章的形式分享给大家,来进 ...

  3. 两种链表的实现以及例题思路分享

    前言 鉴于顺序表在内存中存储有诸多缺点,其中典型的就是数组,数组每个元素在内存中都是连续存放的,导致每次在头部或者中间插入新元素都需要挪动已有的元素,而链表就恰好弥补了顺序表的一些缺点.本篇文章主要讲 ...

  4. NB-IOT实现万物互联设计思路分享 (从硬件到单片机到云平台)

    NB-IOT实现万物互联 产品设计思路分享 NB-IOT窄带物联网(Narrow Band Internet of Things, NB-IoT),是一种专为万物互联打造的蜂窝网络连接技术.NB-IO ...

  5. 水下目标检测算法赛方法总结与思路分享(已开源)

    水下目标检测算法赛方法总结与思路分享 我们团队在此分享下在 "2020年全国水下机器人(湛江)大赛 - 水下目标检测算法赛" 这一比赛中的实验过程及心得体会.不足之处,还望批评指正 ...

  6. display none 隐藏后怎么显示_web前端入门到实战:元素显示隐藏的9种思路

    我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴 ...

  7. shell脚本-创建用户的4种思路

    大家好,我是沐风,互联网老辛的助理,今天由我分享shell脚本之创建用户的4种思路. 这里只是抛砖引玉,希望你看完之后能够用更多种方法实现,集思广益,用老辛讲的 [穷举法]反复练习. 需求描述: 写一 ...

  8. 2亿数据量PostgreSQL 10.4查询调优思路分享

    目录 ●背景 ●使用物理服务器 ●增加内存大小 ●使用NVMe协议的固态硬盘 ●将数据库安装在内存 ●业务调整 ●修改默认配置项 ●启用Gin (Generalized Inverted Index) ...

  9. layer ui ajax 样式,layerUi与AJAX的一种思路

    javascript: function rep(id) { layer.confirm("确定要拒绝此认证吗?", { btn: ["确定", "取 ...

  10. “授之以鱼” 不如 “授之以渔“(网页设计-第十次作业-思路分享)

    一,为什么要写一篇思路分享的文章? 预料之外–阅读量上升 因为我将网页制作和java作业整理上传的CSDN的最初原因,是为了数据归档,当然也可以给有需要的朋友参考一下.之前看了一下,好像网页制作的作业 ...

最新文章

  1. 基于OHCI的USB主机 —— 结束语
  2. NUnitForms 测试GUI应用程序的优秀工具
  3. oracle创建定时任务
  4. 被1.5W用户吐成翔的10大互联网产品,你躺枪了吗?
  5. 计算机微课应用报告书,【计算机专业论文】计算机专业教学中微课的应用(共4253字)...
  6. 16个烧光你脑细胞的悖论
  7. html5 canvas气泡动画
  8. linux用户管理和群界面怎么打开,Linux的用户和组群管理
  9. [BJWC2011]元素
  10. 【亲测有效】vs2017无法断点
  11. Python学习之路day02——007字典的嵌套
  12. [Unity] 利用Culling Group实现LOD和剔除逻辑
  13. 使用N2N搭建虚拟局域网|可用于红警、我的世界联机
  14. 大胆预测,2019年最佳外置硬盘和便携式SSD非这四款莫属!
  15. 9个面向前端开发者的有用VSCode 插件工具
  16. week11作业——C - 必做题11-3
  17. CUDA:两种自适应图像去噪技术KNN和NLM的实例
  18. 多传感器融合定位七-惯性导航解算及误差分析其一
  19. 前端vue+element ie兼容性问题
  20. python爬虫获取网络图片

热门文章

  1. React组件化开发
  2. u盘复制到计算机的文档打不开怎么办,U盘文件复制到别的电脑打不开怎么办
  3. hdu6287 口算训练(质因子分解,二分)
  4. wordpress添加备案链接 亲测无误
  5. 华为手机相册怎么镜像翻转_手机相册里的照片误删怎么恢复?安卓通用照片恢复...
  6. 在Mac里读取NTFS格式的盘
  7. IIS部署添加网站发布网站
  8. iis部署网站 html文件路径,iis发布网页
  9. 计算机笔记本摄像头无法使用,笔记本摄像头不能用怎么回事 笔记本摄像头不能用解决方法...
  10. 计算机科学美国研究生排名,最新出炉 2018年USNews美国大学研究生计算机科学专业排名榜单...