.net core 引用jar_Python一键转Jar包,Java调用Python新姿势!

粉丝朋友们,不知道大家看故事看腻了没(要是没腻可一定留言告诉我^_^),今天这篇文章换换口味,正经的来写写技术文。言归正传,咱们开始吧!

今天的这篇文章,聊一个轩辕君之前工作中遇到的需求:如何在Java中调用Python代码?

要不要先Mark一下,说不定将来哪天就用上了呢?

本文结构:

- 需求背景- 进击的 Python- Java 和 Python
- 给 Python 加速- 寻找方向- Jython?
- Python->Native 代码- 整体思路- 实际动手- 自动化
- 关键问题- import 的问题- Python GIL 问题
- 测试效果
- 总结

需求背景

进击的 Python

随着人工智能的兴起,Python 这门曾经小众的编程语言可谓是焕发了第二春。

cb56ae1212a604ce5d85bb9af22017eb.png

以 tensorflow、pytorch 等为主的机器学习/深度学习的开发框架大行其道,助推了 python 这门曾经以爬虫见长(python 粉别生气)的编程语言在 TIOBE 编程语言排行榜上一路披荆斩棘,坐上前三甲的宝座,仅次于 Java 和 C,将 C++、JavaScript、PHP、C#等一众劲敌斩落马下。

3ecff0dd1cf1e881ca1001cd6745c38c.png

55208582a25e595f0a7d55f9febfe691.png

当然,轩辕君向来是不提倡编程语言之间的竞争对比,每一门语言都有自己的优势和劣势,有自己应用的领域。另一方面,TIOBE 统计的数据也不能代表国内的实际情况,上面的例子只是侧面反映了 Python 这门语言如今的流行程度。

Java 还是 Python

说回咱们的需求上来,如今在不少的企业中,同时存在 Python 研发团队和 Java 研发团队,Python 团队负责人工智能算法开发,而 Java 团队负责算法工程化,将算法能力通过工程化包装提供接口给更上层的应用使用。

可能大家要问了,为什么不直接用 Java 做 AI 开发呢?要弄两个团队。其实,现在包括 TensorFlow 在内的框架都逐渐开始支持 Java 平台,用 Java 做 AI 开发也不是不行(其实已经有不少团队在这样做了),但限于历史原因,做 AI 开发的人本就不多,而这一些人绝大部分都是 Python 技术栈入坑,Python 的 AI 开发生态已经建设的相对完善,所以造成了在很多公司中算法团队和工程化团队不得不使用不同的语言。

现在该抛出本文的重要问题:Java 工程化团队如何调用 Python 的算法能力?

答案基本上只有一个:Python 通过 Django/Flask 等框架启动一个 Web 服务,Java 中通过 Restful API 与之进行交互

上面的方式的确可以解决问题,但随之而来的就是性能问题。尤其是在用户量上升后,大量并发接口访问下,通过网络访问和 Python 的代码执行速度将成为拖累整个项目的瓶颈。

当然,不差钱的公司可以用硬件堆出性能,一个不行,那就多部署几个 Python Web 服务。

那除此之外,有没有更实惠的解决方案呢?这就是这篇文章要讨论的问题。

给 Python 加速

寻找方向

上面的性能瓶颈中,拖累执行速度的原因主要有两个:

  • 通过网络访问,不如直接调用内部模块快
  • Python 是解释执行,快不起来

众所周知,Python 是一门解释型脚本语言,一般来说,在执行速度上:

解释型语言 < 中间字节码语言 < 本地编译型语言

自然而然,我们要努力的方向也就有两个:

  • 能否不通过网络访问,直接本地调用
  • Python 不要解释执行

结合上面的两个点,我们的目标也清晰起来:

将 Python 代码转换成 Java 可以直接本地调用的模块

对于 Java 来说,能够本地调用的有两种:

  • Java 代码包
  • Native 代码模块

