Tomcat-JDBC源码解析及优化

2023-11-12

数据库连接池

连接池是常见的一种资源复用的技术。利用连接池,可以将那些创建开销较大的资源汇聚到一个池子里缓存起来,需要使用的时候只需要从连接池里取出来就可以了。中间省去了频繁的创建和销毁的过程。数据库连接池就是其中的典型应用。

深入Tomcat-JDBC

Tomcat-JDBCSpring Boot中自动配置优先级最高的连接池方案,它的出现是用来替代Apache早期的连接池产品——DBCP 1.x。总得来说,各款连接池的原理大同小异,具体还得看细节,比如某些早期连接池对于并发和利用CPU多核考虑得就不够到位。
在介绍Tomcat-JDBC之前,我们可以简单的思考一下,假设让我们来实现一个数据库连接池,会有哪些问题需要解决?

  1. 如何保障缓存连接的有效性
  2. 如何维护连接池中连接的数量

现在,我们可以带着上面的两个问题来看看Tomcat-JDBC的实现细节

核心动作解析

连接池初始化

Created with Raphaël 2.1.0 开始 连接池参数矫正 构建队列(idle和busy) 初始化清洁工任务 初始化JdbcInterceptors,并调用每个拦截器的poolStarted方法 调用initialSize次borrowConnection方法用于创建初始化连接 将所有连接全部return回连接池 结束

数据库连接池初始化的核心就是:

  1. 构建idle和busy队列
  2. 根据参数决定是否启动清洁工任务
  3. JdbcInterceptors的初始化
  4. 根据参数initialSize创建初始化连接

TIPS:在【初始化JdbcInterceptors】环节,会调用每个拦截器的poolStarted方法。但是这里的JdbcInterceptor实例只是临时创建,不会在后续使用,所以在自己实现JdbcInterceptor重写poolStarted方法的时候,不要里面操作类的成员变量,只有操作静态变量才是有意义的

清洁工任务是什么?

这里写图片描述
上图主要可能会涉及到三块清理业务:

  1. 取出后长期未操作且未还回连接池的连接
  2. idle队列长度大于minIdle时的缩容
  3. idle队列中的所有连接的有效性校验

其中有几点可能从上图看不是特别清楚的再说明下:

  1. waitCount是什么?在从连接池获取连接时,如果连接池的连接数量已达到maxActive,且全部被占用,且maxWait配置大于0,那么会进入wait状态,此时waitCount会加1,后面在【获取连接】这个核心步骤的时候还会提到
  2. connectionVersion 和 poolVersion 是什么?这个是连接池用来实现purge功能的,也就是可以将连接池中的当前连接全部失效。这个功能最简单的做法就是给pool设计一个版本,比如为V1,那么从V1创建出来的连接的版本也都是V1。这个时候想失效所有连接,只需要把poolVersion升到V2,那么V1的连接将全部失效。
  3. 针对于第二个任务,有个可优化的点,可以先判断minEvictableIdleTimeMillis是否大于0,如果不大于0,那么都不需要再遍历idle队列了
  4. 针对于第三个任务,如何判断连接有效性?在下一节【有效性验证】中会有讲解,此处对应的类型为testWhileIdle

哪些参数控制着清洁工任务的启动?

  1. timeBetweenEvictionRunsMillis,清洁工任务频率
  2. removeAbandoned/removeAbandonedTimeout,是否释放取出时间过长的连接
  3. suspectTimeout,疑似超时
  4. testWhileIdle/validateQuery,定时测试空闲连接
  5. minEvictableIdleTimeMillis,在连接池闲置一定时间会被当做空闲连接

1是必要条件,2|3|4|5 只要有一个满足,那么就会启动清洁工任务

有效性验证

还记得刚才的清洁工任务中有一个是专门来检查空闲连接的有效性的,下面就来介绍如何判断一个连接的有效性:

这里写图片描述

