当前位置: 首页 > news >正文

JVM | 基于类加载的一次完全实践

引言

我在上篇文章:JVM | 类加载是怎么工作的 中为你介绍了Java的类加载器及其工作原理。我们简单回顾下:我用一个易于理解的类比带你逐步理解了类加载的流程和主要角色:引导类加载器扩展类加载器应用类加载器。并带你深入了解了这些“建筑工人”如何从底层工作,搬运原材料(类)并将其完整地构建在Java虚拟机(JVM)的“建筑工地”上。然后,我们跟随一个具体的Building类,亲眼目睹了其在JVM中的生命周期。我在文章末尾留了几个问题,你还记得吗?

本篇文章,我将带你了解自定义类加载器的创建和使用。我们还将探索Java的SPI机制,了解它如何利用类加载器实现服务的动态发现和加载。接着,我们再来看下Tomcat的类加载机制,尤其是它的热部署多版本共存的实现,了解类加载机制在现实世界中的高级应用。


自定义类加载器的创建和使用

当我们的类涉及到一些安全的操作,或者我们想从网络或者其它地方加载类。这种情况,我们就会创建自定义的类加载器,重写findClass方法来完成这个特殊的加载逻辑。

沿用上篇文章的例子:假如工地来活了,要求建造一个复杂的建筑物,这个建筑物不仅包括了普通的房间(普通的类),还包括了一些特殊设计的房间(特殊的类)。
在这个情况下,你可能会需要一位专门的工人来处理这些特殊的房间。这位工人需要有特殊的技能和工具,才能按照设计图纸(类的字节码)正确地建造出房间。
接下来,我们来看下类加载器怎么创建与使用的。


创建类加载器

我们来实现一个类加载器,代码如下:


public class CustomClassLoader extends ClassLoader {private String classPath;public CustomClassLoader(String classPath) {this.classPath = classPath;}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {// 先检查类是否已经被加载Class<?> cls = findLoadedClass(name);if (cls != null) {return cls;}try {// 如果类还未被加载,尝试使用父类加载器加载(不破坏双亲委派机制)cls = getParent().loadClass(name);} catch (ClassNotFoundException e) {// 父类加载器无法加载该类,那么就调用 findClass 尝试自己加载cls = findClass(name);}return cls;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 你的类加载逻辑...byte[] classData = loadClassData(name);if (classData == null) {throw new ClassNotFoundException();} else {return defineClass(name, classData, 0, classData.length);}}private byte[] loadClassData(String className) {String fileName = getFileName(className);try {InputStream is = new FileInputStream(fileName);ByteArrayOutputStream baos = new ByteArrayOutputStream();int bufferSize = 4096;byte[] buffer = new byte[bufferSize];int bytesNumRead;while ((bytesNumRead = is.read(buffer)) != -1) {baos.write(buffer, 0, bytesNumRead);}return baos.toByteArray();} catch (IOException e) {e.printStackTrace();}return null;}private String getFileName(String name) {int index = name.lastIndexOf('.');// 如果没有找到'.'则直接在classPath中查找if (index == -1) { return classPath + name + ".class";} else {return classPath + name.substring(index + 1) + ".class";}}
}

上面的类加载器CustomClassLoader 通过构造的方式传入文件路径。当我们要加载类时,它会调用loadClass方法从我们定义的类路径下读取字节流。好,
接下来,我们来使用我们自己定义的类加载器。

使用类加载器

代码如下:

// 把上篇文章的Building类放在桌面
CustomClassLoader customClassLoader = new CustomClassLoader("C:\\Users\\xxx\\Desktop\\");try {// 使用自定义类加载器加载 Building 类,若你的包名不叫这个,请更换。Class<?> cls = Class.forName("org.kfaino.jvm.Building", true, customClassLoader);// 创建类的实例cls.newInstance();System.out.println("实例名:" + cls.getName() + " 被加载器:" + cls.getClassLoader() + "创建");} catch (Exception e) {e.printStackTrace();
}

执行!

Connected to the target VM, address: '127.0.0.1:4706', transport: 'socket'
建筑蓝图已被创建!
实例名:org.kfaino.jvm.Building 被加载器:sun.misc.Launcher$AppClassLoader@3f3fdda9创建Disconnected from the target VM, address: '127.0.0.1:4706', transport: 'socket'
Process finished with exit code 0

类被AppClassLoader加载了,为啥? 原因是我的项目中有Building类, 这个类可以被应用类加载器加载,因此就轮不到·CustomClassLoader 加载了。我们把项目内的Building先改名一下。然后执行!

Connected to the target VM, address: '127.0.0.1:5032', transport: 'socket'
建筑蓝图已被创建!
实例名:org.kfaino.jvm.Building 被加载器:org.kfaino.jvm.CustomClassLoader@6379b5ed创建
Disconnected from the target VM, address: '127.0.0.1:5032', transport: 'socket'Process finished with exit code 0

这次,我们的类成功被CustomClassLoader加载,并且加载的是我们桌面上的字节码文件。


使用Java自带的类加载器工具类

当然,如果你想要从外部加载字节码文件,可以不必这么繁琐。JDK提供了一个功能更强大的URLClassLoader。我们一起来看下它怎么用:

        // 把Building放在桌面URL[] urls = new URL[] {new URL("file:C:\\Users\\xxx\\Desktop\\")};URLClassLoader customClassLoader = new URLClassLoader(urls);try {// 使用自定义类加载器加载 Building 类Class<?> cls = Class.forName("org.kfaino.jvm.Building", true, customClassLoader);// 创建类的实例cls.newInstance();System.out.println("实例名:" + cls.getName() + " 被加载器:" + cls.getClassLoader() + "创建");} catch (Exception e) {e.printStackTrace();}

我们执行看下:

Connected to the target VM, address: '127.0.0.1:9734', transport: 'socket'
建筑蓝图已被创建!
实例名:org.kfaino.jvm.Building 被加载器:java.net.URLClassLoader@28634811创建
Disconnected from the target VM, address: '127.0.0.1:9734', transport: 'socket'Process finished with exit code 0

没有问题,本地的字节码文件Building 被成功读取。因此,当你有从外部读取字节码文件的需求,可以试试用JDK自带的·URLClassLoader类加载器。同时,它还提供了其它更强大的功能。

从网络URL加载类和资源

若你想从网络加载字节码文件,你可以这么做:

URL url = new URL("http://www.github.com/xxx/");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
Class<?> clazz = urlClassLoader.loadClass("包名.类名");

更多的URL加载类和资源

细心的你肯定发现URLClassLoader的构造入参是数组类型,也就意味着可以传入多个URL,具体用法如下:

URL url1 = new URL("http://www.github.com/xxx1/");
URL url2 = new URL("http://www.github.com/xxx2/");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url1, url2});
Class<?> clazz = urlClassLoader.loadClass("包名.类名");

从JAR文件加载类和资源

它可以从完整的jar包中读取字节码文件,代码如下:

File file = new File("/xxx/jarfile.jar");
URL url = file.toURI().toURL();
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
Class<?> clazz = urlClassLoader.loadClass("包名.类名");

加载外部配置文件

它可以从外部读取配置文件,代码如下:

File file = new File("/xxx/resources/");
URL url = file.toURI().toURL();
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
URL resourceUrl = urlClassLoader.getResource("xxx.properties");
InputStream stream = resourceUrl.openStream();
Properties properties = new Properties();
properties.load(stream);

自定义类加载器的注意事项

类加载器在类的加载过程中起着至关重要的作用。因此,在使用时,我们必须倍加警惕。接下来,让我们来看下哪些需要注意的问题:

内存泄漏

长期存活的类加载器持有类的引用就会导致内存泄露。为了避免这个问题,我建议你在关键代码处适当使用如下代码,让旧的类加载器和类实例进行解绑:

// 释放对 ClassLoader 的引用,使其有可能被垃圾回收
classLoader = null;
System.gc();

当然,每个问题都需要我们针对性地分析。我这里只是提供可能导致内存泄漏的一个说法。实际上,引发内存泄漏的原因有很多,如果你在工作中遇到了这个问题,可以使用一些可视化分析工具来综合性的分析。

不要轻易破坏双亲委派机制

双亲委派模型是为了保证Java核心类库的安全性。当然,我们也可以选择破坏双亲委派模型,前提是,你已考虑好这些风险并规避。

在上述代码中,我们没有违背双亲委派模型的原则。回顾一下我们在之前文章中提到的双亲委派模型的概念:在类加载的过程中,我们首先会让父类加载器进行加载,只有在父类加载器无法加载的情况下,我们才会使用自定义的类加载器进行加载。顺便我把上篇缺少的自定义类加载器也补充进去,你可以看下:
在这里插入图片描述