其实我们通常所说的 Python 指的是 CPython,也就是由 C 语言开发的解释器来解释执行。而除此之外,除了 C 语言,不少其他编程语言也能够按照 Python 的语言规范开发出虚拟机来解释执行 Python 脚本:

  • CPython: C 语言编写的解释器
  • Jython: Java 编写的解释器
  • IronPython: .NET 平台的解释器
  • PyPy: Python 自己编写的解释器(鸡生蛋,蛋生鸡)

Jython?

如果能够在 JVM 中直接执行 Python 脚本,与 Java 业务代码的交互自然是最简单不过。但随后的调研发现,这条路很快就被堵死了:

  • 不支持 Python3.0 以上的语法
  • python 源码中若引用的第三方库包含 C 语言扩展,将无法提供支持,如 numpy 等

这条路行不通,那还有一条:把 Python 代码转换成 Native 代码块,Java 通过 JNI 的接口形式调用。

Python -> Native 代码

整体思路

先将 Python 源代码转换成 C 代码,之后用 GCC 编译 C 代码为二进制模块 so/dll,接着进行一次 Java Native 接口封装,使用 Jar 打包命令转换成 Jar 包,然后 Java 便可以直接调用。

07d450948d50ff653d6c0feb2c67b1fa.png

流程并不复杂,但要完整实现这个目标,有一个关键问题需要解决:

Python 代码如何转换成 C 代码?

终于要轮到本文的主角登场了,将要用到的一个核心工具叫:Cython

请注意,这里的Cython和前面提到的CPython不是一回事。CPython 狭义上是指 C 语言编写的 Python 解释器,是 Windows、Linux 下我们默认的 Python 脚本解释器。

而 Cython 是 Python 的一个第三方库,你可以通过pip install Cython进行安装。

官方介绍 Cython 是一个 Python 语言规范的超集,它可以将 Python+C 混合编码的.pyx 脚本转换为 C 代码,主要用于优化 Python 脚本性能或 Python 调用 C 函数库。

听上去有点复杂,也有点绕,不过没关系,get 一个核心点即可:Cython 能够把 Python 脚本转换成 C 代码

来看一个实验:

# FileName: test.py
def TestFunction():print("this is print from python script")

将上述代码通过 Cython 转化,生成 test.c,长这个样子:

28279f92b8cdfd4633bff35376e32c93.png

代码非常长,而且不易读,这里仅截图示意。

实际动手

1.准备 Python 源代码

# FileName: Test.py
# 示例代码:将输入的字符串转变为大写
def logic(param):print('this is a logic function')print('param is [%s]' % param)return param.upper()# 接口函数,导出给Java Native的接口
def JNI_API_TestFunction(param):print("enter JNI_API_test_function")result = logic(param)print("leave JNI_API_test_function")return result

注意1:这里在 python 源码中使用一种约定:以JNI_API_为前缀开头的函数表示为Python代码模块要导出对外调用的接口函数,这样做的目的是为了让我们的 Python 一键转 Jar 包系统能自动化识别提取哪些接口作为导出函数。

注意2:这一类接口函数的输入是一个 python 的 str 类型字符串,输出亦然,如此可便于移植以往通过JSON形式作为参数的 RESTful 接口。使用JSON的好处是可以对参数进行封装,支持多种复杂的参数形式,而不用重载出不同的接口函数对外调用。

注意3:还有一点需要说明的是,在接口函数前缀JNI_API_的后面,函数命名不能以 python 惯有的下划线命名法,而要使用驼峰命名法,注意这不是建议,而是要求,原因后续会提到。

2.准备一个 main.c 文件