其中,所有类型的验证都在这里维护,包括

  1. 创建连接时 testOnConnect
  2. 从连接池取出时 testOnBorrow
  3. 放回连接池时 testOnReturn
  4. Idle连接 testWhileIdle

上面这四种类型,在【需要验证】这个步骤里会有体现,分别对应上面的配置项。
另外,对于非testOnConnect这种类型的验证,可以用到validationInterval来避免频繁的连接验证

下面再给出和有效性验证相关的参数:

  • testOnBorrow : 获取连接后是否检验连接有效性,影响性能
  • testOnConnect : 创建连接后是否检验连接有效性,影响性能
  • testOnReturn : 将连接返回连接池前是否检验链接有效性,影响性能
  • testWhileIdle : 清洁工定时任务校验空闲连接的有效性
  • validationQuery : 校验连接有效性的查询语句,Mysql基本就是select 1
  • validationQueryTimeout : 校验连接有效性的查询语句的超时时间
  • validationInterval:防止频繁校验,如上一次与本次校验时间不超过validationInterval,则不会执行校验,直接返回校验通过
  • logValidationErrors:有效性校验失败是否记录日志

获取连接

这里写图片描述

关于【获取连接】,有两点需要注意一下:

  1. 可能原先对maxWait有点误解,以为是【获取连接】的最大等待时间,这个理解是不对的,从流程图中可以看出,这个maxWait只在连接池满的时候才有用,并且指的是等待idle队列有新的空闲连接的最大超时时间
  2. 关于连接有效性校验的步骤,有一种比较特殊:直接从连接池中拿到连接,做testOnBorrow的校验时,如果第一次校验失败,还会给予一次reconnect的机会去重连数据库,然后继续校验(这次校验不通过那就报错了)。其他的有效性校验只要不通过就报错

返回连接

这里写图片描述
返回连接的核心是将取出的连接放回连接池中,但是在放回池中之前会做一系列的校验:比如是否超过maxAge,有效性验证是否通过等。如果前置校验通不过,那么会将该连接直接释放掉,而不返回到池中。

连接池维护相关参数

下面这些参数在上述流程中基本都有提到,可以结合起来再回顾一下:

  • maxActive : 视系统负载而定,默认值100
  • maxIdle : 视系统负载而定,只有在未开启清洁工任务的情况下会使用到,用在returnToPool操作当中,如果判断当前idle队列大小已经大于等于maxIdle了,则会把该连接释放。默认值 = maxActive
  • minIdle : 池中常驻的空闲连接数量,如果Idle队列大小超过该值,且在池中闲置超过minEvictableIdleTimeMillis的连接将会被释放(只要启用了清洁工任务)
  • initialSize : 初始化时创建的连接数量
  • maxWait : 获取连接时,超过maxActive后,等待idle队列重新有新连接的时长,注意,这个等待时间并不考虑建立连接的耗时
  • maxAge:获取连接时或者返回连接时,都会判断 当前时间 - 连接创建时间 有没有超过 maxAge,如果超过了,则该连接会被释放
  • removeAbandoned:是否移除长时间取出且未归还的连接,对于应用层面没有关闭连接的情况做一个兜底
  • removeAbandonedTimeout:时间阈值
  • abandonWhenPercentageFull:移除还有个前置比例的判断
  • logAbandoned : 是否记录通过abandoned移除的连接
  • suspectTimeout:没什么实质性的功能,最多也就打打日志,并且只会在非abandoned情况或者是关闭了removeAbandoned的情况下才有可能起作用

拦截器

也就是JdbcInterceptor,继承它的拦截器可以拦截connection所有方法的调用。
下面列几个个人觉得非常有用的拦截器:

QueryTimeoutInterceptor

