Mybatis如何兼容各类日志?

文章目录

  • 适配器模式
  • 日志模块
  • 代理模式
    • 1、静态代理模式
    • 2、JDK动态代理
  • JDBC Logger
  • 总结

Apache Commons Logging、Log4j、Log4j2、java.util.logging 等是 Java 开发中常用的几款日志框架,这些日志框架来源于不同的开源组织,给用户暴露的接口也有很多不同之处,所以很多开源框架会自己定义一套统一的日志接口,兼容上述第三方日志框架,供上层使用。

一般实现的方式是使用适配器模式,将各个第三方日志框架接口转换为框架内部自定义的日志接口。MyBatis 也提供了类似的实现。

适配器模式

适配器模式主要解决的是由于接口不能兼容而导致类无法使用的问题,这在处理遗留代码以及集成第三方框架的时候用得比较多。其核心原理是:通过组合的方式,将需要适配的类转换成使用者能够使用的接口。

适配器模式的类图如下所示

在这里插入图片描述
在该类图中,你可以看到适配器模式涉及的三个核心角色。

  • 目标接口(Target) :使用者能够直接使用的接口。以处理遗留代码为例,Target 就是最新定义的业务接口。
  • 需要适配的类/要使用的实现类(Adaptee):定义了真正要执行的业务逻辑,但是其接口不能被使用者直接使用。这里依然以处理遗留代码为例,Adaptee 就是遗留业务实现,由于编写 Adaptee 的时候还没有定义 Target 接口,所以 Adaptee 无法实现 Target 接口
  • 适配器(Adapter):在实现 Target 接口的同时,维护了一个指向 Adaptee 对象的引用。Adapter 底层会依赖 Adaptee 的逻辑来实现 Target 接口的功能,这样就能够复用 Adaptee 类中的遗留逻辑来完成业务。

适配器模式带来的最大好处就是复用已有的逻辑,避免直接去修改 Adaptee 实现的接口,这符合开放-封闭原则(也就是程序要对扩展开放、对修改关闭)

MyBatis 使用的日志接口是自己定义的 Log 接口,但是 Apache Commons Logging、Log4j、Log4j2 等日志框架提供给用户的都是自己的 Logger 接口。为了统一这些第三方日志框架,MyBatis 使用适配器模式添加了针对不同日志框架的 Adapter 实现,使得第三方日志框架的 Logger 接口转换成 MyBatis 中的 Log 接口,从而实现集成第三方日志框架打印日志的功能。

日志模块

MyBatis 自定义的 Log 接口位于 org.apache.ibatis.logging 包中,相关的适配器也位于该包中

首先是 LogFactory 工厂类,它负责创建 Log 对象。这些 Log 接口的实现类中,就包含了多种第三方日志框架的适配器,如下图所示:

在这里插入图片描述
在 LogFactory 类中有一段静态代码块,其中会依次加载各个第三方日志框架的适配器。在静态代码块执行的 tryImplementation() 方法中,首先会检测 logConstructor 字段是否为空,如果不为空,则表示已经成功确定当前使用的日志框架,直接返回;如果为空,则在当前线程中执行传入的 Runnable.run() 方法,尝试确定当前使用的日志框架。