这个文件的作用是对 Cython 转换生成的代码进行一次封装,封装成 Java JNI 接口形式的风格,以备下一步 Java 的使用。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include <Python.h>
#include <stdio.h>#ifndef _Included_main
#define _Included_main
#ifdef __cplusplus
extern "C" {
#endif#if PY_MAJOR_VERSION < 3
# define MODINIT(name)  init ## name
#else
# define MODINIT(name)  PyInit_ ## name
#endif
PyMODINIT_FUNC  MODINIT(Test)(void);JNIEXPORT void JNICALL Java_Test_initModule
(JNIEnv *env, jobject obj) {PyImport_AppendInittab("Test", MODINIT(Test));Py_Initialize();PyRun_SimpleString("import os");PyRun_SimpleString("__name__ = "__main__"");PyRun_SimpleString("import sys");PyRun_SimpleString("sys.path.append('./')");PyObject* m = PyInit_Test_Test();if (!PyModule_Check(m)) {PyModuleDef *mdef = (PyModuleDef *) m;PyObject *modname = PyUnicode_FromString("__main__");m = NULL;if (modname) {m = PyModule_NewObject(modname);Py_DECREF(modname);if (m) PyModule_ExecDef(m, mdef);}}PyEval_InitThreads();
}JNIEXPORT void JNICALL Java_Test_uninitModule
(JNIEnv *env, jobject obj) {Py_Finalize();
}JNIEXPORT jstring JNICALL Java_Test_testFunction
(JNIEnv *env, jobject obj, jstring string)
{const char* param = (char*)(*env)->GetStringUTFChars(env, string, NULL);static PyObject *s_pmodule = NULL;static PyObject *s_pfunc = NULL;if (!s_pmodule || !s_pfunc) {s_pmodule = PyImport_ImportModule("Test");s_pfunc = PyObject_GetAttrString(s_pmodule, "JNI_API_testFunction");}PyObject *pyRet = PyObject_CallFunction(s_pfunc, "s", param);(*env)->ReleaseStringUTFChars(env, string, param);if (pyRet) {jstring retJstring = (*env)->NewStringUTF(env, PyUnicode_AsUTF8(pyRet));Py_DECREF(pyRet);return retJstring;} else {PyErr_Print();return (*env)->NewStringUTF(env, "error");}
}
#ifdef __cplusplus
}
#endif
#endif

这个文件中一共有3个函数:

  • Java_Test_initModule: python初始化工作
  • Java_Test_uninitModule: python反初始化工作
  • Java_Test_testFunction:真正的业务接口,封装了对原来Python中定义对JNI_API_testFuncion函数的调用,同时要负责JNI层面的参数jstring类型的转换。

根据 JNI 接口规范,native 层面的 C 函数命名需要符合如下的形式:

// QualifiedClassName: 全类名
// MethodName: JNI接口函数名
void
JNICALL
Java_QualifiedClassName_MethodName(JNIEnv*, jobject);

所以在main.c文件中对定义需要向上面这样命名,这也是为什么前面强调python接口函数命名不能用下划线,这会导致JNI接口找不到对应的native函数。

3.使用 Cython 工具编译生成动态库

补充做一个小小的准备工作:把Python源码文件的后缀从.py改成.pyx

python源代码Test.pyx和main.c文件都准备就绪,接下来便是Cython登场的时候了,它将会将所有pyx的文件自动转换成.c文件,并结合我们自己的main.c文件,内部调用gcc生成一个动态二进制库文件。

Cython 的工作需要准备一个 setup.py 文件,配置好转换的编译信息,包括输入文件、输出文件、编译参数、包含目录、链接目录,如下所示:

from distutils.core import setup
from Cython.Build import cythonize
from distutils.extension import Extensionsourcefiles = ['Test.pyx', 'main.c']extensions = [Extension("libTest", sourcefiles,include_dirs=['/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/include','/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/include/darwin/','/Library/Frameworks/Python.framework/Versions/3.6/include/python3.6m'],library_dirs=['/Library/Frameworks/Python.framework/Versions/3.6/lib/'],libraries=['python3.6m'])]setup(ext_modules=cythonize(extensions, language_level = 3))

注意:这里涉及Python二进制代码的编译,需要链接Python的库

注意:这里涉及JNI相关数据结构定义,需要包含Java JNI目录

