2024年3月

前言

在.NET中,连接池被广泛用于管理和优化不同类型资源的连接。连接池可以减少建立和关闭连接所需的时间和资源消耗,从而提高了应用程序的性能和响应能力。

HttpClient中的连接池

System.Net.Http.HttpClient 类用于发送 HTTP 请求以及从 URI 所标识的资源接收 HTTP 响应。 每个HttpClient 实例使用自身的连接池,当实例被释放时,连接一会一同被释放,所以为了缓解端口耗尽问题,应该尽量重用HttpClient实例。

我们可以控制HttpClient中的connection pool的行为

  • PooledConnectionLifetime 指的是当连接处于池中时保持活动状态的时间长度。一旦这个生命周期到期,该连接将不再被保留在池中或用于未来的请求。默认为Infinite

  • PooledConnectionIdleTimeout 定义了空闲连接在未被使用时在连接池中保持的时间长度。一旦这个生命周期到期,空闲的连接将被清除并从池中移除。默认为两分钟

  • MaxConnectionsPerServer 定义了每个端点将建立的最大出站连接数。每个端点的连接分别进行池化。例如,如果最大连接数的值是2,而你的应用程序向www.github.com和www.google.com发送请求,则总共可能会有最多4个开放的连接。默认值为Int.MaxValue

DB连接池

DB连接池大家都耳熟能详了,在这里就不必赘述细节了,主要注意以下几点:

  • 选择合适的Package, 比如针对MongoDB有很多连接方式,但是某些方式并没有连接池,当技术选型时,一定要选择正确的package。

  • 每个connection string对应一个连接池

  • 连接池有最大限制,sql server的最大连接默认为100

Redis连接池

在.NET中,大家都使用StackExchange.Redis来连接Redis,但是此Client Library并没有Pool,大家可以考虑使用额外的第三方类库比如StackExchange.Redis.ConnectionPool等

SMTP连接池

在.NET中,大家一般使用SmtpClient发送邮件,SmtpClient 实现将 SMTP 连接池化,以避免重新建立与同一服务器的每条消息的连接开销。 应用程序可以重复使用同一 SmtpClient 对象将许多不同的电子邮件发送到同一 SMTP 服务器.

但请注意,现在官方文档推荐不使用SmtpClient, 而用MailKit替代,原因是Smtp不兼容新的协议。

前言

在前面的讲解中,我们了解了如何获取构造器。当只有一个符合条件的构造器时,自然会选择它作为初始化的构造器。然而,在上一节中,我们遇到了一种特殊情况:当有多个符合条件的构造器时,返回的是一个数组。在这种情况下,Spring又是如何从多个构造器中选择最合适的呢?今天,我们将讨论的主题是:autowireConstructor方法。

autowireConstructor

让我们首先深入研究一下该方法的主要源代码,毕竟源代码是最好的老师。