public static synchronized void useJdkLogging() {setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);}
private static void setImplementation(Class<? extends Log> implClass) {try {// 获取implClass这个适配器的构造方法Constructor<? extends Log> candidate = implClass.getConstructor(String.class);// 尝试加载implClass这个适配器,加载失败会抛出异常Log log = candidate.newInstance(LogFactory.class.getName());// 加载成功,则更新logConstructor字段,记录适配器的构造方法logConstructor = candidate;} catch (Throwable t) {throw new LogException("Error setting Log implementation.  Cause: " + t, t);}}

下面我们以 Jdk14LoggingImpl 为例介绍一下 MyBatis Log 接口的实现。

Jdk14LoggingImpl 作为 Java Logging 的适配器,在实现 MyBatis Log 接口的同时,在内部还封装了一个 java.util.logging.Logger 对象(这是 JDK 提供的日志框架),如下图所示:

在这里插入图片描述
Jdk14LoggingImpl 继承关系图

Jdk14LoggingImpl 对 Log 接口的实现也比较简单,其中会将日志输出操作委托给底层封装的java.util.logging.Logger 对象的相应方法,这与前文介绍的典型适配器模式的实现完全一致。Jdk14LoggingImpl 中的核心实现以及注释如下:

public class Jdk14LoggingImpl implements Log {// 指向一个java.util.logging.Logger对象private final Logger log;public Jdk14LoggingImpl(String clazz) {// 初始化log字段log = Logger.getLogger(clazz);}@Overridepublic void error(String s, Throwable e) {// 全部调用依赖java.util.logging.Logger对象进行实现log.log(Level.SEVERE, s, e);}// 省略其他级别的日志输出方法}

在 MyBatis 的 org.apache.ibatis.logging 包下面,除了集成三方日志框架的适配器实现之外,还有一个 jdbc 包,这个包的功能不是将日志写入数据库中,而是将数据库操作涉及的信息通过指定的 Log 打印到日志文件中。我们可以通过这个包,将执行的 SQL 语句、SQL 绑定的参数、SQL 执行之后影响的行数等信息,统统打印到日志中,这个功能主要是在测试环境进行调试的时候使用,很少在线上开启,因为这会产生非常多的日志,拖慢系统性能。

代理模式

在后面即将介绍的 org.apache.ibatis.logging.jdbc 包中,使用到了 JDK 动态代理的相关知识,所以这里我们就先来介绍一下经典的静态代理模式,以及 JDK 提供的动态代理。

1、静态代理模式

经典的静态代理模式,其类图如下所示:

在这里插入图片描述
从该类图中,你可以看到与代理模式相关的三个核心角色。

  • Subject:程序中的业务接口,定义了相关的业务方法。
  • RealSubject:实现了 Subject 接口的业务实现类,其实现中完成了真正的业务逻辑。
  • Proxy:代理类,实现了 Subject 接口,其中会持有一个 Subject 类型的字段,指向一个 RealSubject 对象。

在使用的时候,会将 RealSubject 对象封装到 Proxy 对象中,然后访问 Proxy 的相关方法,而不是直接访问 RealSubject 对象。在 Proxy 的方法实现中,不仅会调用 RealSubject 对象的相应方法完成业务逻辑,还会在 RealSubject 方法执行前后进行预处理和后置处理。

通过对代理模式的描述可知,Proxy 能够控制使用方对 RealSubject 对象的访问,或是在执行业务逻辑之前执行统一的预处理逻辑,在执行业务逻辑之后执行统一的后置处理逻辑。

代理模式除了实现访问控制以外,还能用于实现延迟加载。例如,查询数据库涉及网络 I/O 和磁盘 I/O,会是一个比较耗时的操作,有些时候从数据库加载到内存的数据,也并非系统真正会使用到的数据,所以就有了延迟加载这种优化操作。

延迟加载可以有效地避免数据库资源的浪费,其主要原理是:用户在访问数据库时,会立刻拿到一个代理对象,此时并没有执行任何 SQL 到数据库中查询数据,代理对象中自然也不会包含任何真正的有效数据;当用户真正需要使用数据时,会访问代理对象,此时会由代理对象去执行 SQL,完成数据库的查询。MyBatis 也提供了延迟加载功能,原理大同小异,具体的实现方式也是通过代理实现的。

针对每个 RealSubject 类,都需要创建一个 Proxy 代理类,当 RealSubject 这种需要被代理的类变得很多的时候,相应地就需要定义大量的 Proxy 类,这也是经典代理模式面临的一个问题。JDK 动态代理可以有效地解决这个问题,所以接下来我们就来一起分析 JDK 动态代理的核心原理。

2、JDK动态代理

JDK 动态代理的核心是 InvocationHandler 接口。这里我先给出了一个 InvocationHandler 的示例实现,如下所示:

public class DemoInvokerHandler implements InvocationHandler {private Object target; // 真正的业务对象,也就是RealSubject对象// DemoInvokerHandler构造方法public DemoInvokerHandler(Object target) { this.target = target;}public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {... // 在执行业务逻辑之前的预处理逻辑Object result = method.invoke(target, args);... // 在执行业务逻辑之后的后置处理逻辑return result;}public Object getProxy() {// 创建代理对象return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),target.getClass().getInterfaces(), this);}}

接下来,我们可以创建一个 main() 方法来模拟使用方创建并使用 DemoInvokerHandler 动态生成代理对象,示例代码如下:

public class Main {public static void main(String[] args) {Subject subject = new RealSubject();DemoInvokerHandler invokerHandler = new DemoInvokerHandler(subject);// 获取代理对象Subject proxy = (Subject) invokerHandler.getProxy();// 调用代理对象的方法,它会调用DemoInvokerHandler.invoke()方法proxy.operation();}}

现在假设有多个业务逻辑类,需要相同的预处理逻辑和后置处理逻辑,那么只需要提供一个 InvocationHandler 接口实现类即可。在程序运行过程中,JDK 动态代理会为每个业务类动态生成相应的代理类实现,并加载到 JVM 中,然后创建对应的代理实例对象。

下面我们就接着来深入分析一下 JDK 动态代理底层动态创建代理类的原理。不同 JDK 版本 Proxy 类的实现会有些许差异,但总体的核心思路基本一致,这里我们就以 JDK 1.8.0 版本为例进行说明。

首先,从前面的示例代码中可以看出,JDK 动态代理的入口方法是 Proxy.newProxyInstance(),这个静态方法有以下三个参数。

  • loader(ClassLoader 类型):加载动态生成的代理类的类加载器。
  • interfaces(Class[] 类型):业务类实现的接口
  • h(InvocationHandler 类型):自定义的 InvocationHandler 对象。

下面进入 Proxy.newProxyInstance() 方法,查看其具体实现如下:


public static Object newProxyInstance(ClassLoader loader,Class[] interfaces, InvocationHandler h) throws IllegalArgumentException {final Class<?>[] intfs = interfaces.clone();... // 省略权限检查等代码Class<?> cl = getProxyClass0(loader, intfs);  // 获取代理类... // 省略try/catch代码块和相关异常处理// 获取代理类的构造方法final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;return cons.newInstance(new Object[]{h});  // 创建代理对象}

从 newProxyInstance() 方法的具体实现代码中我们可以看到,JDK 动态代理是在 getProxyClass0() 方法中完成代理类的生成和加载。getProxyClass0() 方法的具体实现如下:

private static Class getProxyClass0 (ClassLoader loader, Class... interfaces) {// 边界检查,限制接口数量(略)// 如果指定的类加载器中已经创建了实现指定接口的代理类,则查找缓存;// 否则通过ProxyClassFactory创建实现指定接口的代理类return proxyClassCache.get(loader, interfaces);}

proxyClassCache 是定义在 Proxy 类中一个静态字段,它是 WeakCache 类型的集合,用于缓存已经创建过的代理类,具体定义如下:

private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache= new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

WeakCache.get() 方法会首先尝试从缓存中查找代理类,如果查找失败,则会创建相应的 Factory 对象并调用其 get() 方法获取代理类。Factory 是 WeakCache 中的内部类,在 Factory.get() 方法中会通过 ProxyClassFactory.apply() 方法创建并加载代理类。

在 ProxyClassFactory.apply() 方法中,首先会检测代理类需要实现的接口集合,然后确定代理类的名称,之后创建代理类并将其写入文件中,最后加载代理类,返回对应的 Class 对象用于后续的实例化代理类对象。该方法的具体实现如下:

public Class apply(ClassLoader loader, Class[] interfaces) {// ... 对interfaces集合进行一系列检测(略)// ... 选择定义代理类的包名(略)// 代理类的名称是通过包名、代理类名称前缀以及编号这三项组成的long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;// 生成代理类,并写入文件byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);// 加载代理类,并返回Class对象return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);}

ProxyGenerator.generateProxyClass() 方法会按照指定的名称和接口集合生成代理类的字节码,并根据条件决定是否保存到磁盘上。该方法的具体代码如下:

public static byte[] generateProxyClass(final String name,Class[] interfaces) {ProxyGenerator gen = new ProxyGenerator(name, interfaces);// 动态生成代理类的字节码,具体生成过程不再详细介绍final byte[] classFile = gen.generateClassFile();// 如果saveGeneratedFiles值为true,会将生成的代理类的字节码保存到文件中if (saveGeneratedFiles) { java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() {public Void run() {// 省略try/catch代码块FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class");file.write(classFile);file.close();return null;}});}return classFile; // 返回上面生成的代理类的字节码}

最后,为了清晰地看到 JDK 动态生成的代理类的真正代码,我们需要将上述生成的代理类的字节码进行反编译。上述示例为 RealSubject 生成的代理类,反编译后得到的代码如下:

public final class $Proxy143 extends Proxy implements Subject {  // 实现了Subject接口// 这里省略了从Object类继承下来的相关方法和属性private static Method m3;static {// 省略了try/catch代码块// 记录了operation()方法对应的Method对象m3 = Class.forName("design.proxy.Subject").getMethod("operation", new Class[0]);}// 构造方法的参数就是我们在示例中使用的DemoInvokerHandler对象public $Proxy11(InvocationHandler var1) throws {super(var1); }public final void operation() throws {// 省略了try/catch代码块// 调用DemoInvokerHandler对象的invoke()方法// 最终调用RealSubject对象的对应方法super.h.invoke(this, m3, (Object[]) null);}}

到此为止,JDK 动态代理的基本使用以及核心原理就分析完了。这里我做一个简单的总结,JDK 动态代理的实现原理是:动态创建代理类,然后通过指定类加载器进行加载。在创建代理对象时,需要将 InvocationHandler 对象作为构造参数传入;当调用代理对象时,会调用 InvocationHandler.invoke() 方法,从而执行代理逻辑,最终调用真正业务对象的相应方法。

JDBC Logger

了解了代理模式以及 JDK 动态代理的基础知识之后,下面我们开始分析 org.apache.ibatis.logging.jdbc 包中的内容

首先来看其中最基础的抽象类—— BaseJdbcLogger,它是 jdbc 包下其他 Logger 类的父类,继承关系如下图所示:

在这里插入图片描述
在 BaseJdbcLogger 这个抽象类中,定义了 SET_METHODS 和 EXECUTE_METHODS 两个 Set 类型的集合。其中,SET_METHODS 用于记录绑定 SQL 参数涉及的全部 set*() 方法名称,例如 setString() 方法、setInt() 方法等。EXECUTE_METHODS 用于记录执行 SQL 语句涉及的所有方法名称,例如 execute() 方法、executeUpdate() 方法、executeQuery() 方法、addBatch() 方法等。这两个集合都是在 BaseJdbcLogger 的静态代码块中被填充的。

从上面的 BaseJdbcLogger 继承关系图中可以看到,BaseJdbcLogger 的子类同时会实现 InvocationHandler 接口。

我们先来看其中的 ConnectionLogger 实现,其底层维护了一个 Connection 对象的引用,在ConnectionLogger.newInstance() 方法中会使用 JDK 动态代理的方式为这个 Connection 对象创建相应的代理对象。

invoke() 方法是代理对象的核心方法,在该方法中,ConnectionLogger 会为 prepareStatement()、prepareCall()、createStatement() 三个方法添加代理逻辑。下面来看 invoke() 方法的具体实现,具体代码以及注释如下:

public Object invoke(Object proxy, Method method, Object[] params)throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {// 如果调用的是从Object继承的方法,则直接调用,不做任何拦截return method.invoke(this, params);}// 调用prepareStatement()方法、prepareCall()方法的时候,// 会在创建PreparedStatement对象之后,用PreparedStatementLogger为其创建代理对象if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {if (isDebugEnabled()) {// 通过statementLog这个Log输出日志debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);}PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);return stmt;} else if ("createStatement".equals(method.getName())) {// 调用createStatement()方法的时候,// 会在创建Statement对象之后,用StatementLogger为其创建代理对象Statement stmt = (Statement) method.invoke(connection, params);stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);return stmt;} else {// 除了上述三个方法之外,其他方法的调用将直接传递给底层Connection对象的相应方法处理return method.invoke(connection, params);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}

下面我们来看 PreparedStatementLogger,在其 invoke() 方法中调用了 SET_METHODS 集合中的方法、EXECUTE_METHODS 集合中的方法或 getResultSet() 方法时,会添加相应的代理逻辑。StatementLogger 中的 Invoke() 方法实现与之类似,这里就不再赘述。

最后我们再看下 ResultSetLogger 对 InvocationHandler 接口的实现,其中会针对 ResultSet.next() 方法进行后置处理,主要是打印结果集中每一行数据以及统计结果集总行数等信息,具体实现和注释如下:

public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {// 如果调用Object的方法,则直接调用,不做任何其他处理return method.invoke(this, params);}Object o = method.invoke(rs, params);// 针对ResultSet.next()方法进行后置处理if ("next".equals(method.getName())) { if ((Boolean) o) { // 检测next()方法的返回值,确定是否还存在下一行数据rows++; // 记录ResultSet中的行数if (isTraceEnabled()) {// 获取数据集的列元数据ResultSetMetaData rsmd = rs.getMetaData();// 获取数据集的列数final int columnCount = rsmd.getColumnCount();if (first) { // 如果是数据集的第一行数据,会输出表头信息first = false;// 这里除了输出表头,还会记录BLOB等超大类型的列名printColumnHeaders(rsmd, columnCount);}// 输出当前遍历的这行记录,这里会过滤掉超大类型列的数据,不进行输出printColumnValues(columnCount);}} else { // 完成结果集的遍历之后,这里会在日志中输出总行数debug("     Total: " + rows, false);}}clearColumnInfo(); // 清空column*集合return o;} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}

总结

我们主要介绍的是 MyBatis 基础模块中的日志模块。

  • 首先,介绍了适配器模式的核心知识点,这也是日志模块底层的设计思想。
  • 然后,说明了日志模块是如何基于适配器模式集成多种三方日志框架的。
  • 接下来,详细讲解了静态代理模式以及 JDK 动态代理的实现原理。
  • 最后,深入分析了 JDBC Logger 是如何基于 JDK 动态代理实现日志功能的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/577649.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

边缘计算AI智能盒子的视频源必须是固定点监控摄像头吗?

边缘计算AI盒子的视频输入源&#xff0c;要求是RTSP或者GB28181&#xff0c;可以是固定点监控摄像头&#xff08;枪机、球机等&#xff09;&#xff0c;也可以是移动摄像头&#xff0c;例如执法记录仪、智能安全帽、布控球等&#xff0c;但由于RTSP输入要求摄像头有固定IP&…

使用 Postman 进行并发请求:实用教程与最佳实践

背景介绍 最近&#xff0c;我们发起了一个在线图书管理系统的项目。我负责的一个关键模块包括三个主要后台接口&#xff1a; 实现对books数据的检索。实施对likes数据的获取。通过collections端点访问数据。 应对高流量的挑战 在设计并部署接口时&#xff0c;我们不可避免地…

西南科技大学计算机网络实验二 (IP协议分析与以太网协议分析)

一、实验目的 通过分析由跟踪执行traceroute程序发送和接收捕获得到的IP 数据报,深入研究在IP 数据报中的各种字段,理解IP协议。基于ARP命令和Ethereal进行以太网帧捕获与分析,理解和熟悉ARP协议原理以及以太网帧格式。 二、实验环境 与因特网连接的计算机网络系统;主机操…

编写第一个APP自动化脚本 appium_helloworld ,将脚本跑起来

一、前置说明 我们把学习 Appium 的第一个脚本称为 appium_helloworld&#xff0c;它用于展示 Appium 的基本用法&#xff0c;验证配置和环境是否正确。 Appium 自动化操作 APP 的基本流程&#xff08;Android平台&#xff09;&#xff1a; 启动 Appium Serveradb 连接设备&…

【ctf】whireshark流量分析之tcp_杂篇

目录 简介 常考 图片类 提取png.pcap&#xff08;常规&#xff09; 异常的流量分析&#xff08;*&#xff0c;特殊&#xff09; john-in-the-middle&#xff08;特殊&#xff09; ​编辑 zip类 1.pcap&#xff08;常规&#xff09; 方法1&#xff08;常规提取压缩包&…

[足式机器人]Part4 南科大高等机器人控制课 CH10 Bascis of Stability Analysis

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;CLEAR_LAB 笔者带更新-运动学 课程主讲教师&#xff1a; Prof. Wei Zhang 南科大高等机器人控制课 Ch10 Bascis of Stability Analysis 1. Background1.1 What is Stability Analysis1.2 General ODE Models for Dynamic…

Web前端VScode/Vue3/git/nvm/node开发环境安装

目录 1 基本配置 2 安装vscode 3 安装vue 4 配置bash 5 安装nvm 6 安装node 7 安装yarn 8 新建项目 9 运行helloworld 1 基本配置 本篇是为了做前端开发的环境而写。使用的操作系统是windows 10 64位 2 安装vscode 现在做vue和node基本就是vscode和webstorm&#x…

Cesium.js三维地图的实现(依托天地图CDN文件)

零、技术选型&#xff1a; Vue2、VueCli5、天地图、Cesium.js 一、通过天地图官网案例实现 需要引入天地图官方提供的CDN链接访问Cesium.js相关文件 相关文件&#xff1a; https://api.tianditu.gov.cn/cdn/demo/sanwei/static/cesium/Cesium.js https://api.tianditu.gov.cn/…

【WPF.NET开发】数据绑定应用场景

目录 1、实现属性更改通知 示例 2、双向绑定​​​更新源 示例 3、对分层数据使用主-从模式 示例 4、对分层 XML 数据使用主-从模式 示例 5、绑定两个控件的属性 示例 6、创建和绑定到 ObservableCollection 示例 7、使用 XMLDataProvider 和 XPath 查询绑定到 XML…

喜报!酷克数据携手中移在线入选2023大数据“星河”数据库优秀案例

12月20日-21日&#xff0c;由中国信通院、中国通信标准化协会主办&#xff0c;中国通信标准化协会大数据技术标准推进委员会承办的“2023数据资产管理大会”在京召开。 在会上&#xff0c;第七届大数据“星河&#xff08;Galaxy&#xff09;”案例评选结果正式公布。中移在线服…

Local Binary Convolutional Neural Networks (LBCNN)

论文&#xff1a;https://arxiv.org/abs/1608.06049 代码&#xff1a;GitHub - juefeix/lbcnn.torch: Torch implementation of CVPR17 - Local Binary Convolutional Neural Networks http://xujuefei.com/lbcnn.html 摘要&#xff1a; 我们提出了局部二值卷积(LBC)&#x…

【ScienceAI Weekly】DeepMind最新研究再登Nature;我国首个自研地球系统模型开源;谷歌推出医疗保健模型

AI for Science 的新成果、新动态、新视角抢先看—— * DeepMind 最新研究 FunSearch 登 Nature * 谷歌推出医疗保健行业模型 MedLM * 晶泰科技冲刺港交所&#xff0c;AI机器人赋能 AI for Science * GHDDI 与微软研究院科学智能中心达成合作 * 用于地震学处理分析的 AI 工…

【华为机试】2023年真题B卷(python)-分月饼

一、题目 题目描述&#xff1a; 中秋节公司分月饼&#xff0c;m个员工&#xff0c;买了n个月饼&#xff0c;m<n&#xff0c;每个员工至少分1个月饼&#xff0c;但可以分多个&#xff0c;单人份到最多月饼的个数为Max1&#xff0c;单人分到第二多月饼的个数是Max2&#xff0c…

python(上半部分)

第一部分 1、input()语句默认结果是字符串 2、type()可以判断变量的类型 3、input()输出语句 &#xff08;默认为字符串类型&#xff09; 4、命名规则&#xff1a;中文、英文、数字、_&#xff0c;数字不可开头&#xff0c;大小写敏感。 5、 %s&#xff1a;将内容转换成…

Java并发(二十一)----wait notify介绍

1、小故事 - 为什么需要 wait 由于条件不满足&#xff08;没烟干不了活啊&#xff0c;等小M把烟送过来&#xff09;&#xff0c;小南不能继续进行计算 但小南如果一直占用着锁&#xff0c;其它人就得一直阻塞&#xff0c;效率太低 于是老王单开了一间休息室&#xff08;调…

vue场景 无分页列表条件过滤,子组件多选来自父组件的列表

日常开发中&#xff0c;经常会遇到下面场景&#xff1a; 页面加载一个无分页列表&#xff0c;同时工具栏设置多个条件可对列表过滤的场景(典型的就是关键字模糊查询)父组件传给子组件列表&#xff0c;子组件中需要多选列表多选&#xff0c;选择结果返回父组件 1 无分页列表过…

行业首台7英寸彩屏,掌阅iReader Color 7 发布

12月22日消息&#xff0c;掌阅iReader 继2021年发布 C6 Pro 后&#xff0c;时隔2年再次推出彩屏系列新产品 Color 7&#xff0c;该产品为彩色电子纸智能阅读本&#xff0c;采用 Kaleido3 新一代彩色电子纸技术&#xff0c;黑白像素密度为 300PPI &#xff0c;彩色像素密度为 15…

并发程序设计--D1进程的创建和回收

进程和程序内容区别 进程包含的内容&#xff1a; BSS段&#xff1a;存放程序中未初始化的全局变量 数据段&#xff1a;已初始化的全局变量 代码段&#xff1a;程序执行代码 堆&#xff08;heap&#xff09;&#xff1a;malloc等函数分配内存 栈(stack)&#xff1a;局部变量…

让学习成为一种生活方式:数字化助力终身学习

12月以来,“让学习成为一种生活方法”全民终身学习活动在北京、安徽、湖北、湖南、云南等地密集启动,而依托数字化赋能终身学习成为共同关键词。 近年来,随着教育现代化体系的不断深入推进,数字化转型成为建设高质量终身学习体系的重要“推动力”,数字技术与大数据的应用汇聚了…

2023新能源汽车,吵得越凶,卖得越多

作者 | 辰纹 来源 | 洞见新研社 2023年的汽车行业很残酷&#xff0c;合资大败退&#xff0c;市场份额被自主品牌大幅渗透&#xff0c;三菱退出中国市场&#xff0c;成为真实写照。 新能源车企&#xff0c;威马领头&#xff0c;天际、自游家NIUTRON、恒驰、爱驰、雷丁等造车新…