线程安全问题

如果我们在多线程中使用类加载器,可能会导致类被重复加载多次。除了会浪费资源外,还会导致我们一些静态初始化代码被执行多次,造成一些诡异的问题。我在上篇专栏中说到,解决线程安全的方式有多种。为了保险起见,你可以采用同步方案来解决它。


自定义类加载器使用场景

在上面的例子中,我为你展示如何从外部加载字节码文件。接下来,我们来看下还有哪些使用场景:

安全检查

安全,是软件工程中永恒的话题。为了防止第三方的潜在干扰,我们通常在获取外部文件的同时,做一些过滤的机制。你看代码:

public class SecurityCheckingClassLoader extends ClassLoader {private static final String CLASS_NAME_PREFIX = "Safe";@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {// 加上你想要的安全校验逻辑if (!name.startsWith(CLASS_NAME_PREFIX)) {throw new ClassNotFoundException("不安全的类: " + name);}return super.loadClass(name);}
}

上面,我为你举了一个简单的例子。我在加载类方法loadClass前校验类名的前缀,如果你不是Safe开头的类,我们就不予放行。

解密加密的类文件

网络环境充满不确定性,如果你选择从网络获取字节码文件,我建议你首先做好加密工作。既然是从外部获取文件,我们可以通过继承URLClassLoader来实现。代码如下:

import java.net.URL;
import java.net.URLClassLoader;public class DecryptingURLClassLoader extends URLClassLoader {public DecryptingURLClassLoader(URL[] urls) {super(urls);}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 获取字节码文件byte[] classData = loadClassData(name);if (classData == null) {throw new ClassNotFoundException();}byte[] decryptedClassData = decrypt(classData);return defineClass(name, decryptedClassData, 0, decryptedClassData.length);}private byte[] loadClassData(String className) {// 从网络中获取字节码文件}private byte[] decrypt(byte[] classData) {// 解密字节码文件}
}

上面两个是工作中相对比较容易用到的两种场景,还有没有其它类加载器的优秀案例呢?


基于自定义类加载器的其它实现

我们从官网文档中得知,Tomcat会为每个web应用创建类加载器。图片如下:
在这里插入图片描述
现在,我们来看下Tomcat用自定义加载器做了哪些事情。

Tomcat中的热部署

先来解释下什么是热部署? 热部署是指我们的应用在运行过程中,可以在不关闭应用的前提下更新应用。
假如你想开启热部署,你可以在context.xml里面设置reloadable="true”。限于篇幅有限,我在这里只是为你说明Tomcat热部署到底是怎么实现的,如果你感兴趣,建议您亲自动手实操。

热部署实现原理

Tomcat通过一个BackgroundProcessor 后台线程周期性的检查web 应用的 WEB-INF/classes 和 WEB-INF/lib 目录下的 class 文件或 jar 文件是否有变化。具体做法是比较文件的最后修改时间和上次记录的最后修改时间是否一致。如果有变化,就触发 web 应用的重载。
Tomcat重新加载一个web应用时,会创建一个新的WebappClassLoader实例,并使用这个新的类加载器来加载web应用的类。这样,新的类加载器就会加载最新版本的类,而旧的类加载器加载的旧版本的类会在它们不再被引用时被垃圾回收。这就是Tomcat的热部署。

Tomcat中的多版本共存

那什么是多版本共存? 我们在上面说到,每一个web应用都有自己独立的类加载器,这就意味着每一个web应用都有自己的类和库的命名空间。即使同一Tomcat实例中运行的多个web应用使用了同名的类和库,它们也不会相互干扰。
也就是说Tomcat的多版本共存关键也在于每个应用都有不同的类加载器。
限于篇幅有限,更多细节,建议你移步到官方文档,我在文末参考文献中为你贴出官方地址。


ServiceLoader和SPI

我们经常会听到许多Java框架包括Dubbo、Spring等都使用了SPI这个机制,SPI究竟是什么东西?ServiceLoader和我们今天讲的类加载器又有什么关系?

SPI(Service Provider Interface)是什么?

我从服务提供方和服务调用方两个视角来为你讲解:

服务提供方

对于服务提供方而言,它只需要根据接口实现对应方法。并且把配置文件放在META-INF/services/ 告知服务调用方。

服务调用方

服务调用方根据约定,使用ServiceLoader来遍历实现该约定的实现类。加载进内存中供服务提供方调用。

为了加深理解,我为你画了一张图:
在这里插入图片描述

ServiceLoader和类加载器的关系

它和类加载器又有什么关系?我通过代码为你分析,你看:

public static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);
}

