六.热修复
文章目录
- 前言
- 什么是热修复?
- 如何进行热修复?
- 热修复需要解决的问题
- 1.Android常用的热修复解决方案
- 2.ClassLoader类加载机制
- 2.1 Android类加载器
- 2.2 双亲委托机制
- 2.3 类查找流程
- 3.插桩式热修复运行期修复落地
- 3.1 什么是字节码插桩?
- 3.2 ASM
- 3.3 热修复落地实现
- 3.4 热修复存在的版本兼容性问题
- 4.自动化补丁方案
- 4.1 自定义插件
- 4.2 判断哪些文件需要打包进补丁包
- 4.3 代码混淆问题的解决方案
- 面试题
- 1.说一说热修复的方案有哪些,并说说他们的区别?
- 2.热修复用到了哪些技术?说一说双亲委托机制?说说如何通过类加载和反射实现热修复的?
前言
什么是热修复?
应用在上线后出现bug需要及时修复,不用再发布新的安装包,只需要发布补丁包,在用户无感知情况下修复掉bug。
如何进行热修复?
- 服务端:补丁包管理
- 用户端:执行热修复
- 开发端:生成补丁包
热修复需要解决的问题
开发端
- 补丁包是什么?
补丁包就是修复了bug的dex文件和jar包。
- 如何生成补丁包?
将修复了bug的java文件通过javac编译成class文件,然后通过dx工具将class文件打包成dex文件或者是jar包,这就是补丁包。
- 开启混淆后呢?
开启混淆后,类名和包名会发生改变,为了保证混淆产生的包名和类名不变,我们需要通过mapping文件来解决。
- 对比改动自动生成补丁包(gradle)?
gradle生成热修复的插件方式,适合的场景是问题较小、编写的代码不多的情况。
用户端 - 什么时候执行热修复?
越早越好,要在加载bug类之前执行,完成热修复。
- 如果应用已经在运行,并且这个类已经加载过了,还能进行热修复吗?
不能进行热修复了,因为如果这个类已经加载过了,就会存在于缓存中,不会再从dex文件中去查找这个类的class文件。
- 热修复完成后,补丁包dex文件和原来的dex文件,是否需要删除?
不能删除。
如果补丁包dex文件被删除,缓存又被清空的情况下,又会执行原来没有bug的流程;
如果原来的dex文件被删除,那么dex文件中包含的所有逻辑代码都被会删除,我们的app将无法运行。 - 怎么执行热修复(使用补丁包)?
将补丁包下载下来,然后通过反射技术,将补丁包通过DexPathList中的makeElement方法生成修复bug的Element[]数组,再通过反射技术,获取到存在bug的Element[]数组,将修复了bug的Element[]和存在bug的Element[]拼接成一个新的数组,在进行类加载的时候,就会优先加载修复了bug的dex文件中的class,从而实现热修复。
- Android版本兼容性问题
- AndroidN(Android7.0)热点代码存在JIT即时编译,解决方案是自定义一个PathClassLoader,去掉缓存代码逻辑,就可以先加载修复了bug的代码。
- Dalvik(Android5.0)虚拟机存在不同的dex文件使用兼容性问题,如果没有直接使用别的dex文件中的类,该dex文件中这个类就会被打上标签标识,从而无法加载修复了bug的dex文件。
解决方案是,通过字节码插桩技术,对存在bug的dex文件中的字节码文件进行代码编写,引用别的dex文件中的类,消除标签标识,从而可以通过热修复加载修复bug的dex文件中的class,完成热修复。 - 不同的版本,一些方法的方法名和参数个数和参数类型不尽相同,所以也需要进行适配。
1.Android常用的热修复解决方案
Tinker(腾讯) | QZone(腾讯) | AndFix(阿里) | Robust(美团) | |
---|---|---|---|---|
类替换 | Y | Y | N | N |
so替换 | Y | Y | N | N |
资源替换 | Y | Y | N | N |
全平台生效 | Y | Y | Y | Y |
及时生效 | N | N | Y | Y |
性能损耗 | 较小 | 较大 | 较小 | 较小 |
补丁包大小 | 较小 | 较大 | 一般 | 一般 |
开发透明 | Y | Y | N | N |
复杂度 | 较小 | 较小 | 复杂 | 复杂 |
gradle支持 | Y | N | N | N |
rom体积 | 较大 | 较小 | 较小 | 较小 |
成功率 | 较高 | 较高 | 一般 | 最高 |
AndFix(补丁包是.dex文件
)
是由阿里开发的热修复框架,但是已经废弃,很多年没有维护了。
他的热修复实现原理如下:
在native层动态替换Java层的方法,通过native层hook Java层代码。
Robust(补丁包是.dex文件
)
由美团开发,目前抖音就采用这种热修复方案,可以及时生效。
他的热修复实现原理如下:
利用gradle插件,在我们写的类中,编译时生成一个静态接口变量,在方法中会对这个接口变量进行判断。
如果我们需要进行热修复,就写一个接口的实现类,然后通过类加载和反射,找到我们写的类并创建实例,赋值给要修复bug的类中的静态接口变量即可,当我们的方法中判断到这个接口变量非空时,就会去执行实现类中的业务逻辑,从而达到了修复bug的目的,并且是及时生效的。
Tinker(补丁包是差分包
)
由腾讯开发,目前微信就采用了这中热修复方案,优点是可以可以修复类、so包和资源文件。
他的热修复实现原理如下:
由有bug的apk文件和修复bug的apk文件共同生成一个差分包patch,这个patch文件就是我们的补丁包,下载这个差分包和有bug的apk,就可以生成修复bug的apk。如果没有资源文件修复,那么就会生成的就是一个.dex文件,可以通过类加载和反射技术,加载修复bug的类,从而实现热修复。
2.ClassLoader类加载机制
2.1 Android类加载器
ClassLoader继承关系:
- ClassLoader
- BootClassLoader
用于加载Android Framework层class文件
- BaseDexClassLoader
包含了DexPathList{dexElement[]、findClass()、makeElement[]}
- PathClassLoader
额外提供的动态类加载器
加载指定的dex、以及jar、zip、apk中的classes.dex
- DexClassLoader
Android应用程序类加载器
加载指定的dex、以及jar、zip、apk中的classes.dex
- PathClassLoader
- BootClassLoader
使用举例:
public class MyApplication extends Application {private final String TAG = MyApplication.class.getSimpleName();@Overridepublic void onCreate() {super.onCreate();//获取使用ClassLoader的场景ClassLoader classLoader1 = TKActivity.class.getClassLoader();ClassLoader classLoader2 = AppCompatActivity.class.getClassLoader();ClassLoader classLoader3 = Application.class.getClassLoader();ClassLoader classLoader4 = getClassLoader();Log.e(TAG, "classLoader1:" + classLoader1.toString());Log.e(TAG, "classLoader2:" + classLoader2.toString());Log.e(TAG, "classLoader3:" + classLoader3.toString());Log.e(TAG, "classLoader4:" + classLoader4.toString());}
}打印日志:
E/MyApplication: classLoader1:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.tangkun.xiangxuestudy-4SJ9lfa-q8--dmTGHgBPbA==/base.apk"],nativeLibraryDirectories=[/data/app/com.tangkun.xiangxuestudy-4SJ9lfa-q8--dmTGHgBPbA==/lib/arm64, /system/lib64, /product/lib64]]]
E/MyApplication: classLoader2:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.tangkun.xiangxuestudy-4SJ9lfa-q8--dmTGHgBPbA==/base.apk"],nativeLibraryDirectories=[/data/app/com.tangkun.xiangxuestudy-4SJ9lfa-q8--dmTGHgBPbA==/lib/arm64, /system/lib64, /product/lib64]]]
E/MyApplication: classLoader3:java.lang.BootClassLoader@4c33cb2
E/MyApplication: classLoader4:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.tangkun.xiangxuestudy-4SJ9lfa-q8--dmTGHgBPbA==/base.apk"],nativeLibraryDirectories=[/data/app/com.tangkun.xiangxuestudy-4SJ9lfa-q8--dmTGHgBPbA==/lib/arm64, /system/lib64, /product/lib64]]]
2.2 双亲委托机制
当我们在页面中使用PathClassLoader去加载一个类的全路径时,代码如下:
getClassLoader().loadClass("com.tangkun.study.MainActivity");
此时,getClassLoader方法返回的是PathClassLoader,但是该类中没有loadClass方法;所以我们从他的父类BaseDexClassLoader中去寻找这个方法,这个类中同样没有loadClas方法;所以我们再从他的父类ClassLoader中去查找这个方法,代码如下:
//ClassLoader.java
//这一段就是双亲委托机制代码,要找到某个class文件,先委托给父类parent去查找,
//如果父类没有查找到,则通过子类去查找class文件,并且找到的class文件会被存放在缓存中
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{//若class被加载过了,会存到缓存中。所以先从缓存中查找是否存在class//缓存中的class是从native层中查找得来Class<?> c = findLoadedClass(name);if (c == null) {try {//parent是BootClassLoaderif (parent != null) {//调用BootClassLoader.loadClass去查找class;//这里会调用到native层的findLoadedClass方法,从缓存中查找classc = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {}//如果父类无法加载到class,则从PathClassLoader中去查找classif (c == null) {//findClass是一个抽象方法,在子类BaseDexClassLoader中实现了该方法c = findClass(name);}}return c;
}//BaseDexClassLoader.java
private final DexPathList pathList;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {List<Throwable> suppressedExceptions = new ArrayList<Throwable>();//pathList是传入的DexPathList对象,所以我们从DexPathList类中查看findClass方法Class c = pathList.findClass(name, suppressedExceptions);//省略非核心代码return c;
}//DexPathList.java
//一个dex文件对应dexElements数组中一个元素Element;
//因为我们打出来的apk包中可能存在多个dex文件,所以是数组。
//DexPathList包含了内部类Element
private Element[] dexElements;
public Class<?> findClass(String name, List<Throwable> suppressed) {//从dex文件数组中遍历每个dex文件for (Element element : dexElements) {//Android中,多个java文件会被编译成多个.class文件,多个class文件会被打包成一个dex文件//一个dex文件中包含多个class文件,所以从class文件集合中去查找某一个确定的class文件//最后会调用到native层去查找这个classClass<?> clazz = element.findClass(name, definingContext, suppressed);if (clazz != null) {return clazz;}}//省略非核心代码return null;
}
2.3 类查找流程
class查找流程:
- 先从缓存中去查找class是否存在;
- 如果缓存中不存在,则委托给父类,由父类查找class是否存在;
- 若父类无法查找到class,则由当前子类去查到该类。
3.插桩式热修复运行期修复落地
制作补丁包流程:
1、把Bug修复掉后,先生成类的class文件。
2、执行命令:
dx --dex --output=patch.jar com/tangkun/study/Utils.class
应用补丁包: patchElment(补丁包生成的) + oldElement(APK原有的) 赋值给oldElement
1、获取程序的PathClassLoader对象
2、反射获得PathClassLoader父类BaseDexClassLoader的pathList对象
3、反射获取pathList的dexElements对象 (oldElement)
4、把补丁包变成Element数组:patchElement(反射执行makePathElements)
5、合并patchElement+oldElement = newElement (Array.newInstance)
6、反射把oldElement赋值成newElement
makePathElements参数:
1、补丁包:List[new File(“/sdcard/patch.jar”)]
2、optimizedDirectory 传一个私有目录就行比如:context.getCacheDir()
3、ArrayList suppressedExceptions = new ArrayList();
3.1 什么是字节码插桩?
所谓字节码插桩,就是在字节码文件中进行代码编写,而我们的class文件就属于字节码文件。
我们编写的java文件在经过编译后,会生成class文件,class文件是2进制格式的,0101这种格式,我们肯定是无法直接在这种格式的文件上进行编码的。所以我们需要借助于别的工具来进行字节码文件代码编写的,比如:第三方框架ASM
。
如果直接对字节码操作是什么样的一个流程呢?
- 通过文件输入流将字节码文件读到字节数组(
byte[]
)中; - 对字节数组(
byte[]
)中的数据进行修改; - 通过文件输出流将字节数组(
byte[]
)写回字节码文件。
3.2 ASM
概念:ASM是用来操作字节码(.java文件生成的.class文件
)的框架,按照class文件的格式,解析、修改、生成class,可以动态生成类或者增强现有类的功能。
类似于Gson框架,是用来操作json格式字符串的。
怎么使用ASM?
首先需要在build.gradle依赖ASM的的库,然后让我们需要使用字节码插桩的地方(比如:类、方法、属性),添加上ASM注解并带上插桩的实现类,然后在插桩的实现类中完成相应的功能。
开始和结束的地方,仍然还是需要使用文件输入输出流,用于读取和写入字节码文件;中间就是要我们自己实现借助于ASM来完成插桩代码的编写工作;在手写插桩代码之前,我们可以将需要实现的功能写入我们的java文件中,然后编译生成class文件,然后借助javap命令查看class文件中的代码(也可以通过ASM Bytecode Viewer插件来查看,我的AS由于版本原因,即使安装了这个插件还是看不了java文件的字节码代码;但是可以通过编译后生成的class文件,然后再利用这个插件查看,嘿嘿(*^▽^*)
),然后借助于这些代码和ASM的功能,来完成插桩代码的编写。
然后我们就可以得到修复bug后的class文件,然后通过SDK的dx工具,通过执行命令行的命令,可以将class文件转换成dex文件或者是jar文件,我们就直接转换成dex文件,给后面热修复使用。
3.3 热修复落地实现
1、获取程序的PathClassLoader对象
2、反射获得PathClassLoader父类BaseDexClassLoader的pathList对象
3、反射获取pathList的dexElements对象 (oldElement)
4、把补丁包变成Element数组:patchElement(反射执行makePathElements)
5、合并patchElement+oldElement = newElement (Array.newInstance)
6、反射把oldElement赋值成newElement
3.4 热修复存在的版本兼容性问题
AndroidN(也就是Andorid7.0)
会进行JIT即时编译编译,会将app经常使用的一些代码也叫做热点代码,存储到profile文件中,在手机空闲的时候,会执行这些热点代码,从而产生了缓存。
因此,会导致我们热修复失败,因为即使我们通过Hook技术将修复了bug的class放在了有bug的class数组前面;但是由于缓存的原因,不会访问我们这个修复了bug的Element数组;而直接从缓存中获取class进行加载,而这个缓存中的class是存在bug的。所以,热修复无效。
如何解决上面这个问题呢?
采取运行时替换掉PathClassLoader方案,重新创建一个自定义的PathClassLoader,但是不重写缓存的代码,虽然会存在一些性能的影响,但是相较于有bug,这点性能的损耗还是值得的;自定义的PathClassLoader由于没有重写缓存逻辑,因此我们在进行热修复后,会优先执行修复了bug的class,从而问题得到解决。
Android5.0 Dalvik虚拟机
5.0及以下使用的虚拟机是Darvik,存在不同的dex文件之间使用问题,如果有bug的dex文件的类中没有直接引用过别的dex文件中的类时(
需要对别的dex文件中类进行导包才行,反射不可以
),这个类就会被标记成isVerified;在有这个标记之后,有bug的dex文件就无法使用别的dex文件中的类。
如何解决上面的问题呢?
采用字节码插桩技术,在有bug的类编译成的class文件中,通过字节码插桩技术,将另外一个dex文件中的类通过插桩技术写入并且导包,从而将有bug的类取消isVerified标记。我们就可以通过热修复,将修复bug的dex文件中的class添加到有bug的class数组前面,从而优先执行我们修复bug的class中的代码。
扩展知识:
不同版本还存在方法名称、方法中参数个数以及类型的差异,所以,我们还需要对这些情况做版本适配。
比如:我们DexPathList类中的makePathElements方法,是用来生成Element[]数组的。我们通过热修复,将修复bug的dex文件添加到这个数组的前面,达到优先执行修复bug的目的。但是这个方法在不同的版本中存在一定的差异,举例:
Android9.0.0: makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions)
Android5.1.0: makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions)
4.自动化补丁方案
4.1 自定义插件
自己开发插件有3种方式:
- Build script脚本
- 把插件写在build.gradle中,一般用于简单的逻辑,只在该build.grale中文件可见。
- buildSrc目录
- 将插件源代码放在buildSrc/src/main/groovy/中,只在该项目中可见。
- 实现Plugin接口,并重写apply方法。
- 独立项目
- 一个独立的Java项目/模块,可以将文件发布到仓库(Jcenter),使其他项目方便引入。
4.2 判断哪些文件需要打包进补丁包
可以采用缓存机制,将之前的class文件和这个class文件生成的md5值存起来;
- 如果缓存中没有这个class文件,则表示我们的这个class文件是新建的,所以需要打包进入补丁包
- 如果缓存中有这个class文件,那么就去比较缓存中class文件的md5值与我们重新生成的class文件的md5值是否一致,不一致表示新的class文件有代码更改,同样需要打包进入补丁包;
- 如果缓存文件和新生成的class文件的md5值相同,则无需打包进入补丁包。
扩展知识:
可以通过Java代码,执行命令行,然后进行打包操作:
- 把.java编译为.class
Runtime.getRuntime().exe("javac -bootclasspath android.jar路径 java源码和R.java路径");
- 把.class编译为.dex
Runtime.getRuntime().exe("dx --dex classes路径");
4.3 代码混淆问题的解决方案
需要保证这一次混淆后的类名和上一次混淆后的类名相同,通过-applyMapping配置
代码混淆的时候会生成一份mapping文件,通过将mapping文件缓存下来,然后找到没有混淆的文件名、方法名等,然后进行处理。
面试题
1.说一说热修复的方案有哪些,并说说他们的区别?
Tinker(腾讯) | QZone(腾讯) | AndFix(阿里) | Robust(美团) | |
---|---|---|---|---|
类替换 | Y | Y | N | N |
so替换 | Y | Y | N | N |
资源替换 | Y | Y | N | N |
全平台生效 | Y | Y | Y | Y |
及时生效 | N | N | Y | Y |
性能损耗 | 较小 | 较大 | 较小 | 较小 |
补丁包大小 | 较小 | 较大 | 一搬 | 一搬 |
开发透明 | Y | Y | N | N |
复杂度 | 较小 | 较小 | 复杂 | 复杂 |
gradle支持 | Y | N | N | N |
rom体积 | 较大 | 较小 | 较小 | 较小 |
成功率 | 较高 | 较高 | 一搬 | 最高 |
AndFix(补丁包是.dex文件
)
是由阿里开发的热修复框架,但是已经废弃,很多年没有维护了。
他的热修复实现原理如下:
在native层动态替换Java层的方法,通过native层hook Java层代码。
Robust(补丁包是.dex文件
)
由美团开发,目前抖音就采用这种热修复方案,可以及时生效。
他的热修复实现原理如下:
利用gradle插件,在我们写的类中,编译时生成一个静态接口变量,在方法中会对这个接口变量进行判断。
如果我们需要进行热修复,就写一个接口的实现类,然后通过类加载和反射,找到我们写的类并创建实例,赋值给要修复bug的类中的静态接口变量即可,当我们的方法中判断到这个接口变量非空时,就会去执行实现类中的业务逻辑,从而达到了修复bug的目的,并且是及时生效的。
Tinker(补丁包是差分包
)
由腾讯开发,目前微信就采用了这中热修复方案,优点是可以可以修复类、so包和资源文件。
他的热修复实现原理如下:
由有bug的apk文件和修复bug的apk文件共同生成一个差分包patch,这个patch文件就是我们的补丁包,下载这个差分包和有bug的apk,就可以生成修复bug的apk。如果没有资源文件修复,那么就会生成的就是一个.dex文件,可以通过类加载和反射技术,加载修复bug的类,从而实现热修复。
2.热修复用到了哪些技术?说一说双亲委托机制?说说如何通过类加载和反射实现热修复的?
热修复用到了类加载和反射技术。
双亲委托机制,就是在通过PathClassLoader加载某个类的时候,首先委托给他的父类去加载这个类,当父类没有加载成功时,才由子类去加载这个类。
要实现热修复功能,就需要用到Hook技术。
就是在加载有bug的class之前,先去加载我们修复了bug的class类,即可实现热修复技术。
class类是包含在dex文件中的,我们的app打包成apk的时候,就会生成一个.dex文件。如果我们要进行热修复,就需要生成修复了bug的dex文件,然后利用反射技术,在获取到PathClassLoader的前提下,然后通过反射去获取他的父类BaseDexClassLoader,然后再获取里面的属性(DexPathList)pathList,再获取这个属性中的Element[]数组,这个Element[]数组就是有bug的数组。
数组中的每个Element元素就对应一个dex文件,所以我们只需要再通过反射技术,将修复了bug的dex文件下载下来,然后通过makeElement方法生成修复了bug的Element[]数组。
最终将修复了bug的Element[]数组与有bug的Element[]数组组成一个新的数组,修复了bug的数组元素在前,存在bug的数组元素在后;在类加载加载class的时候,就会先加载到修复了bug的Element数组元素中的class类,从而修复了bug。
相关文章:
六.热修复
文章目录 前言什么是热修复?如何进行热修复?热修复需要解决的问题 1.Android常用的热修复解决方案2.ClassLoader类加载机制2.1 Android类加载器2.2 双亲委托机制2.3 类查找流程 3.插桩式热修复运行期修复落地3.1 什么是字节码插桩?3.2 ASM3.3…...

