目录

一、什么是MD5

二、MD5的功能

三、抗膨胀性

四、可逆性

五、MD5是 加密算法吗?

六、MD5用途

1、防止被篡改

2、防止明文读取。

3、防止抵赖

七、MD5算法过程

主要过程描述

第一步:填充

第二步:记录信息长度

第三步:装入标准的幻数(四个整数)

第四步:四轮循环运算

八、java代码如下


一、什么是MD5

MD5信息简要是一种散列算法,可以对任意长度的输入,产生128位的输出,是一种被广泛应用的密码杂凑算法。

二、MD5的功能

输入任意长度的信息,输出128位的散列值(数字密码)

唯一性:不同的输入产生不同的输出

三、抗膨胀性

理论上MD5是不具备抗碰撞性的,因为输入是任意的 , 是无限的 , 但是输出是128位 , 也就是2^128,是有限的。以有限映射无限,必然出现碰撞。但是在实际应用中,我们的输入也是有限的,所以出现碰撞的几率是非常小的。

四、可逆性

MD5是散列哈希,是一种有损强压缩算法,在压缩过程中是会丢失信息,所以是不可逆的。

五、MD5是 加密算法吗?

一部分人认为MD5,是不可逆的,只有加密过程,没有解密过程,不能通过密文还原明文,不属于加密算法。

而另一部分人认为原明文信息经过MD5后生成了密文,已经进行了加密,MD5应该是归于加密算法。

个人观点:MD5用作生成数字码,和BASE64一样,属于是编码算法范畴。

六、MD5用途

1、防止被篡改

比如发送一个电子邮件,我们先生成MD5密文 a , 等对方收到邮件后,根据邮件内容生成密文b , 如果a=b ,则邮件未被篡改。

比如网站提供下载功能,可以预先生成MD5散列,并公布在网站上,客户下载后可以通过验证MD5散列,查看下载的数据是否被更改过。

2、防止明文读取。

比如我们系统里关于用户密码的问题 , 一般是不允许被明文入库的,这样会有丢失风险。常规的做法是MD5(password + salt) 生成密文后入库存档,这样即使数据被泄露 , 也无法通过密文反推用户密码。

3、防止抵赖

比如我做出了某项承诺:“过年给大家发3个月的年终奖” ,并预先生成MD5数字指纹 , 到了年底上,我不能私自将3个月改称1个月,因为MD5输入的微小变化,将引起MD5数字指纹的巨大变化。

七、MD5算法过程

我们可以把MD5算法简要的概述如下:将输入信息分组 , 每组512位 ,而每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。

主要过程描述

第一步:填充

如果输入信息的长度(bit)对512求余的结果不等于448,就需要填充使得对512求余的结果等于448。填充的方法是填充一个1和n个0。填充完后,信息的长度就为N*512+448(bit);

第二步:记录信息长度

用64位来存储填充前信息长度。这64位加在第一步结果的后面,这样信息长度就变为N512+448+64=(N+1)512位。

第三步:装入标准的幻数(四个整数)

标准的幻数(物理顺序)是(A=(01234567)16,B=(89ABCDEF)16,C=(FEDCBA98)16,D=(76543210)16)。如果在程序中定义应该是: (A=0X67452301L,B=0XEFCDAB89L,C=0X98BADCFEL,D=0X10325476L)。有点晕哈,其实想一想就明白了。

第四步:四轮循环运算

循环的次数是分组的个数(N+1)

1)将每一512字节细分成16个小组,每个小组64位(8个字节)

2)先认识四个线性函数(&是与,|是或,~是非,^是异或)

     F(X,Y,Z)=(X&Y)|((~X)&Z) G(X,Y,Z)=(X&Z)|(Y&(~Z)) H(X,Y,Z)=X^Y^Z I(X,Y,Z)=Y^(X|(~Z))

3)设Mj表示消息的第j个子分组(从0到15),<<

    FF(a,b,c,d,Mj,s,ti)表示a=b+((a+F(b,c,d)+Mj+ti)<<<s)GG(a,b,c,d,Mj,s,ti)表示a=b+((a+G(b,c,d)+Mj+ti)<<<s)HH(a,b,c,d,Mj,s,ti)表示a=b+((a+H(b,c,d)+Mj+ti)<<<s)II(a,b,c,d,Mj,s,ti)表示a=b+((a+I(b,c,d)+Mj+ti)<<<s)