如果你不指定类加载器,load方法默认获取当前线程的类加载器去加载该类。它通过扫描META-INF/services/目录下的配置文件,找到服务接口的实现类的全限定名。有了全限定名就等于掌握了花名册,当我们遍历ServiceLoader时,服务加载器会通过类加载器将这些服务提供者实例化,这样我们就可以使用这些服务了。


文中重要部分解析

命名空间

我在上面Tomcat多版本共存中提到命名空间,什么是命名空间?每个类加载器实例其实就是一个命名空间。也就是说,在一个应用程序中允许一个类被多个类加载器实例加载,并且共存于应用程序中。暂停30秒,思考一下,这样会出现什么问题。
好,在回答这个问题之前,我为你展示一个代码:

try {URL[] urls = new URL[] {new URL("file:C:\\Users\\xxx\\Desktop\\")};URLClassLoader customClassLoader1 = new URLClassLoader(urls);URLClassLoader customClassLoader2 = new URLClassLoader(urls);Class cls1 = customClassLoader1.loadClass("org.kfaino.jvm.Building");Class cls2 = customClassLoader2.loadClass("org.kfaino.jvm.Building");Object obj1 = cls1.newInstance();Object obj2 = cls2.newInstance();System.out.println("obj1 class: " + obj1.getClass());System.out.println("obj2 class: " + obj2.getClass());System.out.println("obj1 class loader: " + obj1.getClass().getClassLoader());System.out.println("obj2 class loader: " + obj2.getClass().getClassLoader());// Building对象已经重写hashCode和equals方法System.out.println("obj1 equals obj2: " + obj1.equals(obj2));} catch (Exception e) {e.printStackTrace();
}

在Building已经重写hashCode和equals方法的前提下,obj1 equals obj2: 会是true吗?我们看下结果:

建筑蓝图已被创建!
建筑蓝图已被创建!
obj1 class: class org.kfaino.webTemplate.jvm.Building
obj2 class: class org.kfaino.webTemplate.jvm.Building
obj1 class loader: java.net.URLClassLoader@da236ecf
obj2 class loader: java.net.URLClassLoader@8e02f9da
obj1 equals obj2: falseProcess finished with exit code 0

结果是否定的,和我之前说的吻合。也就是说使用不同的类加载器,不同类加载器的对象(命名空间不同),在JVM中就是类型不一致的。

生产环境中的热部署

BackgroundProcessor 后台线程,需要周期性地检查(checkResources())文件的状态。处于对性能方面的考虑,在生产环境中,通常会关闭 Tomcat 的热部署功能。

SPI配置文件存放位置META-INF/services/可以更改吗?

查阅官方文档,我们可以知道SPI是JDK内置的一种服务提供发现机制。在SPI机制中,服务提供者的配置文件默认放在META-INF/services/目录下。这是Java SPI规范的一部分,无法更改。


总结

至此,本篇完结。我们来回顾下:首先,我带你创建并使用了类加载器完成从本地文件夹下加载自己的类。这些工作我们可以通过Java自带的类加载器来简化,我也为你演示其用法。当然,我们在使用自定义类加载器要格外注意,因为涉及到类初始化往往你会碰到一些不可预见的诡异BUG。然后,我为你介绍自定义类加载器场景的使用场景。顺便看一下Tomcat和Java是怎么用自定义类加载器的特性实现高级功能的。


常见面试题

如何自定义类加载器?

在什么情况下会需要自定义类加载器?

Tomcat的类加载器有什么特点?如何实现热部署和多版本共存?#### 什么是ServiceLoader和SPI,它们如何利用类加载器?

类加载器可能存在的问题有哪些?


参考文献

  1. Java Guide SPI机制详解
  2. class-loader-howto
  3. baeldung-java-classloaders
  4. Inside the Java Virtual Machine
  5. 老大难的 Java ClassLoader 再不理解就老了

相关文章:

JVM | 基于类加载的一次完全实践

引言 我在上篇文章&#xff1a;JVM | 类加载是怎么工作的 中为你介绍了Java的类加载器及其工作原理。我们简单回顾下&#xff1a;我用一个易于理解的类比带你逐步理解了类加载的流程和主要角色&#xff1a;引导类加载器&#xff0c;扩展类加载器和应用类加载器。并带你深入了解…...