2000万的行数在2023年仍然是 MySQL 表的有效软限制吗?
谣言 互联网上有传言说我们应该避免在单个 MySQL 表中有超过 2000 万行。否则,表的性能会下降,当它超过软限制时,你会发现 SQL 查询比平时慢得多。这些判断是在多年前使用HDD硬盘存储时做出的。我想知道在2023年对于基于SSD的MySQL数据库来说…...
jvm问题排查
常用工具 命令查询资源信息 top:显示系统整体资源使用情况 vmstat:监控内存和 CPU iostat:监控 IO 使用 netstat:监控网络使用 查看java进程 jps 查看运行时信息 jinfo pid gc工具 jstat: 查看jvm内存信息 GCViewer — 离线分析G…...

【Redis】浅谈Redis-集群(Cluster)
文章目录 前言1、集群实现1.1 创建cluster目录,并将redis.conf复制到该文件夹1.2 复制redis.conf,并进行配置1.3 启动redis,查看启动状态1.4 合成集群1.5 查看集群1.6 集群读写操作 2、SpringBoot整合redis集群2.1 引入包2.2 设置配置2.3 使用…...

Python3实现基于ARIMA模型来预测茅台股票价格趋势
🤵♂️ 个人主页:艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞Ǵ…...

自动化测试selenium环境搭建
自动化测试工具selenium搭建 1. 自动化和selenium基本概念 1) 什么是自动化?为什么要做自动化? 自动化测试能够代替一部分的手工测试,自动化测试能够提高测试的效率。随着项目功能的增加,版本越来越多,版本的回归测试的压力也…...