可配置Sql超时时间x,超过该时间的Sql将被Kill掉,客户端报错。简单原理就是执行Sql之前会延迟x毫秒启动一个定时任务,该定时任务就是发送Kill Query命令到数据库,如果在x毫秒之内执行完,那么该定时任务会在执行前就被cancel掉,如果到了x毫秒还没执行完,那么定时任务启动,Kill Query,导致客户端连接报错。这个可以对系统起到一定的保护和监控作用

SlowQueryReport

可配置慢查的阈值,主要就是记录慢查,但是这个东西有点不完善的地方,就是打慢查日志只会打PrepareStatement,打不了里面的参数,这样会造成无法拿Sql去DB里找对应的查询,后面在优化之路里会给出一个增强的慢查监控拦截器代码

ConnectionState

用来缓存autoCommit, readOnly, transactionIsolation和catalog这几个属性,将它们缓存在本地,避免各种和数据库之间的roundtrip消耗。比如:

  1. getAutoCommit时如果本地有缓存,则直接读取本地缓存的autoCommit值
  2. setAutoCommit时如果发现与本地缓存一致,则无需发送请求到数据库

其他参数

  • useDisposableConnectionFacade:默认为true,多加了一层Interceptor,防止同一个线程中取出连接,close之后再执行sql,感觉没太大作用

排查数据库相关问题的一些经验

没有慢查,但是请求响应很慢,并且该请求的处理逻辑只有一个数据库事务操作,如何排查?

想要搞清楚这个问题,首先需要了解执行一个事务,我们到底会和数据库(MySQL)产生几次交互?
我们以一个简单的事务为例,比如说订单到店这个事务,涉及到订单表的更新,以及订单操作记录表的更新,我们简化成两条语句:update order 和 update order_operate_record ,在执行该事务的过程中,我们会和MySQL交互几次呢?
1. 先从连接池取出连接,其中可能会碰到连接池中没有空闲连接的情况,这个时候假设还没超出最大活跃连接的话,连接池会发起创建连接,这时就会产生一次交互,并且建立连接的消耗相对于执行Sql更大
2. 根据不同的连接池参数配置,可能还需要对取出的连接做有效性校验,MySQL中一般都是用SELECT 1来充当校验语句的
3. 下面需要开启事务,SET AUTOCOMMIT = 0
4. 然后再发送两条update语句
5. 最后需要告诉MySQL我们的事务结束了,COMMIT,当然也有可能中间碰到一些问题,ROLLBACK掉
6. 还记得第三步开启事务的时候,执行的 SET AUTOCOMMIT = 0吗?所以做为完整的事务操作,最后还有一步SET AUTOCOMMIT = 1
7. 结束了么?最后还要把连接放回连接池。貌似不用和MySQL交互?放回去之前,根据不同的连接池参数配置,可能还需要对放回去的连接做有效性校验。等等,除了有效性校验之外,可能还会有maxAge/maxIdle之类的校验?不过这个不用和MySQL交互。好吧,这里先打住,不然内容太多了。
看看上面的内容,你大概知道一个事务的执行,不仅仅只有事务中的两行更新语句和数据库有交互吧,所以,也不难理解为什么慢查抓不到,但是实际请求处理得很慢了。但是就这样结束了么?既然对于慢查可以监控,为什么不把所有和MySQL有交互的点都监控起来呢?好,有想法是好事,那我们来看看如何把所有的节点都监控起来?
Tomcat-JDBC连接池似乎不提供这个功能。更换连接池?貌似有点牵强。我们何不转向与MySQL更加紧密的MySQL驱动呢?翻阅了MySQL驱动的官方文档,发现其中是有性能监控的开关——profileSQL
通过这个开关,我们可以观察到应用与MySQL交互的每一条语句,包括建立连接时做了哪些初始化操作?什么时候开启事务,什么时候校验连接有效性,什么时候提交事务等,都会有日志打印,包括耗时。
但是,通过上述的配置项,还是无法监控到我们上面第一点的建立连接的耗时,这块通过查询驱动源码发现也是可以实现的,于是自己动手,一点点代码量就完成了这个小功能。至此,上述每个节点都有迹可循,目测可以轻松的找到耗时所在。