Termux实现电脑端远程操作【开启SSH的完整教程】

文章目录 前言一、安装软件1、安装2、启动服务3、特别说明4、添加key二、电脑端连接1、查看ip2、电脑端连接总结前言 上篇文章【安卓手机变身Linux服务器】讲了如何将你的上古安卓手机变废为宝,这节着重为大家解决一个痛点:“手机上操作实在是不方便”。 一、安装软件 1、安…...

java(Collection类)

文章目录 Collection接口继承树Collection接口及方法判断删除其它 Iterator(迭代器)接口迭代器的执行原理 foreach循环Collection子接口1&#xff1a;ListList接口特点List接口方法List接口主要实现类&#xff1a;ArrayListList的实现类之二&#xff1a;LinkedListList的实现类…...

VS2019编译安装OpenMesh8.0

文章目录 一、简介二、相关准备三、编译安装四、举个栗子参考资料一、简介 多边形网格一直以来就是交互式3D图形应用程序中最合适的几何表示,它们足够灵活,可以近似任意形状,并且可以通过当前的图形硬件有效地处理,即使在今天的低成本电脑上也是如此。OpenMesh便是其中一种…...

Python爬虫遇到URL错误解决办法大全

在进行Python爬虫任务时&#xff0c;遇到URL错误是常见的问题之一。一个错误的URL链接可能导致爬虫无法访问所需的网页或资源。为了帮助您解决这个问题&#xff0c;本文将提供一些实用的解决方法&#xff0c;并给出相关代码示例&#xff0c;希望对您的爬虫任务有所帮助。 一、…...

基于Vue+ElementUI+Echarts+G2Plot的大屏设计器,代码完全开源

简介 &#x1f525;DataRoom是一款基于SpringBoot、MyBatisPlus、ElementUI、G2Plot、Echarts等技术栈的大屏设计器&#xff0c;具备大屏设计、预览、资源管理、组件管理等能力&#xff0c;支持JSON、MySQL、Oracle、PostgreSQL、HTTP、JavaScript、Groovy等数据集接入&#x…...

Linux - PostgreSQL 适用于9.x 以上的 tar.gz 源码安装与理解 - 报错集锦

这里写目录标题 序言主要内容bash 配置文件个人理解关于初始化 PostgreSQL 数据库的理解 启动方法检查服务器是否在PostgreSQL中运行关闭 postgresql 数据库方法参考链接 序言 PostgreSQL 9.x 以下版本笔者没用过&#xff0c;具体操作看参考链接&#xff0c;笔者就不记录重复操…...

Django使用用户列表的展示和添加

接着上一篇&#xff1a;https://blog.csdn.net/javascript_good/article/details/132027702 来实现用户表的查询和添加 1、创建数据库表 在models.py 中&#xff0c;增加UserInfo类&#xff0c;包括字段姓名、密码、年龄、账号余额、入职时间、所属部门、性别 verbose_name 就…...

kubernetes错误汇总

title: “kubernetes错误汇总” categories: - “技术” tags: - “Kubernetes” - “错误汇总” toc: false original: true draft: false 1、增加 master etcd 报错 1.1、错误描述 由于创建的k8s集群&#xff0c;其中有一个master节点初始化失败&#xff0c;先删除了这个节…...

[openCV]基于拟合中线的智能车巡线方案V4

import cv2 as cv import os import numpy as np# 遍历文件夹函数 def getFileList(dir, Filelist, extNone):"""获取文件夹及其子文件夹中文件列表输入 dir&#xff1a;文件夹根目录输入 ext: 扩展名返回&#xff1a; 文件路径列表"""newDir d…...

【网络云盘客户端】——上传文件的功能的实现

目录 上传文件功能的实现 uploadtask的设计 设置上传的槽函数 uploadFileAction接口 uploadFile接口 定时上传文件 进度条的设计 上传文件功能的实现 上传文件功能实现 1.双击 ”上传文件 “的 QListWidgetItem 或者 点击 “上传” 菜单项 都会弹出一个文件对话框 2.在文…...

WebView2对比CefSharp的超强优势

