(1w字一篇理解透Unsafe类)Java魔法类:Unsafe详解
Java魔法类 Unsafe
- 文章导读:(约12015字,阅读时间大约1小时)
- 1. Unsafe介绍
- 2. Unsafe创建
- 3. Unsafe功能
- 3.1内存操作
- 3.2 内存屏障
- 3.3 对象操作
- 3.4 数组操作
- 3.5 CAS操作
- 3.6 线程调度
- 3.7 Class操作
- 3.8 系统信息
- 4. 总结
JUC源码中的并发工具类出现过很多次 Unsafe类,它
的功能以及使用场景这篇进行介绍。
文章导读:(约12015字,阅读时间大约1小时)
1. Unsafe介绍
Unsafe是位于sun.misc包下的类,提供一些更底层的,访问系统内存资源,和管理系统内存资源的方法,但是因为会访问系统的内存资源 变成和C语言一样的指针,指针的使用是有风险的,所以Unsafe也是有类似的风险,所以在使用的时候需要注意,过度或者不正确的使用可能导致程序出错。
但是,Unsafe类也使得Java增强了底层操作系统资源的能力。
同时,Unsafe提供的功能的实现是依赖于本地方法(Native Method)的,本地方法就是Java中使用其他语言写的方法,本地方法用native修饰,java只声明方法,具体实现由本地方法实现。
使用本地方法的原因:
- 需要使用到Java没有的特性,就得使用本地方法 来用别的语言来实现,比如Java没有什么底层操作系统的能力,所以要想在跨平台的同时还可以由底层控制的能力,就要使用本地方法。
- 其他语言已经实现的功能,可以Java调用使用
- Java在速度上比一些更底层的语言慢,如果程序对时间要求高或者对性能要求高,那么就需要使用更底层的语言。
JUC包的很多并发工具类在实现并发功能的时候,都调用了本地方法,用来提高Java的运行上限,同时为了能更底层的操作操作系统,也会使用本地方法,对于本地方法来说,不同操作系统的实现不太一样,但是使用起来是一样的。
2. Unsafe创建
public final class Unsafe {// 单例对象private static final Unsafe theUnsafe;......private Unsafe() {}@CallerSensitivepublic static Unsafe getUnsafe() {Class var0 = Reflection.getCallerClass();// 仅在引导类加载器`BootstrapClassLoader`加载时才合法if(!VM.isSystemDomainLoader(var0.getClassLoader())) {throw new SecurityException("Unsafe");} else {return theUnsafe;}}
}
Unsafe类是单例实现,可以通过静态getUnsafe方法获取实例。但是有个前提是 在调用getUnsafe方法的时候会对调用者的ClassLoader进行检查,也就是检查类加载器,如果是由Bootstrap classLoader加载的,那么才可以获得实例 如果不是那么就抛出异常SecurityException,所以,只有启动类加载器加载的类才可以调用Unsafe类中的方法,有助于避免被不可信代码调用,(因为这个原因说一在获取Unsafe类的实例的时候 大概率会抛出SecurityException异常,这样就要有别的方法来获取实例,比如下面讲到的反射方法)
那么为什么使用Unsafe类这么有限制?
Unsafe类提供的功能很底层,它可以访问系统资源,操作系统资源,所以存在一些安全风险,
在这个限制的条件下如何来获取Unsafe实例呢
我知道的方法是一个:
通过反射
我们可以通过反射来获得Unsafe类中以及完成实例化的theUnsafe对象
//通过反射获取Unsafe类中已经实例化完成的theUnsafe对象private static Unsafe reflectGetUnsafe() {try {Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);return (Unsafe) field.get(null);} catch (NoSuchFieldException e) {//处理异常e.printStackTrace();return null;//返回内容} catch (IllegalAccessException e) {e.printStackTrace();return null;}}
3. Unsafe功能
3.1内存操作
Java中不能直接操作内存,对象的分配内存和释放都是JVM完成的,在Unsafe中,提供了几个方法可以直接操作内存:
//分配新的本地空间
public native long allocateMemory(long bytes);
//重新调整内存空间的大小
public native long reallocateMemory(long address, long bytes);
//将内存设置为指定值
public native void setMemory(Object o, long offset, long bytes, byte value);
//内存拷贝
public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset,long bytes);
//清除内存
public native void freeMemory(long address);
测试:
public void Test() {Unsafe unsafe = reflectGetUnsafe();int size = 4;long addr = unsafe.allocateMemory(size);long addr1 = unsafe.reallocateMemory(addr,size*2);System.out.println("addr:" + addr);System.out.println("addr1:" + addr1);try {unsafe.setMemory(null,addr,size,(byte) 1);for (int i = 0; i < 2; i++) {//o为哪个对象 addr为对象中的偏移量(offset) o1是另一个对象 l1为拷贝到哪里 l1为拷贝字节//进行了两次拷贝 每次拷贝将内存地址addr开始的四个字节拷贝到add1和add1+size*i(也就是4)的内存unsafe.copyMemory(null, addr, null, addr1 + size * i, 4);}System.out.println(unsafe.getInt(addr1));System.out.println(unsafe.getLong(addr1));} finally {//因为Unsafe的对象不会自动释放 所以要手动释放unsafe.freeMemory(addr);unsafe.freeMemory(addr1);}}
addr: 2433733895744
addr3: 2433733894944
16843009
72340172838076673
使用allocateMemory方法申请4字节长度的内存空间,调用setMemory方法在每个字节中写入byte类型的1,调用getInt方法的时候 取四个字节 就是00000001 00000001 00000001 00000001 十进制就是16843009
然后再代码中调用reallocateMemory 重新分配8字节的内存空间,在循环中 分别拷贝两次 每次拷贝内存地址addr的4个字节 拷贝到addr1和addr1+4的内存空间上。
此外由于这种分配是堆外内存,不能自动回收,所以要再finally中手动使用freeMemory释放。
使用堆外内存的好处:
- **对垃圾回收停顿改善,**因为推外内存不被JVM管理,所以在JVM垃圾回收的时候,可以减少垃圾回收停顿。
- **提高I/O操作性能,**会存在堆内内存到堆外内存的拷贝,所以如果使用堆外内存的话,就可以提高IO操作性能,可以将频繁需要拷贝的内存 或者 生命周期短的内存直接放在堆外,避免在堆内的时候一直拷贝到堆外去。
应用场景:
DirectByBuffer是Java实现堆外内存的一个重要类,在通信过程中做缓冲池,在NIO框架中使用较多,DirectByBuffer的堆外内存创建,使用,释放,都是由Unsafe提供的堆外内存API来实现的。
DirectByBuffer构造函数👇:
DirectByteBuffer(int cap) { // package-privatesuper(-1, 0, cap, cap);boolean pa = VM.isDirectMemoryPageAligned();int ps = Bits.pageSize();long size = Math.max(1L, (long)cap + (pa ? ps : 0));Bits.reserveMemory(size, cap);long base = 0;try {// 分配内存并返回基地址base = unsafe.allocateMemory(size);} catch (OutOfMemoryError x) {Bits.unreserveMemory(size, cap);throw x;}// 内存初始化unsafe.setMemory(base, size, (byte) 0);if (pa && (base % ps != 0)) {// Round up to page boundaryaddress = base + ps - (base & (ps - 1));} else {address = base;}// 跟踪 DirectByteBuffer 对象的垃圾回收,以实现堆外内存释放cleaner = Cleaner.create(this, new Deallocator(base, size, cap));att = null;
}
创建DirectByBuffer的时候,通过unsafe.allocateMemory进行内存分配,然后使用unsafe.setMemory进行内存初始化,使用Cleaner类的create方法创建对象,这个对象可以用来跟踪DirectByBuffer对象,如果这个对象被垃圾回收了,那么分配的堆外内存也释放。
3.2 内存屏障
计算机的运行时,编译器和cpu会在保证结果不变的前提下,进行代码重排序,提升性能,但是这样可能会导致CPU高速缓存的数据和内存中的数据不一致,所以内存屏障的作用减少避免这样的事情发生。
内存屏障的实现在不同操作系统上可能不一样,所以Java引入3个内存屏障,都是统一由JVM实现的,屏蔽了底层的差异。
Unsafe中的桑个内存屏障:
//内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//内存屏障,禁止load、store操作重排序
public native void fullFence();
对内存屏障的理解,使用内存屏障之前,如果有123操作,那么123是可以打乱来执行的,但是有了内存屏障之后,它可以规定某个点,它让在某一点之前的所有读写任务都执行完以后才可以进行点后任务。 比如123操作 可能会规定 执行完12操作才可以进行3操作,3操作不可以提前到1或2之前。
用fullFence来说 它就规定了在这个屏障之前的所以读写操作执行完以后,才能进行屏障之后的操作。
并且完成屏障前的操作后,会将缓存数据设置为无效,重新从主存中获取,这个功能的用处就是,可以让内存屏障在多线程下实现可见性(内存屏障本身只有禁止指令重排序,volatile可以精致指令重排序和内存可见性)
因为没有内存屏障的时候线程更新数据到主存上可能不及时,或者及时也没有被另一个线程发现,有了内存屏障 必须重新冲主存中获取数据,这样就保证了内存的可见性。
代码举例:
@Getter
class ChangeThread implements Runnable{/**volatile**/ boolean flag=false;@Overridepublic void run() {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("subThread change flag to:" + flag);flag = true;}
}
public static void main(String[] args){ChangeThread changeThread = new ChangeThread();new Thread(changeThread).start();while (true) {boolean flag = changeThread.isFlag();unsafe.loadFence(); //加入读内存屏障if (flag){System.out.println("detected flag changed");break;}}System.out.println("main thread end");
}
subThread change flag to:false
detected flag changed
main thread end
如果没有loadFence方法 那么主线程没有办法感知到flag的变化,因为在Java内存模型中,运行的线程不是直接读取主存中的变量,而是修改线程本身的工作内存的变量,然后传给主内存的,并且线程和线程之间的内存是独立的,如果主线程要感知flag变化,那么就应该借助于主内存,主内存将修改后的flag同步给主线程,因为loadFence屏障之前的线程执行结束了,将缓存数据设置为无效,然后重新读取最新的主存内容,只有在合适的地方加入屏障,才可以保证内存可见性。
!!!保证内存可见性是因为它会立刻刷新屏障前的数据到主存中去,不保证内存可见性是因为一个线程写入的数据 不一定会被另一个线程立刻可见,而屏障就是保证了让他立刻可见后,才让另一个线程拿到正确的数据。
应用场景:
Java8中的一种锁机制——StampedLock,读写锁的一个改进版本,是一种乐观读锁,不会阻塞写线程获取写锁,缓解读多写少的时候,有的线程用的少的情况,但是因为不阻塞写锁,所以线程共享变量从主存到线程内存的时候,会有数据的不一样。
解决方法,stampedLock的validate方法会用过Unsafe1的loadFence方法加入一个load内存屏障,来防止指令重排序(禁止读操作重排序,内存屏障前的读操作不能重排序到内存屏障之后)
public boolean validate(long stamp) {U.loadFence();return (stamp & SBITS) == (state & SBITS);
}
3.3 对象操作
Unsafe提供了全部的8种基础数据类型以及Object的put和get方法,并且所由的put方法都可以直接修改内存种的数据。
Unsafe提供volatile读写和有序写入方法。
//在对象的指定偏移地址处读取一个int值,支持volatile load语义
public native int getIntVolatile(Object o, long offset);
//在对象指定偏移地址处写入一个int,支持volatile store语义
public native void putIntVolatile(Object o, long offset, int x);
有序写入的成本比volatile低,因为只保证有序写入,不保证可见性,也就是一个线程写入的值不能保证其他线程立刻可见,这是有关内存屏障的。
- Load:读,将主存种的数据拷贝到处理器的缓存中
- Store:写,将处理器中的缓存拷贝到主存中
!!内存屏障可以实现内存可见性和顺序写入的原因:
顺序写入和volatile写入的区别在于:
- 顺序写入是在写时加入内存屏障的类型为StoreStore类型
- 而volatile写入时加入的内存屏障类型时StoreLoad类型:

在顺序写入的时候使用StoreStore类型,可以保证Store1立刻刷新数据到主存中去,然后才可以进行Store2的后续操作。
在volatile写入的时候使用StoreLoad类型,保证Store1立刻刷新数据到主存中去,然后才可以进行Load2及后续操作,并且StoreLoad屏障会让在屏障之前的所有指令全部完成以后,才执行屏障之后的指令。
三个写入方法的效率:put>putOrder>putVolatile
对象实例化:
使用Unsafe的allocateInstance方法,进行非常规的对象实例化。
@Data
public class A {private int b;public A(){this.b =1;}
}
public void objTest() throws Exception{A a1=new A();System.out.println(a1.getB());A a2 = A.class.newInstance();System.out.println(a2.getB());A a3= (A) unsafe.allocateInstance(A.class);System.out.println(a3.getB());
}
应用场景:
- 常规对象实例化方法:使用new来创建对象,但是使用new的话,如果类只有一个有参构造方法也没有生命无参构造方法的时候,必须使用有参构造方法来构造对象。
- 非常规对象实例化方法:Unsafe中的allocateInstance方法,通过class对象就可以构造实例,不用调用构造方法,JVM安全检查,构造器是private修饰的 也可以实例化,这个allocateInstance在Gson(反序列化)中也有用到过。
3.4 数组操作
arrayBaseOffest和arrayIndexScale这两个方法配合使用,定位数字中每个元素在内存中的位置。
//返回数组中第一个元素的偏移地址
public native int arrayBaseOffset(Class<?> arrayClass);
//返回数组中一个元素占用的大小
public native int arrayIndexScale(Class<?> arrayClass);
3.5 CAS操作
CAS:意思是比较和替换的意思,在并发编程中经常会用到CAS来保证数据的正确性,是一个原子操作,CAS中有三个参数(内存位置的值,预期值,新值),执行时会将内存位置的值和预期值比较,如果相同就会吧内存位置的值替换为新值,如果不同就不会替换。
Unsafe提供的CAS方法底层实现是CPU指令cmpxchg
在Unsafe类中提供了三种CAS类型:
compareAndSwapObject、compareAndSwapInt、compareAndSwapLong
/*** CAS* @param o 包含要修改field的对象* @param offset 对象中某field的偏移量* @param expected 期望值* @param update 更新值* @return true | false*/
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);
应用场景:
在JUC包的并发工具类中使用了很多CAS操作,如synchronized和AQS都有使用。‘
比如compareAndSwapInt:
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
其中o为要操作的对象,offset是偏移量,expected是预期值,x是替换的值,如果offset的值和expected的值一样,就会把offset这个字段的值更新为x。
使用1加到10的多线程环境下的例子:
import sun.misc.Unsafe;public class CasTest {
// public static void main(String[] args) throws NoSuchFieldException {
// Field field = Unsafe.class.getField("theUnsafe");
// Demo1 demo1 = new Demo1(field);
// }private void increment(int x) {Unsafe unsafe = Demo.reflectGetUnsafe();while (true) {try {//拿到全局变量的a值 初始为0long offset = unsafe.objectFieldOffset(CasTest.class.getDeclaredField("a"));//第一次 当offset(a=0)等于x-1(x=1)的时候 把x替换到offset上if (unsafe.compareAndSwapInt(this,offset,x-1,x)){break;}} catch (NoSuchFieldException e) {e.printStackTrace();}}}private volatile int a;public static void main(String[] args) {CasTest casTest = new CasTest();new Thread(() -> {for (int i = 1; i < 5; i++) {casTest.increment(i);System.out.print(casTest.a+" ");}}).start();new Thread(()->{for (int i = 5; i < 10; i++) {casTest.increment(i);System.out.print(casTest.a+" ");}}).start();}
}
解释:
因为多线程环境下 第一个线程加到2的时候,第二个线程从5开始加,他进入到increment去进行CAS比较的时候会发现内存中的a值(a=2)并不是期望值(x-1=4),那么就不会执行CAS操作把替换值(x=5)给内存中的a值,就会一直循环下去知道a被加到了正确的值的时候,这时候if才会成立然后才break结束这一次任务。
3.6 线程调度
Unsafe类中提供了park,unpark,monitorEnter,monitorExit,tryMonitorEnter方法进行线程调度
//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o);
//释放对象锁
@Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);
park阻塞线程,unpark取消阻塞,monitorEnter获取对象锁,monitorExit释放对象锁,tryMonitorEnter尝试获得对象锁。
在Unsafe源码中 除了park和unpark 剩下的方法以及不建议使用了。
应用场景:
抽象队列同步器 AbstractQueuedSynchronizer(AQS)就是通过调用 LockSupport.park() 和 LockSupport.unpark() 实现线程阻塞和线程唤醒的,而其实他的park和unpark调用的是Unsafe类的park和unpark方法。
public static void park(Object blocker) {Thread t = Thread.currentThread();setBlocker(t, blocker);UNSAFE.park(false, 0L);setBlocker(t, null);
}
public static void unpark(Thread thread) {if (thread != null)UNSAFE.unpark(thread);
}
对Unsafe的park和unpark方法测试👇
public static void main(String[] args) {Thread mainThread = Thread.currentThread();Unsafe unsafe = Demo.reflectGetUnsafe();new Thread(()->{try {TimeUnit.SECONDS.sleep(5);System.out.println("取消阻塞主线程");unsafe.unpark(mainThread);//取消主线程阻塞} catch (InterruptedException e) {e.printStackTrace();}}).start();System.out.println("阻塞主线程");unsafe.park(false,0l);System.out.println("取消成功");
}
阻塞主线程
取消阻塞主线程
取消成功
子线程运行时睡眠,主线程打印内容并且阻塞自己,子线程睡眠结数回复工作 打印内容以后唤醒主线程,主线程打印最后的内容。
3.7 Class操作
Unsafe对Class的相关操作主要包括类加载和静态变量的操作方法。
//获取静态属性的偏移量
public native long staticFieldOffset(Field f);
//获取静态属性的对象指针
public native Object staticFieldBase(Field f);
//判断类是否需要初始化(用于获取类的静态属性前进行检测)
public native boolean shouldBeInitialized(Class<?> c);
应用场景:
Lambda表达式需要实现依赖Unsafe的defineAnonymousClass方法定义实现相应的函数式接口的匿名类。
3.8 系统信息
有两个获取系统信息的方法
//返回系统指针的大小。返回值为4(32位系统)或 8(64位系统)。
public native int addressSize();
//内存页的大小,此值为2的幂次方。
public native int pageSize();
4. 总结
介绍了Unsafe的概念,功能有哪些,和使用的方法和场景,Unsafe可以让我们更底层的操作 操作系统,便捷的访问操作系统内存,管理操作系统内存资源,但是带来的安全隐患也是需要注意的,堆外内存的处理,指针安全的处理等等。
相关文章:
(1w字一篇理解透Unsafe类)Java魔法类:Unsafe详解
Java魔法类 Unsafe 文章导读:(约12015字,阅读时间大约1小时)1. Unsafe介绍2. Unsafe创建3. Unsafe功能3.1内存操作3.2 内存屏障3.3 对象操作3.4 数组操作3.5 CAS操作3.6 线程调度3.7 Class操作3.8 系统信息 4. 总结 JUC源码中的并发工具类出现过很多次 …...
Python的正则表达式使用
Python的正则表达式使用 定义使用场景查替换分割 常用的正则表达符号查原字符英文状态的句号点 .反斜杠 \英文的[]英文的()英文的?加号 星号 *英文状态的大括号 {} 案例 定义 正则表达式是指专门用于描述或刻画字符串内在规律的表达式。 使用场景 无法通过切片,…...
Elasticsearch:评估 RAG - 指标之旅
作者:Quentin Herreros,Thomas Veasey,Thanos Papaoikonomou 2020年,Meta发表了一篇题为 “知识密集型NLP任务的检索增强生成” 的论文。 本文介绍了一种通过利用外部数据库将语言模型 (LLM) 知识扩展到初始训练数据之外的方法。 …...
【2023.12.4练习】数据库知识点复习测试
概论 数据表:用于存储现实中数据的联系。 储存信息联系。 字段:又称列,如姓名、年龄、编号等。 记录:又称元组,为数据表中的一行,代表了一个实体的信息。 数据库(DB)࿱…...
【wvp】测试记录
ffmpeg 这是个莫名其妙的报错,通过排查,应该是zlm哪个进程引起的 会议室的性能 网络IO也就20M...
【若依框架实现上传文件组件】
若依框架中只有个人中心有上传图片组件,但是这个组件不适用于el-dialog中的el-form表单页面 于是通过elementui重新写了一个上传组件,如图是实现效果 vue代码 <el-dialog :title"title" v-model"find" width"600px"…...
玩转大数据5:构建可扩展的大数据架构
1. 引言 随着数字化时代的到来,大数据已经成为企业、组织和个人关注的焦点。大数据架构作为大数据应用的核心组成部分,对于企业的数字化转型和信息化建设至关重要。我们将探讨大数据架构的基本要素和原则,以及Java在大数据架构中的角色&…...
【华为数据之道学习笔记】非数字原生企业的特点
非数字原生企业的数字化转型挑战 软件和数据平台为核心的数字世界入口,便捷地获取和存储了大量的数据,并开始尝试通过机器学习等人工智能技术分析这些数据,以便更好地理解用户需求,增强数字化创新能力。部分数字原生企业引领着云计…...
Kubernetes学习笔记-Part.01 Kubernets与docker
目录 Part.01 Kubernets与docker Part.02 Docker版本 Part.03 Kubernetes原理 Part.04 资源规划 Part.05 基础环境准备 Part.06 Docker安装 Part.07 Harbor搭建 Part.08 K8s环境安装 Part.09 K8s集群构建 Part.10 容器回退 第一章 Kubernets与docker Docker是一种轻量级的容器…...
k8s学习
文章目录 前言一、k8s部署方式二、学习k8s的方式今天主要配置k8s环境的方式今天遇到的是一个在k8s进行初始化的方式,但是发现k8s不能正常初始化总是出现错误,或者在错误中有问题的方式,在网上查询挺多资料需要重新启动kub文件,删除…...
测试:JMeter和LoadRunner比较
比较 JMeter和LoadRunner是两款常用的软件性能测试工具,它们在功能和性能上有一定的相似性和差异。下面从几个方面对它们进行比较: 1. 架构和原理: JMeter和LoadRunner的架构和原理基本相同,都是通过中间代理监控和收集并发客户…...
(C语言)通过循环按行顺序为一个矩阵赋予1,3,5,7,9,等奇数,然后输出矩阵左下角的值。
#include<stdio.h> int main() {int a[5][5];int n 1;for(int i 0;i < 5;i ){for(int j 0;j < 5;j ){a[i][j] n;n 2;}}for(int i 0;i < 5;i ){for(int j 0;j < i;j )printf("%-5d",a[i][j]);printf("\n");}return 0; } 运行截图…...
GitHub项目推荐-Deoldify
有小伙伴推荐了一个老照片上色的GitHub项目,看了简介,还不错,推荐给大家。 项目地址 GitHub - SpenserCai/sd-webui-deoldify: DeOldify for Stable Diffusion WebUI:This is an extension for StableDiffusions AUTOMATIC1111 w…...
微前端qiankun示例 Umi3.5
主应用配置(基座) 安装包 npm i umijs/plugin-qiankun -D 配置 qiankun 开启 {"private": true,"scripts": {"start": "umi dev","build": "umi build","postinstall": "…...
熬夜会秃头——beta冲刺Day7
这个作业属于哪个课程2301-计算机学院-软件工程社区-CSDN社区云这个作业要求在哪里团队作业—beta冲刺事后诸葛亮-CSDN社区这个作业的目标记录beta冲刺Day7团队名称熬夜会秃头团队置顶集合随笔链接熬夜会秃头——Beta冲刺置顶随笔-CSDN社区 一、团队成员会议总结 1、成员工作…...
IntelliJ IDEA设置中文界面
1.下载中文插件 2. 点击重启IDE 3.问题就解决啦!...
RTSP流媒体播放器
rtsp主要还是运用ffmpeg来搭建node后端转发到前端,前端再播放这样的思路。 这里讲的到是用两种方式,一种是ffmpeg设置成全局来实现,一种是ffmpeg放在本地目录用相对路径来引用的方式。 ffmpeg下载地址:http://www.ffmpeg.org/do…...
使用正则表达式时-可能会导致性能下降的情况
目录 前言 正则表达式引擎 NFA自动机的回溯 解决方案 前言 正则表达式是一个用正则符号写出的公式,程序对这个公式进行语法分析,建立一个语法分析树,再根据这个分析树结合正则表达式的引擎生成执行程序(这个执行程序我们把它称作状态机&a…...
Maven生命周期
Maven生命周期 通过IDEA工具的辅助,能很轻易看见Maven的九种生命周期命令,如下: 双击其中任何一个,都会执行相应的Maven构建动作,为啥IDEA能实现这个功能呢?道理很简单,因为IDEA封装了Maven提供…...
深度学习(五):pytorch迁移学习之resnet50
1.迁移学习 迁移学习是一种机器学习方法,它通过将已经在一个任务上学习到的知识应用到另一个相关任务上,来改善模型的性能。迁移学习可以解决数据不足或标注困难的问题,同时可以加快模型的训练速度。 迁移学习的核心思想是将源领域的知识迁…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...
基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