public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
		@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {

	BeanWrapperImpl bw = new BeanWrapperImpl();
	this.beanFactory.initBeanWrapper(bw);

	Constructor<?> constructorToUse = null;
	ArgumentsHolder argsHolderToUse = null;
	Object[] argsToUse = null;

	// 如果getBean()传入了args,那构造方法要用的入参就直接确定好了
	if (explicitArgs != null) {
		argsToUse = explicitArgs;
	}
	else {
		Object[] argsToResolve = null;
		synchronized (mbd.constructorArgumentLock) {
			constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
			if (constructorToUse != null && mbd.constructorArgumentsResolved) {
				// Found a cached constructor...
				argsToUse = mbd.resolvedConstructorArguments;
				if (argsToUse == null) {
					argsToResolve = mbd.preparedConstructorArguments;
				}
			}
		}
		if (argsToResolve != null) {
			argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);
		}
	}

	// 如果没有确定要使用的构造方法,或者确定了构造方法但是所要传入的参数值没有确定
	if (constructorToUse == null || argsToUse == null) {

		// Take specified constructors, if any.
		// 如果没有指定构造方法,那就获取beanClass中的所有构造方法所谓候选者
		Constructor<?>[] candidates = chosenCtors;
		if (candidates == null) {
			Class<?> beanClass = mbd.getBeanClass();
			try {
				candidates = (mbd.isNonPublicAccessAllowed() ?
						beanClass.getDeclaredConstructors() : beanClass.getConstructors());
			}
			catch (Throwable ex) {
				throw new BeanCreationException(mbd.getResourceDescription(), beanName,
						"Resolution of declared constructors on bean Class [" + beanClass.getName() +
						"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
			}
		}

		// 如果只有一个候选构造方法,并且没有指定所要使用的构造方法参数值,并且该构造方法是无参的,那就直接用这个无参构造方法进行实例化了
		if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
			Constructor<?> uniqueCandidate = candidates[0];
			if (uniqueCandidate.getParameterCount() == 0) {
				synchronized (mbd.constructorArgumentLock) {
					mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
					mbd.constructorArgumentsResolved = true;
					mbd.resolvedConstructorArguments = EMPTY_ARGS;
				}
				bw.setBeanInstance(instantiate(beanName, mbd, uniqueCandidate, EMPTY_ARGS));
				return bw;
			}
		}

		// Need to resolve the constructor.
		boolean autowiring = (chosenCtors != null ||
				mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
		ConstructorArgumentValues resolvedValues = null;

		// 确定要选择的构造方法的参数个数的最小值,后续判断候选构造方法的参数个数如果小于minNrOfArgs,则直接pass掉
		int minNrOfArgs;
		if (explicitArgs != null) {
			// 如果直接传了构造方法参数值,那么所用的构造方法的参数个数肯定不能少于
			minNrOfArgs = explicitArgs.length;
		}
		else {
			// 如果通过BeanDefinition传了构造方法参数值,因为有可能是通过下标指定了,比如0位置的值,2位置的值,虽然只指定了2个值,但是构造方法的参数个数至少得是3个
			ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
			resolvedValues = new ConstructorArgumentValues();
			// 处理RuntimeBeanReference
			minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
		}

		// 对候选构造方法进行排序,public的方法排在最前面,都是public的情况下参数个数越多越靠前
		AutowireUtils.sortConstructors(candidates);
		int minTypeDiffWeight = Integer.MAX_VALUE;
		Set<Constructor<?>> ambiguousConstructors = null;
		Deque<UnsatisfiedDependencyException> causes = null;

		// 遍历每个构造方法,进行筛选
		for (Constructor<?> candidate : candidates) {
			// 参数个数
			int parameterCount = candidate.getParameterCount();

			// 本次遍历时,之前已经选出来了所要用的构造方法和入参对象,并且入参对象个数比当前遍历到的这个构造方法的参数个数多,则不用再遍历,退出循环
			if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
				// Already found greedy constructor that can be satisfied ->
				// do not look any further, there are only less greedy constructors left.
				break;
			}
			// 如果参数个数小于所要求的参数个数,则遍历下一个,这里考虑的是同时存在public和非public的构造方法
			if (parameterCount < minNrOfArgs) {
				continue;
			}

			ArgumentsHolder argsHolder;
			Class<?>[] paramTypes = candidate.getParameterTypes();
			// 没有通过getBean()指定构造方法参数值
			if (resolvedValues != null) {
				try {
					// 如果在构造方法上使用了@ConstructorProperties,那么就直接取定义的值作为构造方法的参数名
					String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);

					// 获取构造方法参数名
					if (paramNames == null) {
						ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
						if (pnd != null) {
							paramNames = pnd.getParameterNames(candidate);
						}
					}

					// 根据参数类型、参数名找到对应的bean对象
					argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
							getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
				}
				catch (UnsatisfiedDependencyException ex) {
					// 当前正在遍历的构造方法找不到可用的入参对象,记录一下
					if (logger.isTraceEnabled()) {
						logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
					}
					// Swallow and try next constructor.
					if (causes == null) {
						causes = new ArrayDeque<>(1);
					}
					causes.add(ex);
					continue;
				}
			}
			else {
				// Explicit arguments given -> arguments length must match exactly.
				// 在调getBean方法时传入了参数值,那就表示只能用对应参数个数的构造方法
				if (parameterCount != explicitArgs.length) {
					continue;
				}
				// 不用再去BeanFactory中查找bean对象了,已经有了,同时当前正在遍历的构造方法就是可用的构造方法
				argsHolder = new ArgumentsHolder(explicitArgs);
			}

			// 当前遍历的构造方法所需要的入参对象都找到了,根据参数类型和找到的参数对象计算出来一个匹配值,值越小越匹配
			// Lenient表示宽松模式
			int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
					argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
			// Choose this constructor if it represents the closest match.
			// 值越小越匹配
			if (typeDiffWeight < minTypeDiffWeight) {
				constructorToUse = candidate;
				argsHolderToUse = argsHolder;
				argsToUse = argsHolder.arguments;
				minTypeDiffWeight = typeDiffWeight;
				ambiguousConstructors = null;
			}
			// 值相等的情况下,记录一下匹配值相同的构造方法
			else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
				if (ambiguousConstructors == null) {
					ambiguousConstructors = new LinkedHashSet<>();
					ambiguousConstructors.add(constructorToUse);
				}
				ambiguousConstructors.add(candidate);
			}
		}
		// 遍历结束   x

		// 如果没有可用的构造方法,就取记录的最后一个异常并抛出
		if (constructorToUse == null) {
			if (causes != null) {
				UnsatisfiedDependencyException ex = causes.removeLast();
				for (Exception cause : causes) {
					this.beanFactory.onSuppressedException(cause);
				}
				throw ex;
			}
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Could not resolve matching constructor on bean class [" + mbd.getBeanClassName() + "] " +
					"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
		}
		// 如果有可用的构造方法,但是有多个
		else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Ambiguous constructor matches found on bean class [" + mbd.getBeanClassName() + "] " +
					"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
					ambiguousConstructors);
		}

		// 如果没有通过getBean方法传入参数,并且找到了构造方法以及要用的入参对象则缓存
		if (explicitArgs == null && argsHolderToUse != null) {
			argsHolderToUse.storeCache(mbd, constructorToUse);
		}
	}

	Assert.state(argsToUse != null, "Unresolved constructor arguments");
	bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
	return bw;
}