SaaS系统平台,如何兼顾客户的个性化需求?
在当今数字化的商业环境中,SaaS系统已经成为企业运营的重要组成部分之一。 SaaS系统平台的好处是显而易见的,可以将业务流程数字化,从而帮助企业提高效率并节省成本。 但是,由于每个企业的业务都不尽相同,所以在选择Sa…...

QDir拼接路径解决各种斜杠问题
一般在项目中经常需要组合路径,与其他程序进行相互调用传递消息通信。 经常可能因为多加斜杠、少加斜杠等问题导致很多问题。 为了解决这些问题,我们可以使用QDir来完成路径的拼接,不直接拼接字符串。 QDir的静态方法QDir::cleanPath() 是为了规范化路径名的,在使用QDir组…...

mycat2主从配置实现读写分离
mycat2主从配置实现读写分离 在https://blog.csdn.net/zhangxue_wei/article/details/130840504基础上继续搭建 1.创建mycat数据源,可以在navcat里直接执行 1.1读数据源m1 /* mycat:createDataSource{"dbType":"mysql","idleTimeout&qu…...
如何在Centos7中安装Kubernetes
一、概述 Kubernetes([kubə’netis]),简称K8s,是用8代替名字中间的8个字符“ubernete”而成的缩写,它是一个由Google 开源的全新的分布式容器集群管理系统。 二、准备 IP角色内存192.168.1.130master4G192.168.1.1…...
Stream强化
使用stream求list的对象属性的和 假设有一个Student类,其中有一个属性是score,可以通过以下代码求出List<Student>中score的和: List<Student> students new ArrayList<>(); // 添加学生对象到List中 int sum student…...