4)四轮运算

第一轮

    a=FF(a,b,c,d,M0,7,0xd76aa478)b=FF(d,a,b,c,M1,12,0xe8c7b756)c=FF(c,d,a,b,M2,17,0x242070db)d=FF(b,c,d,a,M3,22,0xc1bdceee)a=FF(a,b,c,d,M4,7,0xf57c0faf)b=FF(d,a,b,c,M5,12,0x4787c62a)c=FF(c,d,a,b,M6,17,0xa8304613)d=FF(b,c,d,a,M7,22,0xfd469501)a=FF(a,b,c,d,M8,7,0x698098d8)b=FF(d,a,b,c,M9,12,0x8b44f7af)c=FF(c,d,a,b,M10,17,0xffff5bb1)d=FF(b,c,d,a,M11,22,0x895cd7be)a=FF(a,b,c,d,M12,7,0x6b901122)b=FF(d,a,b,c,M13,12,0xfd987193)c=FF(c,d,a,b,M14,17,0xa679438e)d=FF(b,c,d,a,M15,22,0x49b40821)
​第二轮a=GG(a,b,c,d,M1,5,0xf61e2562)b=GG(d,a,b,c,M6,9,0xc040b340)c=GG(c,d,a,b,M11,14,0x265e5a51)d=GG(b,c,d,a,M0,20,0xe9b6c7aa)a=GG(a,b,c,d,M5,5,0xd62f105d)b=GG(d,a,b,c,M10,9,0x02441453)c=GG(c,d,a,b,M15,14,0xd8a1e681)d=GG(b,c,d,a,M4,20,0xe7d3fbc8)a=GG(a,b,c,d,M9,5,0x21e1cde6)b=GG(d,a,b,c,M14,9,0xc33707d6)c=GG(c,d,a,b,M3,14,0xf4d50d87)d=GG(b,c,d,a,M8,20,0x455a14ed)a=GG(a,b,c,d,M13,5,0xa9e3e905)b=GG(d,a,b,c,M2,9,0xfcefa3f8)c=GG(c,d,a,b,M7,14,0x676f02d9)d=GG(b,c,d,a,M12,20,0x8d2a4c8a)
​第三轮a=HH(a,b,c,d,M5,4,0xfffa3942)b=HH(d,a,b,c,M8,11,0x8771f681)c=HH(c,d,a,b,M11,16,0x6d9d6122)d=HH(b,c,d,a,M14,23,0xfde5380c)a=HH(a,b,c,d,M1,4,0xa4beea44)b=HH(d,a,b,c,M4,11,0x4bdecfa9)c=HH(c,d,a,b,M7,16,0xf6bb4b60)d=HH(b,c,d,a,M10,23,0xbebfbc70)a=HH(a,b,c,d,M13,4,0x289b7ec6)b=HH(d,a,b,c,M0,11,0xeaa127fa)c=HH(c,d,a,b,M3,16,0xd4ef3085)d=HH(b,c,d,a,M6,23,0x04881d05)a=HH(a,b,c,d,M9,4,0xd9d4d039)b=HH(d,a,b,c,M12,11,0xe6db99e5)c=HH(c,d,a,b,M15,16,0x1fa27cf8)d=HH(b,c,d,a,M2,23,0xc4ac5665)
​第四轮a=II(a,b,c,d,M0,6,0xf4292244)b=II(d,a,b,c,M7,10,0x432aff97)c=II(c,d,a,b,M14,15,0xab9423a7)d=II(b,c,d,a,M5,21,0xfc93a039)a=II(a,b,c,d,M12,6,0x655b59c3)b=II(d,a,b,c,M3,10,0x8f0ccc92)c=II(c,d,a,b,M10,15,0xffeff47d)d=II(b,c,d,a,M1,21,0x85845dd1)a=II(a,b,c,d,M8,6,0x6fa87e4f)b=II(d,a,b,c,M15,10,0xfe2ce6e0)c=II(c,d,a,b,M6,15,0xa3014314)d=II(b,c,d,a,M13,21,0x4e0811a1)a=II(a,b,c,d,M4,6,0xf7537e82)b=II(d,a,b,c,M11,10,0xbd3af235)c=II(c,d,a,b,M2,15,0x2ad7d2bb)d=II(b,c,d,a,M9,21,0xeb86d391)