在进入这个方法之前,还存在一个缓存层,因为原型BeanDefinition可能会多次创建Bean,但无需每次都重新寻找构造器。因此,当第一次找到构造器时,会被缓存起来。如果缓存中已经存在构造方法,那么可以直接进行实例化,无需再次执行推断方法。如果在之前的步骤中没有找到其他构造器,那么将会使用无参构造器来实例化Bean。

推断方法判断

我们现在来仔细观察一下autowireConstructor方法的整体流程,这样我们可以更清楚地理解其运作方式。

如果没有明确确定要使用的构造方法,或者已确定构造方法但其所需传入参数值尚未确定。

  1. 当没有确定要使用的构造方法时,可以遍历类中的所有构造方法。
  2. 当类中只存在一个无参构造方法时,可以直接使用该无参构造方法进行实例化,无需额外的选择操作。
  3. 在选择构造方法时,需要确定所需参数个数的最小值。若已传入构造方法参数值,则所选构造方法的参数个数必不少于传入值的个数;若未传入参数值,则需检查BeanDefinition中是否指定了某个下标的值,确保最小值大于该下标。

比如这样配置:

public class UserServiceBeanPostProcessor implements BeanFactoryPostProcessor {

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		BeanDefinition userService = beanFactory.getBeanDefinition("userService");
		//这里我瞎写的null,正常应该是对象。
		userService.getConstructorArgumentValues().addIndexedArgumentValue(1,null);
	}
}
  1. 对候选构造方法进行排序。首先,将public修饰的构造方法排在最前面;若所有构造方法均为public,那么参数个数越多的构造方法越靠前。

  2. 遍历每个构造方法

  3. 在调用getBean()方法时,如果不指定构造方法的参数值,系统会根据构造器中的参数类型和参数名来匹配相应的bean对象。

  4. 在调用getBean()方法时,如果指定了构造方法的参数值,系统会直接利用这些参数值来实例化bean对象

  5. 在确定构造方法时,尽管找到了匹配的构造方法参数值,但并不意味着这个构造方法是最佳选择。因此,需要考虑是否存在多个构造方法匹配了相同的值。在这种情况下,系统将会根据值和构造方法类型之间的匹配程度进行评分,以找到最佳匹配的构造方法。

分值越低越匹配

打分规则是基于分值越低越匹配的原则。要确定分值,我们需要将代码提取出来进行运行,因为底层的逻辑相当复杂,需要仔细分析。

public int getTypeDifferenceWeight(Class<?>[] paramTypes) {
	// 最终值和类型的匹配程度
	int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments);
	// 原始值和类型的匹配程度,并减掉1024,使得原始值的匹配值更优先,意思就是优先根据原始值来算匹配值
	int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024;
	// 取最小值
	return Math.min(rawTypeDiffWeight, typeDiffWeight);
}

