hikaril连接sql2000_hikari连接池解析(版本:HikariCP-2.5.1.jar)

maxLifetime参数

maxLifetime参数必须小于数据库的time_wait,默认是1800000,即30分钟。如果设置为0,表示存活时间无限大。如果不等于0且小于30秒则会被重置回30分钟。HikariConfig类中有该参数的校验规则。

HikariPool类中,当我们初始化连接池的时候,它的构造方法中,实例化了this.POOL_ENTRY_CREATOR = new HikariPool.PoolEntryCreator();该类实现了Callable接口,用来初始化连接。

public Boolean call() throws Exception {

for(long sleepBackoff = 250L; HikariPool.this.poolState == 0 && HikariPool.this.totalConnections.get() < HikariPool.this.config.getMaximumPoolSize(); sleepBackoff = Math.min(TimeUnit.SECONDS.toMillis(10L), Math.min(HikariPool.this.connectionTimeout, (long)((double)sleepBackoff * 1.5D)))) {

PoolEntry poolEntry = HikariPool.this.createPoolEntry();

if (poolEntry != null) {

HikariPool.this.totalConnections.incrementAndGet();

HikariPool.this.connectionBag.add(poolEntry);

return Boolean.TRUE;

}

UtilityElf.quietlySleep(sleepBackoff);

}

return Boolean.FALSE;

}

复制代码

在其中调用createPoolEntry()生成一个连接。

private PoolEntry createPoolEntry() {

try {

final PoolEntry poolEntry = this.newPoolEntry();

long maxLifetime = this.config.getMaxLifetime();

if (maxLifetime > 0L) {

long variance = maxLifetime > 10000L ? ThreadLocalRandom.current().nextLong(maxLifetime / 40L) : 0L;

long lifetime = maxLifetime - variance;

poolEntry.setFutureEol(this.houseKeepingExecutorService.schedule(new Runnable() {

public void run() {

HikariPool.this.softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false);

}

}, lifetime, TimeUnit.MILLISECONDS));

}

this.LOGGER.debug("{} - Added connection {}", this.poolName, poolEntry.connection);

return poolEntry;

} catch (Exception var8) {

if (this.poolState == 0) {

this.LOGGER.debug("{} - Cannot acquire connection from data source", this.poolName, var8);

}

return null;

}

}

复制代码

在该方法中,设置了一个延时任务,具体的延时执行时间是根据maxLifetime来计算,触发时间距离maxlifetime的差值是根据 maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;

来计算(up to 2.5% of the maxlifetime),在连接存活将要到达maxLifetime之前触发evit,用来防止出现大面积的connection因maxLifetime同一时刻失效。

当被触发时,会标记evict为true,标记为evict只是表示连接池中的该连接不可用,但还在连接池当中,还会被borrow出来,只是getConnection的时候判断了,如果是isMarkedEvicted,则会从连接池中移除该连接,然后close掉。

HikariCP中通过独立的线程池closeConnectionExecutor进行物理连接的关闭。出现以下三种情况时会触发连接的自动关闭:

连接断开;

连接存活时间超出最大生存时间(maxLifeTime)

连接空闲时间超出最大空闲时间(idleTimeout)

closeConnectionExecutor关闭连接后,会调用fillPool()方法对连接池进行连接填充

validationTimeout

validationTimeout用来指定验证连接有效性的超时时间(默认是5秒,最小不能小于250毫秒),在HikariPool.getConnection方法中会调用isConnectionAlive(Connection connection)对连接进行验证。

如果是jdbc4的话,可以使用isUseJdbc4Validation,是直接利用connection.isValid(validationSeconds)来验证连接的有效性;否则的话则用connectionTestQuery查询语句来查询验证。