5)每轮循环后,将A,B,C,D分别加上a,b,c,d,然后进入下一循环。

八、java代码如下

public class MD5 {
​static final String hexs[]={"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};//标准的幻数private static final long A=0x67452301L;private static final long B=0xefcdab89L;private static final long C=0x98badcfeL;private static final long D=0x10325476L;
​
​//下面这些S11-S44实际上是一个4*4的矩阵,在四轮循环运算中用到static final int S11 = 7;static final int S12 = 12;static final int S13 = 17;static final int S14 = 22;
​static final int S21 = 5;static final int S22 = 9;static final int S23 = 14;static final int S24 = 20;
​static final int S31 = 4;static final int S32 = 11;static final int S33 = 16;static final int S34 = 23;
​static final int S41 = 6;static final int S42 = 10;static final int S43 = 15;static final int S44 = 21;
​//java不支持无符号的基本数据(unsigned)private long [] result={A,B,C,D};//存储hash结果,共4×32=128位,初始化值为(幻数的级联)
​public static void main(String []args){MD5 md=new MD5();System.out.println("md5(abc)="+md.digest("abc"));}
​private String digest(String inputStr){byte [] inputBytes=inputStr.getBytes();int byteLen=inputBytes.length;//长度(字节)int groupCount=0;//完整分组的个数groupCount=byteLen/64;//每组512位(64字节)long []groups=null;//每个小组(64字节)再细分后的16个小组(4字节)
​//处理每一个完整 分组for(int step=0;step<groupCount;step++){groups=divGroup(inputBytes,step*64);trans(groups);//处理分组,核心算法}
​//处理完整分组后的尾巴int rest=byteLen%64;//512位分组后的余数byte [] tempBytes=new byte[64];if(rest<=56){for(int i=0;i<rest;i++)tempBytes[i]=inputBytes[byteLen-rest+i];if(rest<56){tempBytes[rest]=(byte)(1<<7);for(int i=1;i<56-rest;i++)tempBytes[rest+i]=0;}long len=(long)(byteLen<<3);for(int i=0;i<8;i++){tempBytes[56+i]=(byte)(len&0xFFL);len=len>>8;}groups=divGroup(tempBytes,0);trans(groups);//处理分组}else{for(int i=0;i<rest;i++)tempBytes[i]=inputBytes[byteLen-rest+i];tempBytes[rest]=(byte)(1<<7);for(int i=rest+1;i<64;i++)tempBytes[i]=0;groups=divGroup(tempBytes,0);trans(groups);//处理分组
​for(int i=0;i<56;i++)tempBytes[i]=0;long len=(long)(byteLen<<3);for(int i=0;i<8;i++){tempBytes[56+i]=(byte)(len&0xFFL);len=len>>8;}groups=divGroup(tempBytes,0);trans(groups);//处理分组}
​//将Hash值转换成十六进制的字符串String resStr="";long temp=0;for(int i=0;i<4;i++){for(int j=0;j<4;j++){temp=result[i]&0x0FL;String a=hexs[(int)(temp)];result[i]=result[i]>>4;temp=result[i]&0x0FL;resStr+=hexs[(int)(temp)]+a;result[i]=result[i]>>4;}}return resStr;}
​/*** 从inputBytes的index开始取512位,作为新的分组* 将每一个512位的分组再细分成16个小组,每个小组64位(8个字节)* @param inputBytes* @param index* @return*/private static long[] divGroup(byte[] inputBytes,int index){long [] temp=new long[16];for(int i=0;i<16;i++){temp[i]=b2iu(inputBytes[4*i+index])|(b2iu(inputBytes[4*i+1+index]))<<8|(b2iu(inputBytes[4*i+2+index]))<<16|(b2iu(inputBytes[4*i+3+index]))<<24;}return temp;}
​/*** 这时不存在符号位(符号位存储不再是代表正负),所以需要处理一下* @param b* @return*/public static long b2iu(byte b){return b < 0 ? b & 0x7F + 128 : b;}
​/*** 主要的操作,四轮循环* @param groups[]--每一个分组512位(64字节)*/private void trans(long[] groups) {long a = result[0], b = result[1], c = result[2], d = result[3];/*第一轮*/a = FF(a, b, c, d, groups[0], S11, 0xd76aa478L); /* 1 */d = FF(d, a, b, c, groups[1], S12, 0xe8c7b756L); /* 2 */c = FF(c, d, a, b, groups[2], S13, 0x242070dbL); /* 3 */b = FF(b, c, d, a, groups[3], S14, 0xc1bdceeeL); /* 4 */a = FF(a, b, c, d, groups[4], S11, 0xf57c0fafL); /* 5 */d = FF(d, a, b, c, groups[5], S12, 0x4787c62aL); /* 6 */c = FF(c, d, a, b, groups[6], S13, 0xa8304613L); /* 7 */b = FF(b, c, d, a, groups[7], S14, 0xfd469501L); /* 8 */a = FF(a, b, c, d, groups[8], S11, 0x698098d8L); /* 9 */d = FF(d, a, b, c, groups[9], S12, 0x8b44f7afL); /* 10 */c = FF(c, d, a, b, groups[10], S13, 0xffff5bb1L); /* 11 */b = FF(b, c, d, a, groups[11], S14, 0x895cd7beL); /* 12 */a = FF(a, b, c, d, groups[12], S11, 0x6b901122L); /* 13 */d = FF(d, a, b, c, groups[13], S12, 0xfd987193L); /* 14 */c = FF(c, d, a, b, groups[14], S13, 0xa679438eL); /* 15 */b = FF(b, c, d, a, groups[15], S14, 0x49b40821L); /* 16 */
​/*第二轮*/a = GG(a, b, c, d, groups[1], S21, 0xf61e2562L); /* 17 */d = GG(d, a, b, c, groups[6], S22, 0xc040b340L); /* 18 */c = GG(c, d, a, b, groups[11], S23, 0x265e5a51L); /* 19 */b = GG(b, c, d, a, groups[0], S24, 0xe9b6c7aaL); /* 20 */a = GG(a, b, c, d, groups[5], S21, 0xd62f105dL); /* 21 */d = GG(d, a, b, c, groups[10], S22, 0x2441453L); /* 22 */c = GG(c, d, a, b, groups[15], S23, 0xd8a1e681L); /* 23 */b = GG(b, c, d, a, groups[4], S24, 0xe7d3fbc8L); /* 24 */a = GG(a, b, c, d, groups[9], S21, 0x21e1cde6L); /* 25 */d = GG(d, a, b, c, groups[14], S22, 0xc33707d6L); /* 26 */c = GG(c, d, a, b, groups[3], S23, 0xf4d50d87L); /* 27 */b = GG(b, c, d, a, groups[8], S24, 0x455a14edL); /* 28 */a = GG(a, b, c, d, groups[13], S21, 0xa9e3e905L); /* 29 */d = GG(d, a, b, c, groups[2], S22, 0xfcefa3f8L); /* 30 */c = GG(c, d, a, b, groups[7], S23, 0x676f02d9L); /* 31 */b = GG(b, c, d, a, groups[12], S24, 0x8d2a4c8aL); /* 32 */
​/*第三轮*/a = HH(a, b, c, d, groups[5], S31, 0xfffa3942L); /* 33 */d = HH(d, a, b, c, groups[8], S32, 0x8771f681L); /* 34 */c = HH(c, d, a, b, groups[11], S33, 0x6d9d6122L); /* 35 */b = HH(b, c, d, a, groups[14], S34, 0xfde5380cL); /* 36 */a = HH(a, b, c, d, groups[1], S31, 0xa4beea44L); /* 37 */d = HH(d, a, b, c, groups[4], S32, 0x4bdecfa9L); /* 38 */c = HH(c, d, a, b, groups[7], S33, 0xf6bb4b60L); /* 39 */b = HH(b, c, d, a, groups[10], S34, 0xbebfbc70L); /* 40 */a = HH(a, b, c, d, groups[13], S31, 0x289b7ec6L); /* 41 */d = HH(d, a, b, c, groups[0], S32, 0xeaa127faL); /* 42 */c = HH(c, d, a, b, groups[3], S33, 0xd4ef3085L); /* 43 */b = HH(b, c, d, a, groups[6], S34, 0x4881d05L); /* 44 */a = HH(a, b, c, d, groups[9], S31, 0xd9d4d039L); /* 45 */d = HH(d, a, b, c, groups[12], S32, 0xe6db99e5L); /* 46 */c = HH(c, d, a, b, groups[15], S33, 0x1fa27cf8L); /* 47 */b = HH(b, c, d, a, groups[2], S34, 0xc4ac5665L); /* 48 */
​/*第四轮*/a = II(a, b, c, d, groups[0], S41, 0xf4292244L); /* 49 */d = II(d, a, b, c, groups[7], S42, 0x432aff97L); /* 50 */c = II(c, d, a, b, groups[14], S43, 0xab9423a7L); /* 51 */b = II(b, c, d, a, groups[5], S44, 0xfc93a039L); /* 52 */a = II(a, b, c, d, groups[12], S41, 0x655b59c3L); /* 53 */d = II(d, a, b, c, groups[3], S42, 0x8f0ccc92L); /* 54 */c = II(c, d, a, b, groups[10], S43, 0xffeff47dL); /* 55 */b = II(b, c, d, a, groups[1], S44, 0x85845dd1L); /* 56 */a = II(a, b, c, d, groups[8], S41, 0x6fa87e4fL); /* 57 */d = II(d, a, b, c, groups[15], S42, 0xfe2ce6e0L); /* 58 */c = II(c, d, a, b, groups[6], S43, 0xa3014314L); /* 59 */b = II(b, c, d, a, groups[13], S44, 0x4e0811a1L); /* 60 */a = II(a, b, c, d, groups[4], S41, 0xf7537e82L); /* 61 */d = II(d, a, b, c, groups[11], S42, 0xbd3af235L); /* 62 */c = II(c, d, a, b, groups[2], S43, 0x2ad7d2bbL); /* 63 */b = II(b, c, d, a, groups[9], S44, 0xeb86d391L); /* 64 */
​/*加入到之前计算的结果当中*/result[0] += a;result[1] += b;result[2] += c;result[3] += d;result[0]=result[0]&0xFFFFFFFFL;result[1]=result[1]&0xFFFFFFFFL;result[2]=result[2]&0xFFFFFFFFL;result[3]=result[3]&0xFFFFFFFFL;}
​/*** 下面是处理要用到的线性函数*/private static long F(long x, long y, long z) {return (x & y) | ((~x) & z);}
​private static long G(long x, long y, long z) {return (x & z) | (y & (~z));}
​private static long H(long x, long y, long z) {return x ^ y ^ z;}
​private static long I(long x, long y, long z) {return y ^ (x | (~z));}
​private static long FF(long a, long b, long c, long d, long x, long s,long ac) {a += (F(b, c, d)&0xFFFFFFFFL) + x + ac;a = ((a&0xFFFFFFFFL)<< s) | ((a&0xFFFFFFFFL) >>> (32 - s));a += b;return (a&0xFFFFFFFFL);}
​private static long GG(long a, long b, long c, long d, long x, long s,long ac) {a += (G(b, c, d)&0xFFFFFFFFL) + x + ac;a = ((a&0xFFFFFFFFL) << s) | ((a&0xFFFFFFFFL) >>> (32 - s));a += b;return (a&0xFFFFFFFFL);}
​private static long HH(long a, long b, long c, long d, long x, long s,long ac) {a += (H(b, c, d)&0xFFFFFFFFL) + x + ac;a = ((a&0xFFFFFFFFL) << s) | ((a&0xFFFFFFFFL) >>> (32 - s));a += b;return (a&0xFFFFFFFFL);}
​private static long II(long a, long b, long c, long d, long x, long s,long ac) {a += (I(b, c, d)&0xFFFFFFFFL) + x + ac;a = ((a&0xFFFFFFFFL) << s) | ((a&0xFFFFFFFFL) >>> (32 - s));a += b;return (a&0xFFFFFFFFL);}
}
​

MD5散列算法原理及实现相关推荐

  1. Shiro框架:Shiro简介、登陆认证入门程序、认证执行流程、使用自定义Realm进行登陆认证、Shiro的MD5散列算法

    一.Shiro介绍: 1.什么是shiro: (1)shiro是apache的一个开源框架,是一个权限管理的框架,实现用户认证.用户授权. (2)spring中有spring security,是一个 ...

  2. url散列算法原理_如何列出与网站相关的所有URL

    url散列算法原理 by Ty Irvine 由Ty Irvine 如何列出与网站相关的所有URL (How to List Out All URLs Associated With a Websit ...

  3. java散列算法_Java sha1散列算法原理及代码实例

    直接调用HashKit.sha1(String str)方法就可以了,,返回的是16进制的字符串长度是40, 也就是用md.digest()方法解析出来的字节数是160字节长度. 而MD5散列算法生成 ...

  4. MD5(单向散列算法)原理分析

    注:本文章转载于网络. MD5(单向散列算法)的全称是Message-Digest Algorithm 5(信息-摘要算法),经MD2.MD3和MD4发展而来.MD5算法的使用不需要支付任何版权费用. ...

  5. SHA1/MD5散列算法实现(C语言)

    一.实验目的   通过实际编程了解MD5算法的加密和解密过程,加深对Hash算法的认识.   二.实验原理  Hash函数是将任意长的数字串转换成一个较短的定长输出数字串的函数,输出的结果称为Hash ...

  6. 密码加密解密(七)——MD5散列算法实现

    Hash函数是将任意长的数字串转换成一个较短的定长输出数字串的函数,输出的结果称为Hash值.Hash函数具有如下特点: (1)快速性:对于任意一个输入值x,由Hash函数H(x),计算Hash值y, ...

  7. 【计算机网络】网络安全 : 报文鉴别 ( 密码散列函数 | 报文摘要算法 MD5 | 安全散列算法 SHA-1 | MAC 报文鉴别码 )

    文章目录 一.报文鉴别 二.鉴别分类 三.报文鉴别 四.密码散列函数 五.MD5 算法 六.SHA-1 安全散列算法 七.MAC 报文鉴别码 一.报文鉴别 计算机网络安全措施 : ① 针对被动攻击 ( ...

  8. java怎么sha散列算法_Java sha1散列算法的原理解析

    Java sha1散列算法的原理解析 发布时间:2020-10-31 00:01:59 来源:亿速云 阅读:101 作者:Leah 今天就跟大家聊聊有关Java sha1散列算法的原理解析,可能很多人 ...

  9. 散列算法比较:MD5、SHA1、SHA256有哪些区别

    在信息安全领域,经常会用到MD5.SHA1.SHA256算法.这三种算法都属于散列算法,或者叫作哈希算法.它们具有输入任意长度,输出长度固定,以及单向性(无法根据散列值还原出消息)的特点.那么,MD5 ...

最新文章

  1. 刚刚做了个chrome浏览器 博客园转载插件,欢迎试用,多提意见!
  2. centos java 指令_Centos 命令方式下载JDK1.7
  3. 给ModalPopupExtender控件添加弹出关闭等事件
  4. LeetCode 55. 跳跃游戏 中等难度
  5. 思维、视角、设计丨产品经理的三板斧
  6. English trip -- VC(情景课)5 Around Town
  7. Microsoft Enterprise Library 5.0 系列(二) Cryptography Application Block (高级)
  8. UDS协议的项目应用
  9. 九大知识领域与五大过程组
  10. 如何设置QQ和微信截图快捷键
  11. 路由器芯片和服务器,软路由就是软路由,还是回归它本该有的身份吧。一个越折腾越迷茫者的经历...
  12. Mysql查询时间段内数据,并处理相同日期或同类型的数据
  13. 静态变量static(C)
  14. template模板
  15. 《西瓜书》-11.特征选择与稀疏学习
  16. 我爱Flask之URL和Flask视图介绍
  17. Alfred Workflow 豆瓣插件开发
  18. Idea导包自动去除Import xxx.*里的星号
  19. 数据预处理第6讲:正态变换
  20. JavaScript(三十九)——鼠标滚轮、滚动窗口制作

热门文章

  1. vscode远程debug mysql
  2. 【Springboot 入门培训】# 17 WebJars + BootStrap5 常用JS组件应用
  3. 电脑一般预装access吗_win7自带access数据库
  4. 如何在matlab中打开一个.dat文件,并载入数据
  5. 抛开激光雷达,像Uber那样众筹也能造出自动驾驶高清地图
  6. jvm 参数 -verbose:gc 和 -XX:+PrintGC 的区别?
  7. SwiftUI AppClip 之01 开发AppClip应知应会
  8. react router
  9. RDIFramework.NET代码生成器全新V3.5版本发布-重大升级
  10. GreenDao的使用;