setup.py文件准备就绪后,便执行如下命令,启动转换+编译工作:

python3.6 setup.py build_ext --inplace

生成我们需要的动态库文件:libTest.so

4.准备Java JNI调用的接口文件

Java业务代码使用需要定义一个接口,如下所示:

// FileName: Test.java
public class Test {public native void initModule();public native void uninitModule();public native String testFunction(String param);
}

到这一步,其实已经实现了在Java中调用的目的了,注意调用业务接口之前,需要先调用initModule进行native层面的Python初始化工作。

import Test;
public class Demo {public void main(String[] args) {System.load("libTest.so");Test tester = new Test();tester.initModule();String result = tester.testFunction("this is called from java");tester.uninitModule();System.out.println(result);}
}

输出:

enter JNI_API_test_function
this is a logic function
param is [this is called from java]
leave JNI_API_test_function
THIS IS CALLED FROM JAVA!

成功实现了在Java中调用Python代码!

5.封装为 Jar 包

做到上面这样还不能满足,为了更好的使用体验,我们再往前一步,封装成为Jar包。

首先原来的JNI接口文件需要再扩充一下,加入一个静态方法loadLibrary,自动实现so文件的释放和加载。

// FileName: Test.java
public class Test {public native void initModule();public native void uninitModule();public native String testFunction(String param);public synchronized static void loadLibrary() throws IOException {// 实现略...}
}

接着将上面的接口文件转换成java class文件:

javac Test.java

最后,准备将class文件和so文件放置于Test目录下,打包:

jar -cvf Test.jar ./Test

自动化

上面5个步骤如果每次都要手动来做着实是麻烦!好在,我们可以编写Python脚本将这个过程完全的自动化,真正做到Python一键转换Jar包

限于篇幅原因,这里仅仅提一下自动化过程的关键:

  • 自动扫描提取python源代码中需要导出的接口函数
  • main.c、setup.py和JNI接口java文件都需要自动化生成(可以定义模板+参数形式快速构建),需要处理好各模块名、函数名对应关系

关键问题

1.import 问题

上面演示的案例只是一个单独的 py 文件,而实际工作中,我们的项目通常是具有多个 py 文件,并且这些文件通常是构成了复杂的目录层级,互相之间各种 import 关系,错综复杂。

Cython 这个工具有一个最大的坑在于:经过其处理的文件代码中会丢失代码文件的目录层级信息,如下图所示,C.py 转换后的代码和 m/C.py 生成的代码没有任何区别。

1934778277fb81113ba4f68862b4ab75.png

这就带来一个非常大的问题:A.py 或 B.py 代码中如果有引用 m 目录下的 C.py 模块,目录信息的丢失将导致二者在执行 import m.C 时报错,找不到对应的模块!

幸运的是,经过实验表明,在上面的图中,如果 A、B、C 三个模块处于同一级目录下时,import 能够正确执行。

轩辕君曾经尝试阅读 Cython 的源代码,并进行修改,将目录信息进行保留,使得生成后的 C 代码仍然能够正常 import,但限于时间仓促,对 Python 解释器机理了解不足,在一番尝试之后选择了放弃。

在这个问题上卡了很久,最终选择了一个笨办法:将树形的代码层级目录展开成为平坦的目录结构,就上图中的例子而言,展开后的目录结构变成了

A.py
B.py
m_C.py

单是这样还不够,还需要对 A、B 中引用到 C 的地方全部进行修正为对 m_C 的引用。

这看起来很简单,但实际情况远比这复杂,在 Python 中,import 可不只有 import 这么简单,有各种各样复杂的形式:

import package
import module
import package.module
import module.class / function
import package.module.class / function
import package.*
import module.*
from module import *
from module import module
from package import *
from package import module
from package.module import class / function
...

除此之外,在代码中还可能存在直接通过模块进行引用的写法。

展开成为平坦结构的代价就是要处理上面所有的情况!轩辕君无奈之下只有出此下策,如果各位大佬有更好的解决方案还望不吝赐教。

2.Python GIL 问题

Python 转换后的 jar 包开始用于实际生产中了,但随后发现了一个问题:

每当 Java 并发数一上去之后,JVM 总是不定时出现 Crash

随后分析崩溃信息发现,崩溃的地方正是在 Native 代码中的 Python 转换后的代码中。