那么,让我们来查看一下getTypeDifferenceWeight方法能够输出怎样的数值。

首先,我们定义一个A类,该类继承自B类,而B类又继承自C类,同时A类实现了接口D。

Object[] objects = new Object[]{new A()};
// 0
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{A.class}, objects));
// 2
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{B.class}, objects));
// 4
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{C.class}, objects));
// 1
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{D.class}, objects));

通过仔细观察,我们可以明白为什么分值越低越具有匹配性。

总结

在本文中,我们深入研究了Spring框架中的autowireConstructor方法。该方法用于在存在多个构造器时选择最合适的构造器进行实例化Bean。通过分析源代码和推断方法判断的流程,我们了解到系统是如何根据参数个数、类型和数值的匹配程度来选择最佳构造器的。

在实际应用中,我们需要注意遍历构造方法、参数个数的最小值、排序规则、参数值的匹配等细节。

总的来说,autowireConstructor方法是Spring框架中一个关键的方法,它为我们提供了灵活且智能的构造器选择机制,帮助我们更好地管理Bean的实例化过程。通过学习和掌握这一方法,我们能够更好地运用Spring框架。

一种理解整个文档上下文的新颖的 RAG 方法

RAG 是当前使用LLM的标准方法,大多数现有方法仅从检索语料库中检索短的连续块,限制了对整个文档上下文的整体理解。

最近,一种名为 RAPTOR (Recursive Abstractive Processing for Tree-Organized Retrieval)方法提出来,该方法核心思想是将doc构建为一棵树,然后逐层递归的查询,如下图所示:

在推理时,RAPTOR 模型从该树中检索,在不同抽象级别的文档中找出匹配片段。

在涉及复杂、多步骤推理的问答任务中,通过将 RAPTOR 检索与 GPT-4 结合使用,可以将 QuALITY 基准的准确率提高 20%。

树的构建过程

构建树的过程,RAPTOR 根据其语义embedding递归地对文本块chunk进行聚类,并生成这些聚类的文本摘要。

RAPTOR 根据向量递归地对文本块进行聚类,并生成这些聚类的文本摘要,从而自下而上构建一棵树。 聚集在一起的节点是兄弟节点; 父节点包含该集群的文本摘要。这种结构使 RAPTOR 能够将代表不同级别文本的上下文块加载到 LLM 的上下文中,以便它能够有效且高效地回答不同层面的问题。

树的聚类算法基于高斯混合模型 (GMM),聚类后,每个聚类中的节点被发送到LLM进行概括。在实验中,作者使用 gpt-3.5-turbo 来生成摘要。摘要步骤将可能大量的检索信息压缩(summarization)到一个可控的大小。

查询过程

查询有两种方法,基于树遍历(tree traversal)和折叠树(collapsed tree)

遍历是从 RAPTOR 树的根层开始,然后逐层查询
折叠树就是全部平铺,用ANN库查询。

查询方法的比较

折叠树方法具有更大的灵活性,F1会更高。

JAR 包要在预装 JRE 环境的系统上执行。
如果没有预先安装 JRE 环境,又想直接运行 Java 程序,该怎么办呢?

这篇文章我们会先学习如何将 Java 程序打包成一个可执行的 Java JAR 文件。然后演示如何使
用这个 JAR 文件生成 Windows、Linux、MacOS 上的可执行程序
。 我们将使用 Java 自带的
jar
命令行工具来创建 JAR 文件。然后学会使用
jpackage
工具创建各个系统上的可执行程序。

注意:jpackage 工具从 Java 14 版本开始提供的,可以用来生成可执行程序。

什么是 jar

jar 文件是一个包含编译后的 Java Class 文件和其他资源的容器。它基于广泛使用的 ZIP 文件格式,因此 jar 文件可以使用 ZIP解压缩工具解压。 一个可执行的 jar 文件需要包含一个 main 类作为程序的入口,并在
MANIFEST.MF
文件中指定

但是为了运行 jar 格式的应用程序,必须有一个Java 运行时环境(JRE)。

jar 命令

Java 的
jar
命令是 Java Archive Tool,它是一个用于创建、查看和管理 jar 文件的命令行工具。此工具包含在 JDK 中。

详细介绍
jar
命令的使用不是本文目的,下面给出
jar
命令的常见用法。