优化之路

1. 上面的问题中与MySQL的交互是否能减少

当然是可以的。
首先最容易想到的就是连接有效性校验那一块儿,比如上面提到的第2点和第7点里,也就是取出连接之后(testOnBorrow/testOnConnect)和把连接放回连接池(testOnReturn)之前,可能需要做的校验操作,我们可以省去。那么有效性怎么保证呢?理论上来说,大多数情况下都是有效的,除非数据库挂了之类,所以单独线程来做就好了(testWhileIdle)
第5点的COMMIT貌似也可以省去,看了官网,应该是只要再SET AUTOCOMMIT = 1的时候会自动COMMIT,不过这个目前还没有验证过

2. 如何监控创建数据库连接的耗时?

这个通过连接池似乎不太好做,我们可以通过MySQL Connector提供的ConnectionLifecycleInterceptor来实现:

/**
 * Created by Zhu on 2017/9/19.
 */
public class ConnectionLifeInteceptor implements ConnectionLifecycleInterceptor{

    private ConnectionImpl connection;

    public static final Logger LOGGER = LoggerFactory.getLogger(ConnectionLifeInteceptor.class);

    // 这里只关注创建连接耗时
    @Override public void init(Connection conn, Properties props) throws SQLException {
        this.connection = (ConnectionImpl) conn;
        Field field = ReflectionUtils.findField(conn.getClass(), "connectionCreationTimeMillis", Long.TYPE);
        ReflectionUtils.makeAccessible(field);
        Long connectionCreationTimeMillis = (Long) ReflectionUtils.getField(field, conn);
        LOGGER.info("connection:{} cost:{}", connection.getId(), System.currentTimeMillis() - connectionCreationTimeMillis);
    }
}

3. 是否能对创建连接做timeout设置?

超时设置一直都是系统优化中很重要的节点,所以这里自然而然就想到了是否可以通过timeout来避免潜在的创建连接hang住的风险。答案自然是肯定的,但是翻遍Tomcat-JDBC连接池配置也并找不到类似的配置。想必聪明的你已经想到了MySQL Connector。没错,还是MySQL Connector,它提供了一个参数connectTimeout用来设置创建连接的超时时间。

4. 衰老连接重连问题

通过监控创建连接耗时帮助我们最后定位到偶尔慢的现象是因为取出的连接衰老而死(超过maxAge),触发了reconnect,导致重新与MySQL建立连接,并且建立连接耗时1s左右。那怎么办呢?我们可以扫描idle队列里即将要超过maxAge(比如60s内)的连接,比如发现快要过期了,那么我们就拿该连接reconnect一下,重新激活,该任务我们可以直接置于PoolCleaner中,附上部分代码:

    protected static class PoolCleaner extends TimerTask {
        // 省略部分代码
        @Override
        public void run() {
            // 省略部分代码
            if (pool.getPoolProperties().isTestWhileIdle()){
                pool.testAllIdle();
                pool.recoverNearDeath();
            }
            // 省略部分代码  
        }
        // 省略部分代码  
}

    public void recoverNearDeath(){
        try {
            if (idle.size()==0) return;
            Iterator<PooledConnection> unlocked = idle.iterator();
            while (unlocked.hasNext()) {
                PooledConnection con = unlocked.next();
                try {
                    con.lock();
                    // 连接被取出,不做处理
                    if (busy.contains(con))
                        continue;
                    if (con.isNearDeath()){
                        log.info("Connection ["+con+"] is near death, last connected:" + con.getLastConnected());
                        con.reconnect();
                    }
                } finally {
                    con.unlock();
                }
            } //while
        } catch (Exception e) {
            log.error("recoverNearDeath failed",e);
        }
    }

    public boolean isNearDeath(){
        // 这里暂时不做配置,定义离maxAge还差1min以内的为濒临死亡的连接
        final long val = 60000;
        return (System.currentTimeMillis() - getLastConnected()) > (getPoolProperties().getMaxAge() - val);
    }