  • 难道是 Cython 的 bug?
  • 转换后的代码有坑?
  • 还是说上面的 import 修正工作有问题?

d1628dbc1396246b4f2923a25084e96e.png

崩溃的乌云笼罩在头上许久,冷静下来思考:为什么测试的时候正常没有发现问题,上线之后才会崩溃?

再次翻看崩溃日志,发现在 native 代码中,发生异常的地方总是在 malloc 分配内存的地方,难不成内存被破坏了?又发现测试的时候只是完成了功能性测试,并没有进行并发压力测试,而发生崩溃的场景总是在多并发环境中。多线程访问 JNI 接口,那 Native 代码将在多个线程上下文中执行。

猛地一个警觉:99%跟 Python 的 GIL 锁有关系!

e31531ba08e23727b9f856a037a83e5a.png

众所周知,限于历史原因,Python 诞生于上世纪九十年代,彼时多线程的概念还远远没有像今天这样深入人心过,Python 作为这个时代的产物一诞生就是一个单线程的产品。

虽然 Python 也有多线程库,允许创建多个线程,但由于 C 语言版本的解释器在内存管理上并非线程安全,所以在解释器内部有一个非常重要的锁在制约着 Python 的多线程,所以所谓多线程实际上也只是大家轮流来占坑。

原来 GIL 是由解释器在进行调度管理,如今被转成了 C 代码后,谁来负责管理多线程的安全呢?

由于 Python 提供了一套供 C 语言调用的接口,允许在 C 程序中执行 Python 脚本,于是翻看这套 API 的文档,看看能否找到答案。

幸运的是,还真被我找到了:

获取 GIL 锁:

13fd97d3148711098b77f3f2a7ce6d22.png

释放 GIL 锁:

202a09b576eec1d835d437914613e454.png

在 JNI 调用入口需要获得 GIL 锁,接口退出时需要释放 GIL 锁。

加入 GIL 锁的控制后,烦人的 Crash 问题终于得以解决!

测试效果

准备两份一模一样的 py 文件,同样的一个算法函数,一个通过 Flask Web 接口访问,(Web 服务部署于本地 127.0.0.1,尽可能减少网络延时),另一个通过上述过程转换成 Jar 包。

在 Java 服务中,分别调用两个接口 100 次,整个测试工作进行 10 次,统计执行耗时:

e27b0777a44026842febd33e1376508d.png

上述测试中,为进一步区分网络带来的延迟和代码执行本身的延迟,在算法函数的入口和出口做了计时,在 Java 执行接口调用前和获得结果的地方也做了计时,这样可以计算出算法执行本身的时间在整个接口调用过程中的占比。