创建一个输出
Hello
的Java 类用于测试。

目录结构:

├── Hello.java
└── META-INF
    └── MANIFEST.MF

查看文件内容然后编译 Hello.java

public class Hello{
    public static void main(String[] args) throws InterruptedException{
        System.out.println("Hello");
        // 3s 后退出
        Thread.sleep(3 * 1000);
    }
}
// 编译:javac Hello.java

配置
MANIFEST.MF
文件,
注意,最后一定要有一个换行
,否则可能在 Windows 上运行失败。

Manifest-Version: 1.0
Main-Class: Hello

  1. 创建 jar 文件

    jar cmf META-INF/MANIFEST.MF hello.jar Hello.class
    

    其中
    c
    表示创建新的归档文件,
    m
    指定清单文件,
    f
    指定生成的 jar 文件的名称,最后是要添加到 jar 包中的文件列表。

  2. 执行 jar 文件

    java -jar hello.jar
    Hello
    
  3. 查看 jar 文件

    $ jar tf hello.jar
    META-INF/
    META-INF/MANIFEST.MF
    Hello.class
    

    其中
    t

    list
    ,列出文件。
    f
    指定 jar 文件。

创建 jar 文件有多种方式,比如借助 Maven 或者 Gradle 工具都可以打包 Java 程序为 jar 文件,而且更加方便。比如 Spring Boot 开发过程中,
mvn package
即可生成 jar 文件。

jpackage 命令

jpackage
命令是从 Java 14 开始提供的,可以帮助我们为模块化或非模块化 Java 应用程序生成指定系统平台的可执行程序,而不用预先安装 JRE 环境。如何做到的呢?

我们知道 Java 程序必须在 JRE环境才能运行,
jpackage
其实是把 JRE 和 JAR 文件以及所有必要依赖项一起打包生成指定平台的可执行程序。例如 Windows 上的 exe 或 macOS 上的 dmg。每种格式都必须构建在其运行的平台上,没有跨平台支持。工具还提供了常见的自定义操作,如应用名,应用图标等。

查看
jpackage
帮助:

jpackage --help
用法:jpackage <options>

示例用法:
--------------
    生成适合主机系统的应用程序包:
        对于模块化应用程序:
            jpackage -n name -p modulePath -m moduleName/className
        对于非模块化应用程序:
            jpackage -i inputDir -n name \
                --main-class className --main-jar myJar.jar
        从预构建的应用程序映像:
            jpackage -n name --app-image appImageDir
    生成应用程序映像:
        对于模块化应用程序:
            jpackage --type app-image -n name -p modulePath \
                -m moduleName/className
        对于非模块化应用程序:
            jpackage --type app-image -i inputDir -n name \
                --main-class className --main-jar myJar.jar
        要为 jlink 提供您自己的选项,请单独运行 jlink:
            jlink --output appRuntimeImage -p modulePath \
                --add-modules moduleName \
                --no-header-files [<additional jlink options>...]
            jpackage --type app-image -n name \
                -m moduleName/className --runtime-image appRuntimeImage
    生成 Java 运行时程序包:
        jpackage -n name --runtime-image <runtime-image>
    对预定义应用程序映像进行签名:
        jpackage --type app-image --app-image <app-image> \
            --mac-sign [<additional signing options>...]
        注:此模式下允许的其他选项只有:
              一组其他 mac 签名选项和 --verbose
........

jpackage 创建可执行文件

创建可执行程序命令格式:

jpackage --input . --name YouAppName --main-jar youfile.jar

现在让我们使用上面的
hello.jar
来创建一个可执行的JAR文件。
hello.jar
直接运行会输出
Hello
字符。

Windows 平台

注意: 对于 Windows,
jpackage
需要 WiX 3.0 或更高版本。

Winx3.14 下载:
https://github.com/wixtoolset/wix3/releases/tag/wix314rtm

由于
hello.jar
是一个命令行程序,没有 UI界面,因此打包时使用
--win-console
参数配置以命令行方式启动。

常见的 Windows 下
jpackage
参数还有:

  • --type
    : 指定打包后的格式,如 msi、exe,默认 exe。
  • --win-console
    :使用控制台窗口启动我们的应用程序
  • --win-shortcut
    : 在 Windows 开始菜单中创建快捷方式文件
  • --win-dir-chooser
    :让最终用户指定自定义目录来安装可执行文件