5. 重写慢查监控拦截器,用来打印参数解析完的SQL

/**
 * @author Zhu
 * @date 2017年3月22日 下午11:00:11
 * @description
 */
public class MonitorSlowQueryReport extends SlowQueryReport {

    private String systemCode;
    // logger
    private static final Logger LOGGER = LoggerFactory.getLogger(MonitorSlowQueryReport.class);

    class RecordParamStatementProxy extends StatementProxy {

        /**
         * @param parent
         * @param query
         */
        public RecordParamStatementProxy(Object parent, String query) {
            super(parent, query);
        }

        /*
         * (non-Javadoc)
         * 
         * @see org.apache.tomcat.jdbc.pool.interceptor.AbstractQueryReport.
         * StatementProxy#invoke(java.lang.Object, java.lang.reflect.Method,
         * java.lang.Object[])
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().startsWith("set") && args != null && args.length >= 2) {
                ParamHolder.params.get().add(args[1]);
            }
            Object result = null;
            try {
                result = super.invoke(proxy, method, args);
            } finally {
                if (isExecute(method, false)) {
                    ParamHolder.params.remove();
                }
            }
            return result;
        }
    }

    @Override
    public void setProperties(Map<String, InterceptorProperty> properties) {
        super.setProperties(properties);
        final String systemCode = "systemCode";
        InterceptorProperty p1 = properties.get(systemCode);
        if (p1 != null) {
            setSystemCode(p1.getValue());
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport#reportSlowQuery(
     * java.lang.String, java.lang.Object[], java.lang.String, long, long)
     */
    @Override
    protected String reportSlowQuery(String query, Object[] args, String name, long start, long delta) {
        // extract the query string
        String sql = (query == null && args != null && args.length > 0) ? (String) args[0] : query;
        // if we do batch execution, then we name the query 'batch'
        if (sql == null && compare(EXECUTE_BATCH, name)) {
            sql = "batch";
        }
        if (isLogSlow() && sql != null) {
            String beautifulSql = sql.replace("\n", "").replaceAll("[' ']+", " ");
            LOGGER.warn("Slow Query Report SQL={}; param:[{}], consume={};", beautifulSql,
                    StringUtils.join(ParamHolder.params.get(), ','), delta);    
        }
        return sql;
    }

    public String getLocalHostAddress() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            LOGGER.error("获取本地ip异常", e);
        }
        return "";
    }


    /**
     * 为了打印全貌sql,重写一下
     */
    @Override
    public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
        try {
            Object result = null;
            String name = method.getName();
            String sql = null;
            Constructor<?> constructor = null;
            if (compare(CREATE_STATEMENT, name)) {
                // createStatement
                constructor = getConstructor(CREATE_STATEMENT_IDX, Statement.class);
            } else if (compare(PREPARE_STATEMENT, name)) {
                // prepareStatement
                sql = (String) args[0];
                constructor = getConstructor(PREPARE_STATEMENT_IDX, PreparedStatement.class);
                if (sql != null) {
                    prepareStatement(sql, time);
                }
            } else if (compare(PREPARE_CALL, name)) {
                // prepareCall
                sql = (String) args[0];
                constructor = getConstructor(PREPARE_CALL_IDX, CallableStatement.class);
                prepareCall(sql, time);
            } else {
                // do nothing, might be a future unsupported method
                // so we better bail out and let the system continue
                return statement;
            }
            result = constructor.newInstance(new Object[] { new RecordParamStatementProxy(statement, sql) });
            return result;
        } catch (Exception x) {
            LOGGER.warn("Unable to create statement proxy for slow query report.", x);
        }
        return statement;
    }

    /**
     * @return the systemCode
     */
    public String getSystemCode() {
        return systemCode;
    }

    /**
     * @param systemCode
     *            the systemCode to set
     */
    public void setSystemCode(String systemCode) {
        this.systemCode = systemCode;
    }
}