leakDetectionThreshold`

该参数主要用来开启连接泄漏检测,在通过getConnection()获取连接的时候,会继续调用另外一个createProxyConnection()方法获取连接,这里我们关注入参this.leakTask.schedule(poolEntry)。

public final Connection getConnection(long hardTimeout) throws SQLException {

this.suspendResumeLock.acquire();

long startTime = clockSource.currentTime();

try {

long timeout = hardTimeout;

do {

PoolEntry poolEntry = (PoolEntry)this.connectionBag.borrow(timeout, TimeUnit.MILLISECONDS);

if (poolEntry == null) {

break;

}

long now = clockSource.currentTime();

if (!poolEntry.isMarkedEvicted() && (clockSource.elapsedMillis(poolEntry.lastAccessed, now) <= this.ALIVE_BYPASS_WINDOW_MS || this.isConnectionAlive(poolEntry.connection))) {

this.metricsTracker.recordBorrowStats(poolEntry, startTime);

//获取连接

Connection var10 = poolEntry.createProxyConnection(this.leakTask.schedule(poolEntry), now);

return var10;

}

复制代码

该schedule方法返回一个ProxyLeakTask对象

//返回 ProxyLeakTask

ProxyLeakTask schedule(PoolEntry bagEntry) {

return this.leakDetectionThreshold == 0L ? NO_LEAK : new ProxyLeakTask(this, bagEntry);

}

复制代码

这里判断leakDetectionThreshold参数是否为0,默认是0,不开启检测。否则,就会开启一个延时执行任务,时间正好为设置的leakDetectionThreshold值,该任务的作用就是用来抛出Apparent connection leak detected异常。

private ProxyLeakTask(ProxyLeakTask parent, PoolEntry poolEntry) {

this.exception = new Exception("Apparent connection leak detected");

this.connectionName = poolEntry.connection.toString();

//延时执行

this.scheduledFuture = parent.executorService.schedule(this, parent.leakDetectionThreshold, TimeUnit.MILLISECONDS);

}

复制代码

截取一部分异常,如下

22:14:49.096 volte-cmd-service-test [HikariPool-1 housekeeper] WARN com.zaxxer.hikari.pool.ProxyLeakTask - Connection leak detection triggered for com.mysql.jdbc.JDBC4Connection@429fe922, stack trace follows

java.lang.Exception: Apparent connection leak detected

at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122)

at org.hibernate.internal.AbstractSessionImpl$NonContextualJdbcConnectionAccess.obtainConnection(AbstractSessionImpl.java:386)

at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.java:87)

at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.getPhysicalConnection(LogicalConnectionManagedImpl.java:112)

at org.hibernate.internal.SessionImpl.connection(SessionImpl.java:489)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:497)

at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:215)

at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:200)

at org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle.doGetConnection(HibernateJpaDialect.java:414)

at org.springframework.orm.jpa.vendor.HibernateJpaDialect.beginTransaction(HibernateJpaDialect.java:177)

复制代码

也就是从我们获取这个连接开始,到归还连接之前的这一段时间,如果超过了leakDetectionThreshold,则会抛出上面的异常。

HouseKeeper

它是HikariPool中的一个内部类,实现了Runnable接口,主要就是对连接进行管理。在初始化HikariPool的时候,会创建一个scheduleWithFixedDelay任务(已固定延迟时间执行,就是说两个任务之间的时间间隔是固定的,但每个任务的执行时长可能是不定的,与scheduleFixedRate的区别就是,不管任务是否执行完,到点就执行下一次任务),默认30s执行一次,刷新配置,进行判断。

public HikariPool(HikariConfig config) {

super(config);

this.ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindowMs", TimeUnit.MILLISECONDS.toMillis(500L));

this.HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", TimeUnit.SECONDS.toMillis(30L));

this.POOL_ENTRY_CREATOR = new HikariPool.PoolEntryCreator();

this.connectionBag = new ConcurrentBag(this);

this.totalConnections = new AtomicInteger();

this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;

this.checkFailFast();

if (config.getMetricsTrackerFactory() != null) {

this.setMetricsTrackerFactory(config.getMetricsTrackerFactory());

} else {

this.setMetricRegistry(config.getMetricRegistry());

}

this.setHealthCheckRegistry(config.getHealthCheckRegistry());

this.registerMBeans(this);

ThreadFactory threadFactory = config.getThreadFactory();

this.addConnectionExecutor = UtilityElf.createThreadPoolExecutor(config.getMaximumPoolSize(), this.poolName + " connection adder", threadFactory, new DiscardPolicy());

this.closeConnectionExecutor = UtilityElf.createThreadPoolExecutor(config.getMaximumPoolSize(), this.poolName + " connection closer", threadFactory, new CallerRunsPolicy());

//创建定时任务类

if (config.getScheduledExecutorService() == null) {

ThreadFactory threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory(this.poolName + " housekeeper", true);

this.houseKeepingExecutorService = new ScheduledThreadPoolExecutor(1, (ThreadFactory)threadFactory, new DiscardPolicy());

//传递false参数给这个方法,执行shutdown()方法之后,待处理的任务将不会被执行。

this.houseKeepingExecutorService.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);

//取消任务后,判断是否需要从阻塞队列中移除任务

this.houseKeepingExecutorService.setRemoveOnCancelPolicy(true);

} else {

this.houseKeepingExecutorService = config.getScheduledExecutorService();

}

this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), this.houseKeepingExecutorService);

//初始化HouseKeeper

this.houseKeepingExecutorService.scheduleWithFixedDelay(new HikariPool.HouseKeeper(), 100L, this.HOUSEKEEPING_PERIOD_MS, TimeUnit.MILLISECONDS);

}

复制代码

时间回拨处理

在HouseKeeper的run方法中,会先对时间进行判断。

这里主要就是通过一个时间差来判断这个时间差返回内是否有时间回拨,在初始化的时候会通过下面构造方法生成一个时间戳

// previous=当前时间-30s(默认的定时任务间隔时间)

private HouseKeeper() {

this.previous = HikariPool.clockSource.plusMillis(HikariPool.clockSource.currentTime(), -HikariPool.this.HOUSEKEEPING_PERIOD_MS);

}

复制代码

当初始化完成的第一次30s后或者上次任务执行完的30s后,再执行该任务,如果当前的时间戳+128ms还要小于previous(上次执行后减去30s的时间戳)+30s,则表示有时间回拨

//检测逆行时间,根据NTP规范允许+128ms

if (HikariPool.clockSource.plusMillis(now, 128L) < HikariPool.clockSource.plusMillis(this.previous, HikariPool.this.HOUSEKEEPING_PERIOD_MS)) {

HikariPool.this.LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.", HikariPool.this.poolName, HikariPool.clockSource.elapsedDisplayString(this.previous, now));

this.previous = now;

HikariPool.this.softEvictConnections();

HikariPool.this.fillPool();

return;

}

复制代码

此时,会打印日志,并重置previous为当前时间,设置连接为不可用,再重新生成连接。

保持最小连接

如果时间没有错误,则会判断idleTimeout,如果大于0,取出当前空闲连接

判断是否大于最小连接数minimumIdle,如果大于,则继续对当前的空闲连接基于lastAccessed(最后一次访问时间)进行排序,再遍历

如果取出的每个连接的空闲时间已经超过了idleTimeout,并且成功将连接从NOT_IN_USE(闲置中)更改为RESERVED(标记为保留中)

则关闭该连接

最后再新创建连接

public void run() {

try {

//刷新connectionTimeout、validationTimeout

HikariPool.this.connectionTimeout = HikariPool.this.config.getConnectionTimeout();

HikariPool.this.validationTimeout = HikariPool.this.config.getValidationTimeout();

HikariPool.this.leakTask.updateLeakDetectionThreshold(HikariPool.this.config.getLeakDetectionThreshold());

long idleTimeout = HikariPool.this.config.getIdleTimeout();

long now = HikariPool.clockSource.currentTime();

//时钟回拨判断

if (HikariPool.clockSource.plusMillis(now, 128L) < HikariPool.clockSource.plusMillis(this.previous, HikariPool.this.HOUSEKEEPING_PERIOD_MS)) {

HikariPool.this.LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.", HikariPool.this.poolName, HikariPool.clockSource.elapsedDisplayString(this.previous, now));

this.previous = now;

HikariPool.this.softEvictConnections();

HikariPool.this.fillPool();

return;

}

if (now > HikariPool.clockSource.plusMillis(this.previous, 3L * HikariPool.this.HOUSEKEEPING_PERIOD_MS / 2L)) {

HikariPool.this.LOGGER.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", HikariPool.this.poolName, HikariPool.clockSource.elapsedDisplayString(this.previous, now));

}

this.previous = now;

String afterPrefix = "Pool ";

if (idleTimeout > 0L) {

//取出空闲连接 连接状态,IN_USE(1:使用中)、NOT_IN_USE(0:闲置中)、REMOVED(-1:已移除)、RESERVED(-1:标记为保留中)

List idleList = HikariPool.this.connectionBag.values(0);

int removable = idleList.size() - HikariPool.this.config.getMinimumIdle();

if (removable > 0) {

HikariPool.this.logPoolState("Before cleanup ");

afterPrefix = "After cleanup ";

//排序

idleList.sort(PoolEntry.LASTACCESS_COMPARABLE);

Iterator var8 = idleList.iterator();

while(var8.hasNext()) {

PoolEntry poolEntry = (PoolEntry)var8.next();

//idleTimeout判断,连接状态修改

if (HikariPool.clockSource.elapsedMillis(poolEntry.lastAccessed, now) > idleTimeout && HikariPool.this.connectionBag.reserve(poolEntry)) {

HikariPool.this.closeConnection(poolEntry, "(connection has passed idleTimeout)");

--removable;

if (removable == 0) {

break;//keep min idle cons

}

}

}

}

}

HikariPool.this.logPoolState(afterPrefix);

HikariPool.this.fillPool();

} catch (Exception var10) {

HikariPool.this.LOGGER.error("Unexpected exception in housekeeping task", var10);

}

}

}

复制代码

问题

minimumIdle不一致问题

当前版本在应用初始化的时候,连接池也会进行初始化,但是当我们配置的数据源属性minimumIdle

//HikariCP-3.4.5.jar

private synchronized void fillPool()

{

final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())

- addConnectionQueueReadOnlyView.size();

if (connectionsToAdd <= 0) logger.debug("{} - Fill pool skipped, pool is at sufficient level.", poolName);

//生成的个数减去了1

for (int i = 0; i < connectionsToAdd; i++) {

addConnectionExecutor.submit((i < connectionsToAdd - 1) ? poolEntryCreator : postFillPoolEntryCreator);

}

}

//HikariCP-2.5.1.jar 的该方法

for (int i = 0; i < connectionsToAdd; i++) {

addBagItem();

}

复制代码

超时问题

错误日志如下:

java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.

复制代码首先检查配置是否有问题,maxLifetime不能大于数据库的time_wait,查询mysql配置show variables like ‘%timeout%’,默认为8小时。

配置没有问题,则有可能与HikariCP无关。这个错误的产生原因就是请求向池中borrow时,没有可用连接超时导致。第一点,此时我们要思考我们的连接池数量设置是否合理,与业务量相关;第二点,看我们代码是否存在慢sql;第三点,与使用的持久层框架有关,分析我们的连接到底是被谁所持有,它的连接管理方法是怎么样的,什么情况下才会归还连接。

这篇文章就是因为我遇到这个错而无法定位才决定好好研究下的,我的这个错误产生的原因就是上面的第三点,我的项目采用的是jpa做数据库交互,且是一个非常简单的单表查询,连接应该很快归还池中才对,但是经过我的测试,当经过数据库查询以后,连接并没有被释放,反而是在我的整个会话结束后,才会归还连接。

jpa的核心是hibernate-core,在网上查询了hibernate的连接释放策略,知道了原因。hibernate 中连接释放的策略hibernate. connection. release_ mode有以下四种属性:

on_close,当Session被显式关闭或被断开连接时,才会释放JDBC连接

after_transaction,每次事务结束都会释放链接

after_statement,在每次JDBC调用后,都会主动的释放连接

auto,为JTA和CMT事务策略选择after_statement, 为JDBC事务策略选择after_transaction

我的springboot项目版本为1.x,即便将hibernate-core升级到较高版本,并开启事物,也还是基于on_close方式去释放连接;我测试了2.x版本的,不开启事物时也是on_close,开启事物后,就成了after_transaction,具体1.x版本为何开启事物也不生效还不清楚。

参考资料

流程图的方式讲解,很清晰易懂,版本也比较高

HikariCP是如何管理数据库连接的?

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[hikari连接池解析(版本:HikariCP-2.5.1.jar)]http://www.zyiz.net/tech/detail-143838.html

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

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

相关文章

app开发历程————Android程序解析服务器端的JSON格式数据,显示在界面上

上一篇文章写的是服务器端利用Servlet 返回JSON字符串&#xff0c;本文主要是利用android客户端访问服务器端链接&#xff0c;解析JSON格式数据&#xff0c;放到相应的位置上。 首先&#xff0c;android程序的布局文件main.xml 1 <LinearLayout xmlns:android"http://s…

Android IOS WebRTC 音视频开发总结(八十七)-- WebRTC中丢包重传NACK实现分析

Android IOS WebRTC 音视频开发总结&#xff08;八十七&#xff09;-- WebRTC中丢包重传NACK实现分析 本文主要介绍WebRTC中丢包重传NACK的实现&#xff0c;作者&#xff1a;weizhenwei &#xff0c;文章最早发表在编风网&#xff0c;微信ID&#xff1a;befoio 支持原创&#x…

如何去除TD之间的空隙

table{border-collapse:collapse;}转载于:https://www.cnblogs.com/passer1991/archive/2013/02/27/2935967.html

android切换到上个页面,Android 返回上一个界面刷新数据

有些界面需要返回上一个界面刷刷新数据,再此做个记录.首先startActivityForResult进行Actvity进行跳转,这是跳转前的界面.// 通过 startActivityForResult() 启动 ActivityBIntent intent new Intent(getActivity(), NoticeActivity.class);startActivityForResult(intent, 1)…

composer设置代理_composer 设置代理

Docker registry V2部署私有Docker Registry 搭建 Insecure Registry 修改Registry server上的Docker daemon的配置,为DOCKER_OPTS增加–insecure ...css中的position&colon;relative和absolute 属性语法: position : static | absolute | fixed | relative 取值: static :…

为网格布局图片打造的超炫 CSS 加载动画

今天&#xff0c;我想与大家分享一些专门为网格布局的图像制作的很酷的 CSS 加载动画效果。您可以把这些效果用在你的作品集&#xff0c;博客或任何你想要的网页中。设置很简单。我们使用了下面这些工具库来实现这个效果&#xff1a; Normalize.css 来替代传统的 CSS 复位&…

HTML多选框滚动条,《HTM单选.doc

《HTM单选1. 下面标记中&#xff0c;( )在标记的位置添加一个回车符。【选择一项】A. B. C. D. 2. 要实现以下功能&#xff1a;在网页中插入一个图片joke11.gif,使用者通过单击该图片&#xff0c;连接到joke11.htm上去。下面的HTML代码&#xff0c;( )是正确的。【选择一项】A…

python时间处理模块有哪些_Python模块之时间处理

time 模块>>> import time>>> dir(time)[__doc__, __name__, __package__, accept2dyear, altzone, asctime, clock, ctime, daylight, gmtime, localtime, mktime, sleep, strftime,strptime, struct_time, time, timezone, tzname]包含的变量:timezone -- …

wel

欢迎来到mathant.com 这个网站是什么 这个网站是我搭建在阿里云vps上的个人网站。目前的用途是充当个人博客和云存储&#xff0c;当然它的功能不止如此。我会在以后的日子里完善他&#xff0c;希望他能变得更好。目前我在主机上只搭建了这个个人博客和一个ftp服务器。这个网站采…

php 安装rabbitmq扩展无报错版

需要安装rabbitmq-c&#xff0c;rabbitmq-c是一个用于C语言的&#xff0c;与AMQP server进行交互的client库。下载了v0.5.2版本(https://github.com/alanxz/rabbitmq-c/releases/download/v0.5.2/rabbitmq-c-0.5.2.tar.gztar xvf rabbitmq-c-0.5.2.tarcd rabbitmq-c-0.5.2autor…

ImageMagick 打水印支持透明度设置

convert 35021021120924162418300.jpg DD.png -geometry 60002048 -compose dissolve -define compose:args50 -composite -quality 95 35021021120924162418300_res.jpg转载于:https://www.cnblogs.com/mfryf/archive/2013/03/04/2943209.html

spring mvc使用html页面,使用Spring MVC的纯HTML页面应用程序

在Spring MVC所有的请求经过FrontController - DispatcherServlet的有你需要告诉Spring allowe JSP和HTML都在你的情况例dispatcher-servlet.xml:xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:p"http://www.springframework.org/schema/p"x…

python win+r时不成功_Win与R(不使用Anaconda的情况下)

配置R的路径信息&#xff1a;Path&#xff1a;添加R.dll的路径 如&#xff1a;E:\software\R\R-3.5.1\bin\x64R_HOME:R的安装路径 如&#xff1a;E:\software\R\R-3.5.1\binR_USER:R的使用路径 如&#xff1a;E:\software\R\R-3.5.1\bin\x64pip install rpy2在win下安装失败&am…

Devexpress VCL Build v2014 vol 14.1.4 发布

虽然这次没加什么新东西&#xff0c;但是及时更新支持xe7&#xff0c;还算可以。 Whats New in 14.1.4 (VCL Product Line) New Major Features in 14.1 Whats New in VCL Products 14.1 Feature Highlights To learn about feature highlights in this version, please refer …

一个YII社区学习网站

2019独角兽企业重金招聘Python工程师标准>>> https://getyii.com/ 转载于:https://my.oschina.net/u/2552765/blog/803311

一站式 Java Web 框架 firefly-2.0_07发布

Firefly是一个高性能一站式Web框架。 涵盖了web开发的主要技术栈。 包含Template engine、IOC、MVC framework、HTTP Server、Common tools、Log、Json parser等模块。 firefly-2.0_07修复了模版压缩对javascript单行注释的影响&#xff0c;并新增了自定义错误页面功能。 更新日…

计算机控制学什么,计算机控制技术专业介绍

专业前景需要早了解&#xff0c;计算机控制技术专业学什么&#xff0c;好不好找工作等是学子和家长朋友们十分关心的问题。以下是个人简历网整理的计算机控制技术专业介绍、主要课程、培养目标、就业前景&#xff0c;供大家参考。1、计算机控制技术专业简介计算机控制技术专业&…

【Python】Python 批量转换PDF到Excel

PDF是面向展示和打印使用的&#xff0c;并未考虑编辑使用&#xff0c;所以缺少了很多编辑属性且非常难修改PDF里面的数据。当您需要分析或修改PDF文档数据时&#xff0c;可以将PDF保存为Excel工作簿&#xff0c;实现轻松编辑数据的需求。PDF转Excel&#xff0c;技术关键就是提取…

js showModalDialog参数的使用详解(转)

js showModalDialog参数的使用详解_javascript技巧_脚本之家 http://www.jb51.net/article/45281.htm 本篇文章主要是对js中showModalDialog参数的使用进行了详细的分析介绍&#xff0c;需要的朋友可以过来参考下&#xff0c;希望对大家有所帮助 基本介绍&#xff1a; showModa…

ad19生成gerber文件_在“AD19”中怎样将PCB文件转换为GERBER

四川自贡是历史悠久的老工业城市&#xff0c;上世纪八、九十年代&#xff0c;自贡的锅炉、泵业、阀门全国闻名&#xff0c;在近年发展中&#xff0c;电子产业也取得可喜的成绩。Altium Designer在设计电子产品中是应用较多的工具&#xff0c;它的版本更新很快&#xff0c;从最早…