打包成 exe 程序。

jpackage --input . --name helloApp1 --win-console --win-shortcut --main-jar hello.jar

打包后可以得到
helloApp1-1.0.exe
文件。

PS C:\Users\Administrator\Desktop\test> jpackage --input . --name helloApp --win-console --win-shortcut --main-jar hello.jar
PS C:\Users\Administrator\Desktop\test> ls
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----          2024/3/7     22:14            526 Hello.class
-a----          2024/3/7     22:14            802 hello.jar
-a----          2024/3/7     22:13            208 Hello.java
-a----          2024/3/7     22:17      110145536 helloApp1-1.0.exe
-a----          2024/3/7     21:58             42 MANIFEST.MF

直接双击运行安装。

安装完成后,桌面上会出现图标,双击可以运行并输出
Hello
字符串。

Mac 平台

Mac 平台运行
jpackage
命令会自动生成 dmg 安装包。

jpackage --input . --name hello --main-jar hello.jar

生成
hello-1.0.dmg
文件,双击弹出安装界面。

因为测试程序
hello.jar
是一个输出
Hello
字符串的命令行程序,并没有 UI,因此测试从命令行启动查看输出。

➜  ~ /Applications/hello.app/Contents/MacOS/hello
Hello

Linux 平台

jpackage --input . --name hello --main-jar hello.jar

我所在 Linux 系统为
Ubuntu22
,所以生成安装包
hello_1.0_amd64.deb

$ ls -l -h
total 37M
-rw-r--r-- 1 root root  37M Mar  7 16:50 hello_1.0_amd64.deb
-rw-r--r-- 1 root root  401 Mar  6 11:42 Hello.class
-rw-r--r-- 1 root root 1.1K Mar  7 16:42 hello.jar
-rw-r--r-- 1 root root   96 Mar  6 11:41 Hello.java
-rw-r--r-- 1 root root   41 Mar  6 11:42 MANIFEST.MF

安装
hello_1.0_amd64.deb

$ apt install hello_1.0_amd64.deb

安装后命令位于
/opt
目录下,运行测试:

$ /opt/hello/bin/hello
Hello

总结

本文介绍了在没有预装 JRE 环境的系统上运行 Java 程序的方法。首先,介绍如何使用 Java 的
jar
命令行工具创建一个可执行的 JAR 文件,这需要编写 Java 程序,配置
MANIFEST.MF
文件,并使用
jar
命令创建包含主类的 JAR 文件。接着,介绍了
jpackage
工具(从 Java 14 版本开始提供),该工具可以打包 JAR 文件和必要的 JRE 环境,生成适用于 Windows、Linux、MacOS 的可执行程序,使得 Java 应用程序能够在无需预装 JRE 的情况下运行。

参考

本文
Github.com/niumoo/JavaNotes
仓库已经收录。
本文原发于网站:
Jpackage - 制作无需预装 Java 环境的 Jar 可执行程序
本文原发于公众号:
程序猿阿朗

Python作为一门灵活、充满技巧的语言,有着很多奇技淫巧,今天小编就跟大家分享一下在平时工作中所积累的技巧,这里面既有语法上的技巧,也有库函数的应用,可以帮助大家在平时的工作中提升效率,规避某些错误,一起来看看吧。

  1. 列表推导式
  2. 字典推导式
  3. 使用
    zip
    进行并行迭代
  4. 使用
    enumerate
    获取迭代器索引和值
  5. 使用
    collections.Counter
    进行计数
  6. 使用
    map
    函数进行批量操作
  7. 使用列表解析展平列表
  8. 列表内容转字符串
  9. 去除列表中重复元素
  10. 将字典值作为参数传递
  11. 两个变量值互换
  12. 连续赋值
  13. 链式比较
  14. 重复列表
  15. 重复字符串
  16. 三目运算
  17. 字典合并
  18. 字符串反转
  19. 列表转字符串
  20. for else 语句

1、列表推导式

使用一行代码生成列表,提高代码的简洁性和可读性。

squared = [x**2 for x in range(10)]
print(squared)

结果
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

当我们使用列表推导式
[x**2 for x in range(10)]
时,它等价于使用普通的
for
循环来生成一个列表。让我们将列表推导式转换为等效的普通
for
循环代码

squared = []  # 创建一个空列表,用于存放计算结果