  • 从结果可以看出,通过 Web API 执行的接口访问,算法本身执行的时间只占到了 30%+,大部分的时间用在了网络开销(数据包的收发、Flask 框架的调度处理等等)。
  • 而通过 JNI 接口本地调用,算法的执行时间占到了整个接口执行时间的 80%以上,而 Java JNI 的接口转换过程只占用 10%+的时间,有效提升了效率,减少额外时间的浪费。
  • 除此之外,单看算法本身的执行部分,同一份代码,转换成 Native 代码后的执行时间在 300~500μs,而 CPython 解释执行的时间则在 2000~4000μs,同样也是相差悬殊。

总结

本文提供了一种 Java 调用 Python 代码的新思路,仅供参考,其成熟度和稳定性还有待商榷,通过 HTTP Restful 接口访问仍然是跨语言对接的首选。

至于文中的方法,感兴趣的朋友欢迎留言交流。

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

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

相关文章

PHP版本的区别

2019独角兽企业重金招聘Python工程师标准>>> 以为这个已经写过了&#xff0c;发现没有&#xff0c;赶紧补充下。 PHP的版本&#xff0c;自从进入5以后&#xff0c;发布新版本速度明显提升很多&#xff0c;从PHP5.2开始&#xff0c;5.3 、5.4 、5.5&#xff0c;就快要…

1515 跳 - Wikioi

题目描述 Description邪教喜欢在各种各样空间内跳。现在&#xff0c;邪教来到了一个二维平面。在这个平面内&#xff0c;如果邪教当前跳到了(x,y)&#xff0c;那么他下一步可以选择跳到以下4个点&#xff1a;(x-1,y), (x1,y), (x,y-1), (x,y1)。而每当邪教到达一个点&#xff0…

快速格式化的DOS命令是什么?

https://zhidao.baidu.com/question/3269005.html Dos下的format命令是用来进行格式化的。 FORMAT A:或c:, d:等等&#xff0c;后面可以加一些参数 比如FORMAT A: /S 这是格式化并追加系统。也就是制作一个简单的启动盘。 如果给硬盘进行格式化&#xff08;重装系统前的准备工…

ADT-bundle

eclipse 弹出 Version 1.4.2_03 of the JVM not suitable for this product.Version1.6or geeater is requir 原因jdk版本过低&#xff0c;需更换高版本 ADT-bundle-linux-X86 解压后有两个文件夹eclipse 和sdk 注意&#xff1a;adb命令在platform-tools下 解压后还需修改sdk的…

合并两个有序链表算法(leetcode第21题)

题目描述&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a;输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&#xff1a;输入&#xff1a;l1 [], l2 [] 输…

修改maven本地仓库位置

2019独角兽企业重金招聘Python工程师标准>>> 首先需要安装好&#xff0c;假设安装位置在&#xff1a;D:\****\maven\apache-maven-3.3.3修改本地仓库位置&#xff0c;打开配置文件&#xff1a;D:\****\maven\apache-maven-3.3.3\conf\settings.xml<!-- localRepo…

iOS sqlite

iOS sqlite数据库操作。步骤是&#xff1a; 先加入sqlite开发库libsqlite3.dylib&#xff0c; 新建或打开数据库&#xff0c; 创建数据表&#xff0c; 插入数据&#xff0c; 查询数据并打印 1、新建项目sqliteDemo,添加使用sqlite的库libsqlite3.dylib 2、sqlite 的方法 sqlite…

ad如何设置pcb板子形状_手把手教你在PCB上添加泪滴

泪滴(Teardrops)在PCB电路板设计中&#xff0c;为了让焊盘更坚固&#xff0c;防止机械制板时焊盘与导线之间断开&#xff0c;常在焊盘和导线之间用铜膜布置一个过渡区&#xff0c;形状像泪滴&#xff0c;故常称做补泪滴&#xff08;Teardrops&#xff09;。泪滴的作用避免电路板…

华为云hcip认证试题_首信AAA认证计费系统通过华为云兼容性认证,成为华为认证级ISV伙伴...

点击上方“蓝字”关注我们首信AAA认证计费系统软件3.0在2020年9月24日通过华为云Stack 6.5(鲲鹏)的兼容性测试&#xff0c;最终获得华为技术认证书及HUAWEI COMPATEBLE相关认证徽标使用权。同时&#xff0c;首信科技获得华为认证级ISV伙伴认证证书。本次获得的产品兼容性认证及…

Netty4.x中文教程系列(二) Hello World !

在中国程序界。我们都是学着Hello World !慢慢成长起来的。逐渐从一无所知到熟悉精通的。 第二章就从Hello World 开始讲述Netty的中文教程。 首先创建一个Java项目。引入一个Netty 框架的包。这个步骤我在本系列教程的后面就不在重复了。 先上一张我示例的项目工程图给大家看一…

jq金钱如何加千分位_拼多多如何玩转场景推广

首先&#xff0c;我们先弄明白拼多多场景推广的展示以及扣费规则&#xff1a;排名规则&#xff1a;综合排名商品质量分广告出价。商品质量分点击率转化率销量交易额。扣费规则&#xff1a;扣费&#xff08;下一位的出价*下一位的商品素材点击率&#xff09;/自己的商品素材点击…

硬盘安装win10,笔者教你如何一步步从硬盘安装win10系统

https://www.ghostxpsp3.net/czxtjc/12280.html 对于没有U盘系统和光驱的用户来说&#xff0c;使用硬盘安装系统&#xff0c;无疑是最好的解决方案。今天笔者教你如何一步步从硬盘安装win10系统&#xff0c;笔者教你如何一步步从硬盘安装win10系统要保证在能进入系统的前提下进…

prometheus命令_Prometheus入门教程(一):Prometheus 快速入门

点击蓝色“陈树义”关注我哟Prometheus 是任何一个高级工程师必须要掌握的技能。那么如何从零部署一套 Prometheus 监控系统呢&#xff1f;本篇文章将从 Prometheus 的原理讲起&#xff0c;手把手带你用一个最简单的例子部署一套 Prometheus 监控系统。基本原理Prometheus 的基…

Java不定参数

先看两个简单的例子&#xff0c;来感受一下Java的不定长度参数 第一个例子&#xff1a; Java代码 public class VariArgs { public static void main(String[] args) { test(); test("aaa"); test("aaa", "bbb&q…

「mysql优化专题」主从复制面试宝典!面试官都没你懂得多!(11)

内容较多&#xff0c;可先收藏&#xff0c;目录如下&#xff1a; 一、什么是主从复制 二、主从复制的作用&#xff08;重点&#xff09; 三、主从复制的原理&#xff08;重中之重&#xff09; 四、三步轻松构建主从 五、必问面试题干货分析&#xff08;最最重要的点&#xff09…

为什么WordPress网站应尽量避免使用过多插件

前几天&#xff0c;我们在给一个客户优化其企业网站时&#xff0c;发现其网站使用了太多的WordPress插件。WP插件可以扩展网站的功能&#xff1b;然而如果使用不当&#xff0c;也会给网站带来一些负面的影响。在这篇文章中&#xff0c;WPChina.org就给大家介绍一下&#xff0c;…

OracleApps Dropship 流程

做的一个Dropship流程的实录(包括流程期间遇到问题的解决)What are the advantages of Drop Shipment Orders?These are the benefits: No inventory is required Reduced order fulfillment processing costs Reduced flow times Elimination of losses on non-sellable …

word取消下一页_word文档页码设置及文中小箭头清除办法

在很长一段时间里&#xff0c;朋友圈流传的这样一段话&#xff1a;世人慌慌张张&#xff0c;不过图碎银几两&#xff1b;可偏偏就是这几两碎银&#xff0c;能免饥荒&#xff0c;能定安康 &#xff0c;能解世人惆怅。。。问&#xff1a;word怎么从第二页开始加页码答&#xff1a…

一起写框架-Ioc内核容器的实现-对象的调用-属性注入容器的对象(十)

实现功能 需求&#xff1a;在类的成员属性使用Autowirde注解注入容器中的对象。 实现思路 要实现这个功能。我们首先要思考一个问题&#xff1a;类与类的关系是在调用的建立的&#xff0c;还是说在创建对象的时候就就将建立了&#xff1f; ---我实现的方案是&#xff0c;在在程…

2064: 分裂 - BZOJ

Description 背景&#xff1a; 和久必分&#xff0c;分久必和。。。 题目描述&#xff1a; 中国历史上上分分和和次数非常多。。通读中国历史的WJMZBMR表示毫无压力。 同时经常搞OI的他把这个变成了一个数学模型。 假设中国的国土总和是不变的。 每个国家都可以用他的国土面积代…