在多线程应用中,数据库操作往往是性能瓶颈与稳定性风险的高发区。当多个线程同时读写数据库时,若处理不当,轻则出现查询卡顿、事务冲突,重则导致数据错乱、连接泄漏甚至程序崩溃。Qt作为跨平台框架,提供了QSql系列类支持数据库操作,但原生API并未直接解决多线程场景下的效率与安全问题。本文将从线程安全基础出发,深入讲解数据库连接池设计、事务优化、并发控制等核心技术,通过实战案例展示如何将多线程数据库操作的性能提升3-5倍,同时保证数据一致性。

一、多线程数据库操作的核心挑战

在单线程应用中,数据库操作通常通过单一连接顺序执行,虽简单但无法利用多核资源,且容易阻塞UI线程。而多线程操作数据库虽能提升并发能力,但会面临一系列底层挑战,这是优化的前提。

1. 连接的线程安全性:最容易踩的“坑”

Qt的QSqlDatabaseQSqlQuery并非线程安全组件,这是多线程操作的首要风险点:

  • QSqlDatabase不能跨线程使用:官方明确规定,在一个线程中创建的QSqlDatabase对象,不能在另一个线程中直接使用(即使通过指针传递),否则会导致数据库驱动崩溃;
  • QSqlQuery依赖连接上下文:每个QSqlQuery必须关联到当前线程的数据库连接,跨线程复用查询对象会引发未定义行为;
  • 驱动差异:不同数据库驱动的线程安全特性不同(如SQLite默认不支持多线程写入,MySQL需配置thread_handling参数)。

错误示例:跨线程使用数据库连接

// 线程A中创建连接
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "conn1");
db.setDatabaseName("testdb");
db.open();// 线程B中直接使用线程A的连接(错误!)
QThread *thread = new QThread;
thread->start();
QMetaObject::invokeMethod(thread, [&db](){QSqlQuery query(db); // 严重错误:跨线程使用连接query.exec("SELECT * FROM users"); 
});

上述代码会导致数据库驱动内部状态混乱,可能出现查询无结果、连接句柄泄漏甚至程序崩溃。

2. 并发操作的性能瓶颈

即使保证了线程安全,不合理的多线程数据库操作仍会面临性能问题:

  • 连接创建开销:每次操作数据库都新建连接(QSqlDatabase::open()),会触发TCP握手、权限验证等耗时操作(单次连接耗时可达100-500ms);
  • 事务冲突:多个线程同时写入同一表时,会引发行锁/表锁竞争,导致事务等待甚至回滚;
  • 资源竞争:无限制创建线程和连接会导致数据库连接数超限(多数数据库默认连接上限为100-500),引发“too many connections”错误;
  • UI阻塞风险:若在主线程执行耗时查询(如大表扫描),会导致界面卡顿。

3. 数据一致性挑战

多线程并发写入时,若缺乏有效的同步机制,会导致数据一致性问题:

  • 丢失更新:两个线程同时更新同一条记录,后提交的更新覆盖前一次更新,导致数据丢失;
  • 脏读:线程A读取到线程B未提交的事务数据,若B回滚,A读取的数据无效;
  • 不可重复读:线程A两次查询同一数据,期间线程B修改并提交了数据,导致A两次结果不一致。

二、数据库连接池:线程安全的基石

解决多线程数据库操作的核心方案是数据库连接池——通过预创建一定数量的数据库连接,由连接池统一管理连接的创建、复用、释放,避免频繁创建连接的开销,同时保证每个线程安全使用独立连接。

1. 连接池核心设计思想

连接池的本质是“资源复用”与“线程隔离”,其核心设计需包含以下组件:

  • 连接容器:用队列/栈存储可用连接,通过互斥锁保证线程安全访问;
  • 连接创建策略:初始化时创建最小连接数,当可用连接不足时动态扩容至最大连接数;
  • 连接复用机制:线程从池获取连接,使用完毕后归还(而非关闭),供其他线程复用;
  • 连接有效性检测:归还和获取连接时检查连接是否有效(如通过ping命令),失效则重建;
  • 超时控制:当所有连接都被占用时,获取连接的线程等待超时后返回错误,避免无限阻塞。

2. 连接池实现代码:Qt连接池核心框架

以下是一个可直接复用的Qt数据库连接池实现,支持MySQL、SQLite、PostgreSQL等主流数据库:

// DatabasePool.h
#ifndef DATABASEPOOL_H
#define DATABASEPOOL_H#include <QSqlDatabase>
#include <QMutex>
#include <QWaitCondition>
#include <QString>
#include <QQueue>
#include <QMap>class DatabasePool {
public:// 单例模式:全局唯一连接池static DatabasePool& instance();// 设置连接池参数void setConfig(const QString& driver, const QString& host, int port,const QString& dbName, const QString& user, const QString& password,int minConnections = 5, int maxConnections = 20, int waitTimeout = 5000);// 从连接池获取连接(线程安全)QSqlDatabase getConnection();// 归还连接到连接池(线程安全)void releaseConnection(const QSqlDatabase& db);private:DatabasePool();~DatabasePool();DatabasePool(const DatabasePool&) = delete;DatabasePool& operator=(const DatabasePool&) = delete;// 创建新连接QSqlDatabase createConnection();// 检查连接是否有效bool isConnectionValid(const QSqlDatabase& db);// 连接池配置参数struct Config {QString driver;    // 数据库驱动(如"QMYSQL")QString host;      // 主机地址int port;          // 端口号QString dbName;    // 数据库名QString user;      // 用户名QString password;  // 密码int minConnections; // 最小连接数int maxConnections; // 最大连接数int waitTimeout;   // 获取连接超时时间(ms)} config;QQueue<QString> freeConnections; // 可用连接名队列QMap<QString, QSqlDatabase> allConnections; // 所有连接(连接名->连接)QMutex mutex; // 保护连接队列的互斥锁QWaitCondition waitCondition; // 等待可用连接的条件变量int nextConnId = 0; // 连接ID生成器
};#endif // DATABASEPOOL_H
// DatabasePool.cpp
#include "DatabasePool.h"
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
#include <QTime>DatabasePool::DatabasePool() {}DatabasePool::~DatabasePool() {// 析构时关闭所有连接QMutexLocker locker(&mutex);for (const QString& connName : allConnections.keys()) {QSqlDatabase::removeDatabase(connName);}allConnections.clear();freeConnections.clear();
}DatabasePool& DatabasePool::instance() {static DatabasePool pool;return pool;
}void DatabasePool::setConfig(const QString& driver, const QString& host, int port,const QString& dbName, const QString& user, const QString& password,int minConnections, int maxConnections, int waitTimeout) {QMutexLocker locker(&mutex);config.driver = driver;config.host = host;config.port = port;config.dbName = dbName;config.user = user;config.password = password;config.minConnections = qMax(1, minConnections); // 最小连接数至少为1config.maxConnections = qMax(config.minConnections, maxConnections);config.waitTimeout = waitTimeout;// 初始化连接池:创建最小数量的连接for (int i = 0; i < config.minConnections; ++i) {QString connName = createConnection();if (!connName.isEmpty()) {freeConnections.enqueue(connName);}}
}QString DatabasePool::createConnection() {// 生成唯一连接名(格式:"dbpool_conn_xxx")QString connName = QString("dbpool_conn_%1").arg(++nextConnId);// 创建连接QSqlDatabase db = QSqlDatabase::addDatabase(config.driver, connName);db.setHostName(config.host);db.setPort(config.port);db.setDatabaseName(config.dbName);db.setUserName(config.user);db.setPassword(config.password);// SQLite特殊配置:开启多线程模式if (config.driver == "QSQLITE") {db.setConnectOptions("QSQLITE_THREADSAFE=1"); // 线程安全模式}// 打开连接if (!db.open()) {qCritical() << "Create connection failed:" << db.lastError().text();QSqlDatabase::removeDatabase(connName);return "";}allConnections[connName] = db;return connName;
}bool DatabasePool::isConnectionValid(const QSqlDatabase& db) {// 检查连接是否有效:执行ping命令或简单查询if (!db.isOpen()) return false;QSqlQuery query(db);// MySQL/SQLite通用检查:查询版本号if (query.exec("SELECT 1")) {return true;} else {qWarning() << "Connection invalid:" << query.lastError().text();return false;}
}QSqlDatabase DatabasePool::getConnection() {QMutexLocker locker(&mutex);QTime timer;timer.start();// 循环等待可用连接while (true) {// 检查是否有可用连接if (!freeConnections.isEmpty()) {QString connName = freeConnections.dequeue();QSqlDatabase db = allConnections[connName];// 验证连接有效性,无效则重建if (isConnectionValid(db)) {return db;} else {// 连接无效,重建并替换QSqlDatabase::removeDatabase(connName);allConnections.remove(connName);connName = createConnection();if (!connName.isEmpty()) {return allConnections[connName];}}}// 无可用连接,检查是否可扩容if (allConnections.size() < config.maxConnections) {QString connName = createConnection();if (!connName.isEmpty()) {return allConnections[connName];}}// 无法扩容,等待可用连接(超时退出)if (timer.elapsed() >= config.waitTimeout) {qCritical() << "Get connection timeout (>" << config.waitTimeout << "ms)";return QSqlDatabase(); // 返回无效连接}// 等待100ms后重试waitCondition.wait(&mutex, 100);}
}void DatabasePool::releaseConnection(const QSqlDatabase& db) {if (!db.isValid()) return;QString connName = db.connectionName();if (!allConnections.contains(connName)) return;QMutexLocker locker(&mutex);// 检查连接是否仍有效,无效则移除if (!isConnectionValid(db)) {QSqlDatabase::removeDatabase(connName);allConnections.remove(connName);// 若总连接数低于最小连接数,补充新连接if (allConnections.size() < config.minConnections) {QString newConnName = createConnection();if (!newConnName.isEmpty()) {freeConnections.enqueue(newConnName);}}} else {// 连接有效,归还到可用队列freeConnections.enqueue(connName);}// 唤醒等待连接的线程waitCondition.wakeOne();
}