for x in range(10):  # 对于范围内的每一个数 x
    squared.append(x**2)  # 计算 x 的平方并将结果添加到列表 squared 中

print(squared)  # 打印最终的列表 squared

2、字典推导式

类似列表推导式,用于创建字典。所以代码用大括号包裹

square_dict = {x: x**2 for x in range(5)}
print(square_dict)

结果
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

3、使用
zip
进行并行迭代

将多个可迭代对象压缩在一起进行并行迭代。

names = ['Alice', 'Bob', 'Charlie']
ages = [30, 35, 40]

for name, age in zip(names, ages):
    print(name, age)

结果
Alice 30
Bob 35
Charlie 40

4、使用
enumerate
获取迭代器索引和值

在迭代时获取索引和对应的值,在迭代一些可迭代对象时(例如list,dict),可通过内置enumerate来获取迭代元素的索引值

for index, value in enumerate(names):
    print(index, value)

5、使用
collections.Counter
进行计数

方便地计算可迭代对象中元素的频率。

from collections import Counter

words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
word_counts = Counter(words)
print(word_counts)

结果
Counter({'apple': 3, 'banana': 2, 'orange': 1})

6、使用
map
函数进行批量操作

通过map函数可进行批量操作,将函数应用于迭代器中的每个元素。

nums = [1, 2, 3, 4, 5]

def square(x):
    return x**2
# 代码表示将list的每个元素迭代后应用square函数中
squared_nums = list(map(square, nums))
print(squared_nums)

结果
[1, 4, 9, 16, 25]

7、使用列表解析展平列表

将嵌套的列表展平为一维列表。

nested_list = [[1, 2, 3], [4, 5], [6, 7, 8]]
flattened_list = [num for sublist in nested_list for num in sublist]
print(flattened_list)

结果
[1, 2, 3, 4, 5, 6, 7, 8]

还有一种方式可以
连接两个列表

  
a = [1, 2, 3]  
b = [5, 6, 7]  
  
c = [*a, *b]  
print(c)

结果
[1, 2, 3, 5, 6, 7]

8、列表内容转字符串

而列表中会存在字符串、数字等类型的数据,通过map将列表中元素转换成str类型,然后通过join函数就可以完成列表到字符串的转换。

9、去除列表中重复元素

list1 = [1,2,3,4,5,2,1,4,2,1]  
print(list(set(list1)))

结果
[1, 2, 3, 4, 5]

10、将字典值作为参数传递

当你想将一个字典的值作为参数传递给函数时,你可以使用
**
运算符来解包字典并将其作为关键字参数传递给函数。以下是一个示例:

def greet(name, age):  
    print(f"Hello, {name}! You are {age} years old.")  
  
person_info = {'name': 'Alice', 'age': 30}  
  
greet(**person_info)

结果
Hello, Alice! You are 30 years old.

11、两个变量值互换

>>> a=1
>>> b=2
>>> a,b=b,a
>>> a
2
>>> b
1

12、连续赋值

a = b = c = 50

13、链式比较

a = 15
if (10 < a < 20):
    print("Hi")

等价于

a = 15
if (a>10 and a<20):
    print("Hi")

14、重复列表

>>> [5,2]*4
[5, 2, 5, 2, 5, 2, 5, 2]

15、重复字符串

>>> "hello"*3
'hellohellohello'

16、三目运算

age = 30
slogon = "牛逼" if  age == 30 else "niubility"

等价于

if age == 30:
    slogon = "牛逼"
else:
    slogon = "niubility"

17、字典合并

>>> a= {"a":1}
>>> b= {"b":2}
>>> {**a, **b}
{'a': 1, 'b': 2}
>>>

18、字符串反转

>>> s = "i love python"
>>> s[::-1]
'nohtyp evol i'
>>>

19、列表转字符串

>>> s = ["i", "love", "pyton"]
>>> " ".join(s)
'i love pyton'
>>>

20、for else 语句

检查列表foo是否有0,有就提前结束查找,没有就是打印“未发现"

found = False
for i in foo: 
	if i == 0: 
		found = True
		break 
if not found: 
	print("未发现")

总结

以上就是小编为大家分享总结的Python技巧,大家还有什么Python的奇淫技巧呢,欢迎转载、收藏、有所收获点赞支持一下。

关注公众号【Python魔法师】,一起进群沟通学习~

qrcode.jpg