第一次使用了CefSharp组件&#xff0c;集成开发结束后&#xff0c;测试及使用过程中遇到了一些无法处理的bug及严重的性能问题。然后又测试对比了其他多种组件&#xff0c;具体情况可以阅读我的博客​ ​《.NET桌面程序集成Web网页开发的十种解决方案》​​。最终选用了微软新出…...

前端需要知道的计算机网络知识

1 Web 机制 无论通过有线方式 (通常是网线) 还是无线方式&#xff08;比如 wifi 或蓝牙)&#xff0c;通信需要进行连接&#xff0c;网络上的每台计算机需要链接到路由器&#xff08;router&#xff09;。 路由器确保从一台计算机上发出的一条信息可以到达正确的计算机。计算机…...

[2023杭电多校5 1005] Snake (生成函数)

题意 有 n n n 个标号为 1 , 2 , ⋯ , n 1,2,\cdots,n 1,2,⋯,n 的球&#xff0c;放到 m m m 个无标号盒子 (盒内顺序有标号)&#xff0c;且每个盒子球数不超过 k k k&#xff0c;求方案数对 998 244 353 998\,244\,353 998244353 取模。 1 ≤ m , k ≤ n ≤ 1 0 6 1 \le…...

【MyBtis】各种查询功能

目录 【MyBtis】配置和映射 11.1 示例:实现表数据的增、删、改、查 1.创建工程mybatis_DML demo 2.创建数据库操作的工具类&#xff1a;DBOperatorMgr.java 3.创建映射接口 4.创建XML映射文件 5.测试 【MyBtis】配置和映射 MyBatis 的真正强大之外在于它的映射语句&#xf…...

H5打包封装小程序系统开发

H5打包封装小程序系统开发 H5打包封装小程序系统开发是指将H5页面打包封装成小程序的开发过程。下面是一个简单的步骤&#xff1a; 准备工作&#xff1a;首先&#xff0c;需要准备好H5页面的代码和资源文件。确保H5页面在浏览器中正常运行&#xff0c;并且没有依赖于浏览器特…...

SpringBoot集成jasypt,加密yml配置文件

SpringBoot集成jasypt&#xff0c;加密yml配置文件 一、pom配置二、生成密文代码三、配置3.1、yml加密配置3.2、密文配置3.3、启动配置3.4、部署配置 四、遇到的一些坑 最新项目安全检测&#xff0c;发现配置文件中数据库密码&#xff0c;redis密码仍处理明文状态 一、pom配置…...

【C++】模板(初阶)

1、泛型编程 泛型编程&#xff1a;编写与类型无关的通用代码&#xff0c;是代码复用的一种手段。模板是泛型编程的基础 2、函数模板 函数模板代表了一个函数家族&#xff0c;该函数模板与类型无关&#xff0c;在使用时被参数化&#xff0c;根据实参类型产生函数的特定类型版本…...

windows下的txt文档,传到ubuntu后,每行后面出现^M,怎么处理?

问题背景&#xff1a;windows下pycharm生成的txt文档&#xff0c;传到ubuntu后&#xff0c;每行后面出现^M 用vim打开显示 使用cat -A filename显示如下 参考https://www.lmlphp.com/user/16697/article/item/579325/给出的几种方法 方法一、dos2unix filename。服务器没装…...

LabVIEW FPGA开发实时滑动摩擦系统

LabVIEW FPGA开发实时滑动摩擦系统 由于非线性摩擦效应的建模和补偿的固有困难&#xff0c;摩擦系统的运动控制已被广泛研究。最近&#xff0c;人们更加关注滑动动力学和滑动定位&#xff0c;作为传统机器人定位的低成本和更灵活的驱动替代方案。摩擦控制器设计和适当选择基础…...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

PHP 8.5 即将发布:管道操作符、强力调试

前不久&#xff0c;PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5&#xff01;作为 PHP 语言的又一次重要迭代&#xff0c;PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是&#xff0c;借助强大的本地开发环境 ServBay&am…...

Unity UGUI Button事件流程

场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...

苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会

在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...

vue3 daterange正则踩坑

<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...

【Veristand】Veristand环境安装教程-Linux RT / Windows

首先声明&#xff0c;此教程是针对Simulink编译模型并导入Veristand中编写的&#xff0c;同时需要注意的是老用户编译可能用的是Veristand Model Framework&#xff0c;那个是历史版本&#xff0c;且NI不会再维护&#xff0c;新版本编译支持为VeriStand Model Generation Suppo…...

ubuntu22.04 安装docker 和docker-compose

首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...