6. MySQL Connector 其他一些有趣的参数

后续再更新吧

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Tomcat-JDBC源码解析及优化 的相关文章

  • 关于Java调用dll的方法

    Java语言本身具有跨平台性 如果通过Java调用DLL的技术方便易用 使用Java开发前台界面可以更快速 也能带来跨平台性 Java调用C C 写好的DLL库时 由于基本数据类型不同 使用字节序列可能有差异 所以在参数传递过程中容易出现问
  • HashMap的数据操作演示

    import java util public class mytest public static void main String args mytest mytest new mytest Map map new HashMap fo
  • ERROR - Connection is read-only.

    今天在serviceImpl的查询中 调用了一样更新的操作 结果出现如下错误 ERROR Connection is read only Queries leading to data modification are not allowe
  • 集合框架集-List

    1 UML 统一建模语言 例如 类图 用例图等等 2 Collection接口 1 1 集合框架的顶级接口 1 2 是Set和List的父接口 1 3 但不是Map的父接口 集合中只能添加引用类型数据 Collection接口 是集合框架的
  • IDEA使用JUnit时@Test无效以及无法导入org.junit包的一系列问题

    先找到idea的安装位置 进入lib文件夹 然后打开idea File gt Project Structure 选择Project Settings中的Libraries 点击如图 号 然后添加以下两个包 点击OK 添加成功就可以了 ht
  • C++11实现的数据库连接池

    它什么是 数据库连接池负责分配 管理和释放数据库连接 它允许应用程序重复使用一个现有的数据库连接 而不是再重新建立一个 类似的还有线程池 为什么要用 一个数据库连接对象均对应一个物理数据库连接 每次操作都打开一个物理连接 使用完都关闭连接
  • 在windows上装oracle11g R2 小麻烦

    发现在windows上装11GR2这么麻烦的 因为下载有两个包 所以 必须要把stage里面的components中的内容copy在一起 或将下载下来的两个包解压到同一个目录下 否则会报错误找不到em ear等文件 大家小心了
  • 文件md5验证生成器(java版)

    import java applet import java io import java security public class HashFile public static char hexChar 0 1 2 3 4 5 6 7
  • 将文件或目录移动到另外的目录

    File or directory to be moved File file new File filename Destination directory File dir new File directoryname Move fil
  • 链塔智库

    目录 一 各地政策要闻 宁夏 推广区块链等技术实现数字化转型 宁波 前瞻性布局区块链等未来产业 重庆 区块链等新一代信息技术产业占全市软件业务收入总额近两成 云南 积极探索区块链等新技术在药品安全事前事中事后监管应用 广西 充分发挥跨境金融
  • 通过Hyperic-hq产品的基础包sigar.jar来实现服务器状态数据的获取

    通过Hyperic hq产品的基础包sigar jar来实现服务器状态数据的获取 Sigar jar包是通过本地方法来调用操作系统API来获取系统相关数据 Windows操作系统下Sigar jar依赖sigar amd64 winnt d
  • Web应用下实现定时任务简便方法

    在WEB应用下实现定时任务的简便方法 在web方式下 如果我们要实现定期执行某些任务的话 除了用quartz等第三方开源工具外 我们可以使用Timer和TimeTask来完成指定的定时任务 第一步 创建一个任务管理类 实现ServletCo
  • JAVA 写Excel附件 每天定时发送邮件

    JAVA 写Excel附件 每天定时发送邮件 http hi baidu com star850323 blog item 63c5750f520e05ec37d1228d html http hi baidu com star850323
  • javaRebel(jRebel)使用手记

    想必大家对项目开发中 调试类文件修改时 容器自动重新加载漫长的过程早已厌倦 我今天闲来无事 于是 想试试javaRebel jRebel 这个东西 javaRebel jRebel 现在是收费软件 不过在网上可以下载到确解版的 在网上查了一
  • Java操作json的通用类

    package com baiyyy polabs util json import java text ParseException import java util ArrayList import java util Date imp
  • struts2配置commons-fileupload的问题

    这个问题主要出现在上传文件时parseRequest 的值为空 原因是struts2的Filter拦截了 ServletFileUpload sfu new ServletFileUpload factory List fileList s
  • Java调用Win API

    官方网站 http jawinproject sourceforge net 把lib文件夹下的jawin jar和jawin stubs jar放到 JAVA HOME jre lib ext 目录下 把bin文件夹下的jawin dll
  • netty源码分析之LengthFieldBasedFrameDecoder

    http www jianshu com p a0a51fd79f62 hmsr toutiao io utm medium toutiao io utm source toutiao io 拆包的原理 关于拆包原理的上一篇博文 netty
  • Netty 4.0 实现心跳检测和断线重连

    一 实现心跳检测 原理 当服务端每隔一段时间就会向客户端发送心跳包 客户端收到心跳包后同样也会回一个心跳包给服务端 一般情况下 客户端与服务端在指定时间内没有任何读写请求 就会认为连接是idle 空闲的 的 此时 客户端需要向服务端发送心跳
  • Spring-Boot:如何设置 JDBC 池属性,例如最大连接数?

    Spring Boot 是一个非常棒的工具 但是当涉及到更高级的配置时 文档有点稀疏 如何设置数据库连接池的最大大小等属性 Spring Boot 支持tomcat jdbc HikariCP and Commons DBCP它们本身的配置