3. 连接池使用方法:线程安全的数据库操作

使用连接池时,每个线程通过getConnection()获取独立连接,操作完成后调用releaseConnection()归还,无需手动关闭连接:

// 线程任务:查询用户信息
void queryUserTask(int userId) {// 从连接池获取连接QSqlDatabase db = DatabasePool::instance().getConnection();if (!db.isOpen()) {qWarning() << "Get connection failed";return;}// 执行查询QSqlQuery query(db);query.prepare("SELECT name, age FROM users WHERE id = :id");query.bindValue(":id", userId);if (query.exec() && query.next()) {QString name = query.value(0).toString();int age = query.value(1).toInt();qDebug() << "User info: name=" << name << ", age=" << age;} else {qWarning() << "Query failed:" << query.lastError().text();}// 归还连接到池(必须调用!)DatabasePool::instance().releaseConnection(db);
}// 初始化连接池(通常在main函数或应用启动时)
void initDatabasePool() {DatabasePool::instance().setConfig("QMYSQL",       // 驱动"localhost",    // 主机3306,           // 端口"testdb",       // 数据库名"root",         // 用户名"123456",       // 密码5,              // 最小连接数20,             // 最大连接数5000            // 超时时间(ms));
}

4. 连接池关键参数调优

连接池的性能取决于参数配置,需根据业务场景调整:

  • 最小连接数(minConnections):建议设置为CPU核心数的1-2倍(如8核CPU设为10),保证基本并发需求;
  • 最大连接数(maxConnections):不超过数据库服务的连接上限(MySQL默认最大连接数为151),建议设为50-200(根据服务器配置);
  • 超时时间(waitTimeout):根据业务容忍度设置,一般设为3000-10000ms(3-10秒);
  • 连接检测频率:在高并发场景下,可缩短连接有效性检测的间隔(如每次归还连接时检测)。

三、事务优化:提升并发写入效率

多线程写入数据库时,频繁的事务提交会导致大量IO操作和锁竞争。通过合理的事务管理,可将写入性能提升3-5倍,同时保证数据一致性。

1. 事务粒度控制:避免“一操作一事务”

默认情况下,数据库处于“自动提交”模式(每执行一条SQL自动提交事务),这在多线程写入时效率极低。优化方案是增大事务粒度:将多个相关操作合并为一个事务,减少提交次数。

反例(低效):单条插入即提交

// 低效:1000条记录,1000次事务提交
for (int i = 0; i < 1000; ++i) {QSqlQuery query(db);query.prepare("INSERT INTO logs (content) VALUES (:content)");query.bindValue(":content", QString("log %1").arg(i));query.exec(); // 自动提交,每次执行触发磁盘IO
}

优化方案:批量插入+单次事务

// 高效:1000条记录,1次事务提交
db.transaction(); // 手动开启事务
QSqlQuery query(db);
query.prepare("INSERT INTO logs (content) VALUES (:content)");for (int i = 0; i < 1000; ++i) {query.bindValue(":content", QString("log %1").arg(i));if (!query.exec()) {qWarning() << "Insert failed:" << query.lastError().text();db.rollback(); // 失败回滚return false;}
}if (!db.commit()) { // 批量提交qWarning() << "Commit failed:" << db.lastError().text();db.rollback();return false;
}
return true;

性能对比:在MySQL中插入10000条记录,自动提交模式耗时约8-10秒,批量事务模式仅需1-2秒,效率提升80%以上。

2. 事务隔离级别选择:平衡一致性与性能

数据库事务隔离级别决定了并发事务之间的可见性,级别越高一致性越好,但性能越低。Qt中可通过QSqlDatabase::setTransactionIsolationLevel()设置隔离级别:

隔离级别说明适用场景
ReadUncommitted(读未提交)允许读取未提交的事务数据,可能脏读、不可重复读、幻读对一致性要求低,追求极致性能(如日志采集)
ReadCommitted(读已提交)只能读取已提交的数据,避免脏读,可能不可重复读、幻读大多数业务场景(如用户信息查询)
RepeatableRead(可重复读)保证同一事务中多次读取结果一致,避免脏读、不可重复读,可能幻读统计分析、报表生成
Serializable(串行化)事务完全串行执行,避免所有并发问题金融交易等强一致性场景

设置示例

// 设置事务隔离级别为“读已提交”(MySQL默认级别)
db.setTransactionIsolationLevel(QSql::ReadCommitted);

建议:多数业务场景优先选择ReadCommitted,平衡一致性与性能;仅在强一致性需求(如支付)时使用Serializable

3. 乐观锁与悲观锁:解决并发更新冲突

多线程同时更新同一条记录时,需通过锁机制避免丢失更新,Qt中常用两种锁策略:

(1)悲观锁:抢占式锁定

悲观锁假设冲突一定会发生,通过数据库的行锁/表锁机制,在更新前锁定记录,阻止其他线程修改:

// 悲观锁实现:更新用户余额(MySQL示例)
db.transaction();
QSqlQuery query(db);// 锁定id=100的记录(FOR UPDATE会加行锁)
query.prepare("SELECT balance FROM users WHERE id = :id FOR UPDATE");
query.bindValue(":id", 100);
if (!query.exec() || !query.next()) {db.rollback();return false;
}int currentBalance = query.value(0).toInt();
int newBalance = currentBalance + 100; // 增加100元// 更新余额
query.prepare("UPDATE users SET balance = :balance WHERE id = :id");
query.bindValue(":balance", newBalance);
query.bindValue(":id", 100);
if (!query.exec()) {db.rollback();return false;
}db.commit();

注意:悲观锁会增加锁竞争,长时间持有锁会导致其他线程阻塞,建议锁的范围越小越好(行锁优先于表锁),且事务执行时间尽可能短。

(2)乐观锁:无锁并发控制

乐观锁假设冲突很少发生,通过版本号机制实现无锁并发,适用于读多写少场景:

  1. 表中新增version字段(整数,初始值0);
  2. 更新时检查版本号,仅当版本号匹配时才更新,并自增版本号;
  3. 若版本号不匹配,说明记录已被修改,需重试。

实现示例

// 乐观锁实现:更新用户余额
int retryCount = 3; // 最多重试3次
while (retryCount-- > 0) {db.transaction();QSqlQuery query(db);// 查询当前余额和版本号query.prepare("SELECT balance, version FROM users WHERE id = :id");query.bindValue(":id", 100);if (!query.exec() || !query.next()) {db.rollback();return false;}int currentBalance = query.value(0).toInt();int currentVersion = query.value(1).toInt();int newBalance = currentBalance + 100;int newVersion = currentVersion + 1;// 仅当版本号匹配时更新(避免覆盖其他线程的修改)query.prepare("UPDATE users SET balance = :balance, version = :version ""WHERE id = :id AND version = :oldVersion");query.bindValue(":balance", newBalance);query.bindValue(":version", newVersion);query.bindValue(":id", 100);query.bindValue(":oldVersion", currentVersion);if (!query.exec()) {db.rollback();return false;}// 检查是否更新成功(影响行数为1则成功)if (query.numRowsAffected() == 1) {db.commit();return true;} else {// 更新失败(版本号不匹配),重试db.rollback();qWarning() << "Update conflict, retry..." << retryCount;}
}// 多次重试失败
return false;

对比:悲观锁实现简单但并发效率低,适合写多读少场景;乐观锁无锁竞争,并发效率高,但需处理重试逻辑,适合读多写少场景(如商品库存更新)。

四、性能优化实战:从卡顿到流畅

结合连接池、事务优化、索引设计等技术,我们通过一个实战案例展示多线程数据库操作的完整优化流程。

1. 场景描述

某日志分析系统需多线程采集设备日志,每秒钟生成约1000条日志记录,写入MySQL数据库。原实现采用单线程+自动提交事务,出现以下问题:

  • 写入延迟高达5-10秒,日志堆积严重;
  • 主线程频繁卡顿,UI无响应;
  • 高峰期出现“too many connections”错误。

2. 优化方案实施

(1)引入连接池

配置连接池参数:最小连接数=5,最大连接数=20,超时时间=5000ms,避免频繁创建连接。

(2)多线程写入+批量事务
  • 创建4个日志处理线程(与CPU核心数匹配);
  • 每个线程累积100条日志后批量提交事务,减少IO次数。
(3)索引优化

为日志表的device_idcreate_time字段创建联合索引,加速后续查询:

CREATE INDEX idx_log_device_time ON logs(device_id, create_time);
(4)异步化处理

使用QtConcurrent::run()在后台线程执行写入操作,主线程通过QFutureWatcher监控进度,避免UI阻塞。

3. 优化后代码实现

// 日志写入任务
bool writeLogsBatch(const QList<LogRecord>& logs) {// 从连接池获取连接QSqlDatabase db = DatabasePool::instance().getConnection();if (!db.isOpen()) return false;// 开启事务db.transaction();QSqlQuery query(db);query.prepare("INSERT INTO logs (device_id, content, create_time) ""VALUES (:device_id, :content, :create_time)");// 批量绑定参数for (const LogRecord& log : logs) {query.bindValue(":device_id", log.deviceId);query.bindValue(":content", log.content);query.bindValue(":create_time", log.createTime);if (!query.exec()) {qWarning() << "Insert log failed:" << query.lastError().text();db.rollback();DatabasePool::instance().releaseConnection(db);return false;}}// 提交事务bool success = db.commit();if (!success) {db.rollback();qWarning() << "Commit logs failed:" << db.lastError().text();}// 归还连接DatabasePool::instance().releaseConnection(db);return success;
}// 多线程批量写入日志
void startLogWriter() {QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>();connect(watcher, &QFutureWatcher<bool>::finished,watcher, &QFutureWatcher<bool>::deleteLater);// 启动4个后台线程处理日志for (int i = 0; i < 4; ++i) {// 从日志队列获取100条日志QList<LogRecord> batchLogs = logQueue.dequeueBatch(100);if (batchLogs.isEmpty()) break;// 异步执行批量写入QFuture<bool> future = QtConcurrent::run(writeLogsBatch, batchLogs);watcher->setFuture(future);}
}

4. 优化效果对比

指标优化前优化后提升幅度
写入延迟5-10秒<500ms10倍以上
每秒写入量约200条约1500条7.5倍
UI响应性频繁卡顿流畅无卡顿-
连接错误频繁出现“too many connections”无连接错误-

五、最佳实践与避坑指南

多线程数据库操作优化需兼顾性能与稳定性,以下是经过实战验证的最佳实践:

1. 连接池使用禁忌

  • 禁止跨线程归还连接:连接必须在获取它的线程中归还,避免线程安全问题;
  • 避免长时间占用连接:连接是稀缺资源,耗时操作(如大文件导入)应拆分步骤,及时归还连接;
  • 不手动关闭连接:连接的关闭由连接池统一管理,不要调用QSqlDatabase::close(),只需调用releaseConnection()

2. 事务使用原则

  • 事务尽可能小:事务中只包含必要的SQL操作,避免在事务中执行耗时任务(如网络请求);
  • 及时提交或回滚:事务开启后应尽快完成提交或回滚,减少锁持有时间;
  • 异常必回滚:在try-catch或错误处理中,确保事务异常时能正确回滚,避免长期锁表。

3. 线程安全编码规范

  • 线程数据隔离:每个线程使用独立的QSqlQuery对象,不共享查询对象;
  • 避免UI操作:数据库操作线程中禁止直接调用UI组件接口,通过信号槽将结果发送到主线程更新UI;
  • 参数绑定优先:使用QSqlQuery::bindValue()传递参数,避免字符串拼接SQL(防止SQL注入和编码问题)。

4. 数据库选型注意事项

  • MySQL:支持完善的事务和锁机制,适合高并发写入,需注意配置max_connections参数;
  • SQLite:轻量级嵌入式数据库,多线程写入需开启QSQLITE_THREADSAFE=1,但不支持真正的并行写入(写操作串行执行),适合单机低并发场景;
  • PostgreSQL:事务隔离级别更严格,支持复杂查询,适合数据量大、查询复杂的场景。

六、总结

多线程数据库操作优化是Qt开发中的核心难点,其本质是资源复用(连接池)、并发控制(事务与锁)、性能平衡(批量与异步)的综合应用。本文通过连接池设计、事务优化、锁机制详解和实战案例,展示了如何将多线程数据库操作从卡顿变为流畅,核心要点包括:

  • 连接池是线程安全的基础,通过复用连接降低创建开销;
  • 批量事务和合理的隔离级别是提升写入性能的关键;
  • 乐观锁与悲观锁分别适用于不同并发场景,解决更新冲突;
  • 多线程+异步化处理可避免UI阻塞,提升用户体验。

如果你在多线程数据库优化中遇到特殊场景或问题,欢迎在评论区留言讨论!

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

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

相关文章

Qt 状态机框架:复杂交互逻辑的处理

Qt状态机框架&#xff08;Qt State Machine Framework&#xff09;是一个强大的工具&#xff0c;用于简化复杂的交互逻辑和状态管理。它基于UML状态图概念&#xff0c;提供了声明式的方式来定义对象行为&#xff0c;特别适合处理具有多种状态和转换的场景&#xff08;如GUI交互…

【docker】DM8达梦数据库的docker-compose以及一些启动踩坑

摘要&#xff1a;本文介绍了通过docker-compose配置启动达梦数据库(DM8)的方法。配置包括容器镜像、端口映射、数据卷挂载以及关键环境变量设置&#xff08;如字符集、大小写敏感等&#xff09;。也说明了启动过程可能遇到的一些问题。通过docker-compose启动达梦数据库可以按照…

服务器中的防火墙设置需要打开吗

服务器中的防火墙属于是一种保护计算机网络不会受到未经授权的网络设备所访问的技术手段&#xff0c;防火墙还有着将内部网络和外部网络进行隔离的功能&#xff0c;在网络之间创建安全屏障&#xff0c;以此来保护网络中数据的安全。防火墙是一种网络安全系统&#xff0c;可以帮…

Java项目:基于SSM框架实现的社区团购管理系统【ssm+B/S架构+源码+数据库+毕业论文+答辩PPT+远程部署】

摘 要 使用旧方法对社区团购信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在社区团购信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。 这次开发的社区团购系统有…

介绍一下static关键字

在Java中&#xff0c;被static修饰的成员称为静态成员&#xff0c;static关键字可以用来修饰方法或者成员变量&#xff0c;且被static修饰的方法或者成员变量属于类方法或者类属性&#xff0c;也就是说被static修饰的方法或者成员变量不是单独存储在某一个对象的空间&#xff0…

【Java学习|黑马笔记|Day23】网络编程、反射、动态代理

【DAY23】 文章目录【DAY23】一.网络编程1&#xff09;三要素1.1&#xff09;IPInetAddress类的使用1.2&#xff09;端口号1.3&#xff09;协议2.1&#xff09;UDP协议发送数据2.2&#xff09;UDP协议接收数据2.3&#xff09;UDP的三种通信方式3.1&#xff09;TCP协议的发送和接…

【Zephyr】Window下的Zephyr编译和使用

工具下载 参考文档&#xff08;Getting Started Guide — Zephyr Project Documentation&#xff09;中介绍&#xff0c;可以直接通过winget下载&#xff1a; winget download Kitware.CMake Ninja-build.Ninja oss-winget.gperf python Git.Git oss-winget.dtc wget 7zip.7z…

图论(BFS)构造邻接表(运用队列实现搜索)

码蹄集OJ-夏日漫步 #include<bits/stdc.h> using namespace std; int n; int a[200010],dis[200010],qaq[1000010]; vector<int>son[200010]; int que[200010]; int main( ) {memset(qaq,-1,sizeof(qaq));memset(dis,-1,sizeof(dis));cin>>n;for(int i1;i…

vue怎么实现导入excel表功能

<el-uploadref"upload":action"aaa":on-change"importProject"name"excelFile"multiple:auto-upload"false":show-file-list"false"><el-button type"warning">导入</el-button><…

Linux DNS解析3 -- DNS解析代理配置使用

当网关设备配置了 /etc/hosts 文件时&#xff0c;确实可以为终端设备提供自定义DNS解析功能&#xff0c;但具体效果取决于网关的DNS代理服务配置。下面详细解释其工作原理和限制&#xff1a; 一、/etc/hosts 文件的作用 /etc/hosts 是本地静态域名解析文件&#xff0c;格式为&a…

历史版本的vscode下载地址

我有点厌恶vscode越来越臃肿的体积&#xff0c;也不需要层出不穷的新功能&#xff0c;于是网上找寻历史版本。 首先是这个页面&#xff1a;https://code.visualstudio.com/updates &#xff0c;但最多只显示两年&#xff0c;更早的就要手工修改地址栏&#xff0c;我试了最早的…

如何规范化项目执行

要实现项目执行的规范化&#xff0c;应做到以下几点&#xff1a;制定详细的项目执行计划、明确项目团队角色职责、建立高效沟通与协调机制、实施全面的质量与风险管理、采用合适的项目管理工具。其中&#xff0c;尤其重要的是明确项目团队角色职责&#xff0c;通过构建清晰的责…

【Rust异步】async和await异步编程实战:高并发任务处理全解析

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

在Linux上使用DuckCP实现从csv文件汇总数据到SQLite数据库的表

从pypi网站Duckcp页面下载duckcp-0.1.1-py3-none-any.whl 一开始用的Python 3.11.2环境。 继续沿用上文打补丁的方法&#xff0c;得到一个支持python3.11.1的安装包。 因为缺少zip压缩工具&#xff0c;使用python程序来完成对修改后文件的重新压缩。 import os import zipfile…

基于深度学习的图像分类:使用EfficientNet实现高效分类

前言 图像分类是计算机视觉领域中的一个基础任务&#xff0c;其目标是将输入的图像分配到预定义的类别中。近年来&#xff0c;深度学习技术&#xff0c;尤其是卷积神经网络&#xff08;CNN&#xff09;&#xff0c;在图像分类任务中取得了显著的进展。EfficientNet是一种新型的…

Java基础-综合案例

1、设计一个可以执行基本数学运算&#xff08;加减乘除&#xff09;的计算器程序功能描述&#xff1a;用户输入两个数字、一个运算符&#xff08;、-、*、/&#xff09;。根据所选运算符执行相应的数学运算&#xff0c;显示运算结果。import java.util.Scanner;public class Te…

四、计算机组成原理——第3章:存储系统

目录 3.1存储器概述 3.1.1存储器的分类 1.按在计算机中的作用(层次)分类 2.按存储介质分类 3.按存取方式分类 4.按信息的可保存性分类 3.1.2存储器的性能指标 3.2主存储器 3.2.1SRAM芯片和DRAM芯片 1.SRAM的工作原理 2.DRAM的工作原理 3.SRAM和DRAM的比较 4.存储器芯片的内部结…

3D Semantic Occupancy Prediction

3D 语义占用预测&#xff08;3D Semantic Occupancy Prediction&#xff09;旨在将真实世界环境划分为规则的三维体素&#xff08;voxel&#xff09;网格&#xff0c;并对每个体素同时预测&#xff1a; 占用状态&#xff08;Occupancy&#xff09;&#xff1a;该体素是否被物体…

在Word和WPS文字中添加的拼音放到文字右边

在Word和WPS文字中&#xff0c;可以方便地为中文汉字添加拼音。默认的是拼音在汉字的上方&#xff0c;而且不方便直接编辑。可以简单操作后把拼音放在汉字的右边&#xff0c;并且可以方便地编辑。一、Word&#xff1a;先为汉字添加拼音&#xff0c;然后选择性粘贴为纯文本即可1…

Torchv Unstrustured 文档解析库

一个强大且开发者友好的文档解析库&#xff0c;专为RAG&#xff08;检索增强生成&#xff09;应用优化。基于Apache Tika、Apache POI和PDFBox等业界标准Java库构建&#xff0c;TorchV Unstructured提供了增强的解析能力&#xff0c;具备智能表格结构识别和内容提取功能。 &am…