第一部分-基础篇-第一章:PSTN与VOIP(下篇)
文章目录 序言上一篇文章:1.6 电路交换与分组交换1.6.1 电路交换1.6.2 分组交换 1.7 VoIP1.8 IMS1.8.1 什么是IMS1.8.2 IMS的特点1.8.3 IMS核心网元(1 ) CSCF(2 ) MGCF(3 ) IM-MGW(5…...

《汇编语言》- 读书笔记 - 第4章-第一个程序
《汇编语言》- 读书笔记 - 第4章-第一个程序 4.1 一个源程序从写出到执行的过程4.2 源程序程序 4.11. 伪指令1.1 segment ends 声明段1.2 end 结束标记1.3 assume 关联 2. 源程序中的“程序”3. 标号4. 程序的结构5. 程序返回6. 语法错误和逻辑错误 4.3 编辑源程序4.4 编译4.5 …...

AI工具 ChatGPT-4 vs Google Bard , PostgreSQL 开发者会pick谁?
在人工智能 (AI) 进步的快节奏世界中,开发人员正在寻找最高效和突破性的解决方案来加快和提高他们的工作质量。对于 PostgreSQL 开发人员来说,选择理想的 AI 支持的工具以最专业的方式解决他们的查询至关重要。 近年来,人工智能工具的普及率…...

【网络】基础知识1
目录 网络发展 独立模式 网络互联 局域网LAN 广域网WAN 什么是协议 初识网络协议 协议分层 OSI七层模型 TCP/IP四层(或五层)模型 OSI和TCP/IP对比 网络传输流程 什么是报头 局域网通信原理 同网段的主机通讯 跨网段的主机通讯 数据包封装…...

chatgpt赋能python:Python倒序range的完整指南
Python倒序range的完整指南 Python是一种高级编程语言,很多人认为它非常容易学习和使用。其中一个非常有用的功能是range()函数,可以生成数字序列。然而,有时候我们需要以相反的顺序生成这个数字序列,这时候倒序range()函数就派上…...
工作笔记!
搭建tomcat Tomcat详细使用教程 tomcat配置用戶名和密碼 tomcat设置外网能访问_tomcat让别人通过网络访问 如何在windows开端口_windows开放端口命令 tomcat进Manager 403 Access Denied You are not authorized to view this page_tomcat报错you are not_ferry_cai 关于依…...
java设计模式之享元设计模式的前世今生
享元设计模式是什么? 享元设计模式是一种结构型设计模式,它的目的是在大规模重复使用相似对象时提高内存利用率和性能。它通过共享对象的公共部分来减少所需要的内存,从而在系统中同时存在更多的对象。 享元设计模式通过将对象分为可共享的内…...
RESTful:理解REST架构风格、RESTful API
一、REST架构风格 REST(英文Representational State Transfer)是一种基于客户端和服务器的架构风格,用于构建可伸缩、可维护的Web服务。REST的核心思想是,将Web应用程序的功能作为资源来表示,使用统一的标识符&#x…...

网络面试题:什么是 TCP/IP?
目录标题 什么是 TCP/IP?1) 网络接口层:2) 网络层:3) 传输层:4) 应用层: 2.数据包3.网络接口层4.网络层1) IP:2)地址解析协议 ARP3)子网 5 传输层1)UDP:2)TCP: 6 应用层运行在TCP协议上的协议:运行在UDP协议上的协议&…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...