随机推荐

  • 【Windows】Windows下wget的安装与环境变量配置

    1 wget安装 GNU Wget常用于使用命令行下载网络资源 包括但不限于文件 网页等 GNU Wget官网 GNU Wget GNU Wget for Windows GNU Wget for Windows 安装时首先下载主安装包 C
  • 老王的24天,

    数组元素的反转 数组元素的反转 本来的样子 1 2 3 4 之后的样子 4 3 2 1 要求不能使用新数组 就用原来的一个数组 public class Demo07ArrayReverse public static void main
  • nRF52832 — 多通道ADC接口的使用

    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XX 作 者 文化人 XX 联系方式 XX 版权声明 原创文章 欢迎评论和转载 转载时能告诉我一声就最好了 XX 要说的
  • Golang基础(项目结构)

    一 标准的项目结构 在实际开发中不可能只有一个main包 更不可能就只有一个 go文件 不同级别大小的项目中包和文件数量都不同 Go语言中组织单元最大的为项目 项目下包含包 一个包可以有多个文件 包在物理层面上就是文件夹 同一个文件夹中多个
  • iOS App的上架和版本更新流程

    一 前言 作为一名iOSDeveloper 把开发出来的App上传到App Store是必要的 下面就来详细讲解一下具体流程步骤 二 准备 一个已付费的开发者账号 账号类型分为个人 Individual 公司 Company 企业 Ente
  • curl使用总结

    curl使用官网 https curl haxx se docs manpage html 1 查看curl的安装版本以及支持的协议 curl V 2 CURL分析HTTPS请求耗时时间 HTTPS耗时 TCP握手 SSL握手 因为涉及到一
  • 短视频矩阵系统源代码开发搭建分享--代码开源SaaS

    一 什么是短视频矩阵系统 短视频矩阵系统是专门为企业号商家 普通号商家提供帐号运营从流量 到转化成交的一站式服务方案 具体包含 点赞关注评论主动私信 评论区回复 自动潜客户挖掘 矩阵号营销 自动化营销 粉丝 管理等功能 可以帮助企业或商家快
  • stl排序之sort函数

    STL容器的排序 支持随机访问的容器vector deque string没有sort成员 可调用std sort排序 list排序调用自带的list sort 下面是std sort函数 有两个版本 template
  • LED点阵书写显示屏

    LED点阵书写显示屏 题目的大概要求是做一个32 32的点阵书写屏 LED 点阵模块显示屏工作在人眼不易觉察的扫描微亮和人眼可见的 显示点亮模式下 当光笔触及 LED 点阵模块表面时 先由光笔检测触及位置处 LED 点 的扫描微亮以获取其行
  • springboot no tests were found

    springboot单元测试报错 no tests were found 如图所示 原因分析 1 进行单元测试的方法不能有返回值 2 方法不能私有化 以上两个问题都会报 no tests were found 错误 正确写法
  • 华为od机试 Java 【url拼接】

    题目 给定一个URL的前缀和后缀 我们需要将其合并成一个完整的URL 在合并时 请注意以下几点 如果前缀的结尾没有斜线 而后缀的开头也没有斜线 那么在两者之间需要添加一个斜线 如果前缀的结尾和后缀的开头都有斜线 那么需要保留其中的一个 删除
  • Vue-生命周期函数

    Vue 生命周期函数 一 生命周期和生命周期函数 生命周期 Life Cycle 是指一个组件从创建 gt 运行 gt 销毁的整个阶段 强调的是一个时间段 生命周期函数 是由vue 框架提供的内置函数 会伴随着组件的生命周期 自动按次序执行
  • 【js】从数组中随机选一个数,从数组中随机选几个数

    每组中随机选一个 每组中随机选一个 randomFun arr let ri Math floor Math random arr length return arr ri 使用 let arr 1 2 3 4 5 6 7 console
  • Android QQ 登录接入详细介绍

    今日科技快讯 近日 百度地图发布2022春节出行大数据 迁徙大数据显示 2022年春运迁徙规模较去年农历同期有明显上升 春节期间全国人口迁徙规模日均值为去年农历同期的近两倍 春节前的迁徙规模峰值出现在1月29日 腊月廿七 春节后于2月6日达
  • Python写简单的拼图小游戏(附源码、资源)

    郑重声明 嘿嘿 代码与图片已上传资源 需要者自取 资源地址 https download csdn net download qq 44651842 20009562 Python小白一只 正在成长 程序自己设计 很多不足 算法很多地方能优
  • Java语言与面向对象的程序设计

    这几天很迷茫 听老师介绍了一款软件 中国大学MOOC 刚好我是软件专业的学生 索性听了北京大学老师的一节关于Java的讲课 果断被震撼到了 他们的讲课给人一种很深刻 深入 的感觉 我今天就把老师讲到的东西记了下来 以便于有兴趣的朋友阅读 J
  • Qt快速入门学习笔记(画图篇)

    1 Qt中提供了强大的2D绘图系统 可以使用相同的API在屏幕和绘图设备上进行绘制 它主要基于QPainter QPaintDevice和QPaintEngine这三个类 其中QPainter用来执行绘图操作 QPaintDevice提供绘
  • java开发利用jacob将word转pdf

    jacob 缺点 需要 window 环境 而且速度是最慢的需要安装 msofficeWord 以及 SaveAsPDFandXPS exe word 的一个插件 用来把 word 转化为 pdf 开发流程 SaveAsPDFandXPS
  • STM32驱动BH1750模块

    模块描述 BH1750FVI是一款用于I2C总线接口的数字环境光传感器IC 该集成电路最适合获取环境光数据 用于调整手机的 LCD和键盘背光功率 可以在高分辨率下检测宽范围 1 65535 lx 引脚说明 VCC 5V GND GND SC
  • Tomcat-JDBC源码解析及优化

    数据库连接池 连接池是常见的一种资源复用的技术 利用连接池 可以将那些创建开销较大的资源汇聚到一个池子里缓存起来 需要使用的时候只需要从连接池里取出来就可以了 中间省去了频繁的创建和销毁的过程 数据库连接池就是其中的典型应用 深入Tomca