鸿蒙应用开发—数据持久化之SQLite
文章目录
- SQLite简介
- 创建数据库
- 添加数据
- 查询数据
- 更新数据
- 删除数据
- 升级数据库
- 使用事务
- 参考
SQLite简介
SQLite是一个轻量级关系数据库,占用资源很少,只有几百KB的大小,无需服务器支撑,是一个零配置、事务性的SQL数据库引擎。
相对于首选项Preferences,SQLite更适合存储大量复杂的关系型数据,首选项则适合于保存一些简单的键值对数据;比如IM应用的聊天会话信息的本地存储,用首选项存储是明显是不合适,因为其数据量是极大的,数据关系结构也很复杂,在这方面首选项是明显是不合适的,SQLite则可以很轻松存储操作这些数据。那么SQLite在鸿蒙中是如何使用的,下面会一一讲解。
创建数据库
在@kit.ArkData方舟数据管理模块中,为开发者提供数据存储、数据管理和数据同步能力,而SQLite的服务则在这个模块中,专门提供了一个relationalStore来辅助创建数据库。我们以一个用户信息为例,创建一个名称是user.db的数据库,首先创建DBUtils类来管理数据库的行为操作。
import { relationalStore } from '@kit.ArkData'
import AppUtils from './AppUtils'
export default class DBUtils {private static rdbStore?: relationalStore.RdbStoreprivate constructor() {}static init(callback: Function = (state: boolean, msg?: string) => {}) {const context = AppUtils.getContext()// 数据库配置const STORE_CONFIG: relationalStore.StoreConfig = {name: 'user.db', // 数据库名称securityLevel: relationalStore.SecurityLevel.S1, // 数据库安全级别encrypt: false, // 可选参数,指定数据库是否加密,默认不加密} as relationalStore.StoreConfig// 数据库文件的默认存储路径,可通过 customDir修改路径console.log(`${TAG} db dir: `, context.databaseDir)// 1、获取RdbStore实例,用于操作数据库relationalStore.getRdbStore(context, STORE_CONFIG).then((r) => {DBUtils.rdbStore = rconsole.log(TAG, 'db create success')callback(true)}).catch((err: Error) => {console.error(`${TAG} db create error: `, err.message)callback(false, err.message)})}
}
上面代码是配置和初始化数据库相关的配置,主要步骤:
- 创建一个STORE_CONFIG的对象,包含了数据库配置的信息,有数据库名称、安全级别和加密状态。name是数据库文件名称,值是user.db,安全级别是relationalStore.SecurityLevel.S1,表示数据库的安全级别为低级别,当数据泄露时会产生较低影响,是不加密的状态。
- 通过relationalStore获取RdbStore实例,这是操作数据库的接口,通过调用relationalStore.getRdbStore 函数并传入上下文和配置对象来实现。在创建数据库成功后,会执行then代码块,接着将RdbStore实例赋值给DBUtils.rdbStore,这样使得这个实例可以被 DBUtils 类的其他方法使用。
- 最后在外部触发调用DBUitls的init()方法就完成了数据库的创建。当在控制台有打印
db create success
日志则表示数据库文件创建成功了。
数据库的安全级别除了S1,还有S2、S3、S4,如下:
属性 | 值 | 概述 |
---|---|---|
S1 | 1 | 表示数据库的安全级别为低级别,当数据泄露时会产生较低影响。例如,包含壁纸等系统数据的数据库。 |
S2 | 2 | 表示数据库的安全级别为中级别,当数据泄露时会产生较大影响。例如,包含录音、视频等用户生成数据或通话记录等信息的数据库。 |
S3 | 3 | 表示数据库的安全级别为高级别,当数据泄露时会产生重大影响。例如,包含用户运动、健康、位置等信息的数据库。 |
S4 | 4 | 表示数据库的安全级别为关键级别,当数据泄露时会产生严重影响。例如,包含认证凭据、财务数据等信息的数据库。 |
上面AppUtils是一个简单的工具类,用于存储全局context实例,代码如下所示:
import { common } from '@kit.AbilityKit'export default class AppUtils {private constructor() {}private static context: common.UIAbilityContextstatic init(context: common.UIAbilityContext) {AppUtils.context = context}static getContext(): common.UIAbilityContext {if (!AppUtils.context) {throw new Error('在EntryAbility类的onCreate()方法中调用init()方法完成初始化')}return AppUtils.context}
}
通常会在EntryAbility的onCreate()方法中初始化,代码如下所示:
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {AppUtils.init(this.context)
}
如果我们需要查看数据库文件在设备中的位置,可以通过context上下文获取数据库文件目录,代码如下所示:
// 数据库文件的默认存储路径,可通过 customDir修改路径
const context = AppUtils.getContext()
console.log(`${TAG} db dir: `, context.databaseDir)
应用创建的数据库与其上下文(Context)有关,即使使用同样的数据库名称,但不同的应用上下文,会产生多个数据库,例如每个UIAbility都有各自的上下文。
在控制台我们可以看到打印数据库文件的默认存储路径,使用 console.log 来展示数据库目录的路径。如下:
DBUtils db dir: /data/storage/el2/database/entry
从日志可知数据库文件默认位置是/data/storage/el2/database/entry,但在DevEco Studio 5.0.3.400 API12 上,发现没有data目录下没有storage目录,反而在app目录下可以找到对应的数据库文件,完整的文件路径是/data/app/el2/database/entry。
我们可以在DevEco Studio编译器中的Device File Browser工具栏中可以查看到数据库文件。如下图所示:
如果希望移动数据库文件到其它地方使用查看,则需要同时移动这些以-wal和-shm结尾的临时文件。
在创建数据库文件后,此时就要创建数据表来描述数据,这里以创建一个USER表为例,user表包括了id,name、age、sex 、height、weight属性,然后通过RdbStore实例的executeSql()方法执行创建数据表的SQL语句,代码如下所示:
static createTable() {// 创建USER表的SQL语句const CREATE_TABLE_USER ='CREATE TABLE IF NOT EXISTS USER(ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL, sex INTEGER NOT NULL, height REAL, weight REAL)'// 2、创建表DBUtils.rdbStore?.executeSql(CREATE_TABLE_USER).then(() => {console.log(TAG, 'create table done')}).catch((err: BusinessError) => {console.error(TAG, err.message)})}
DBUtils.rdbStore对象实例还记得是如何获取的吗,在创建数据库文件时通过relationalStore.getRdbStore()来获取的,这里把RdbStore实例赋值给了DBUtils类的rdbStore属性。如果数据表创建异常则会执行catch代码块,创建成功则执行then代码块。
在完成创建数据USER表后,如何可视化查看其内容呢,除了将user.db数据库文件导出通过SQLite Studio查看外,我们还可以借助于IDE的Database Navigator插件,目前在DevEco Studio的Setting -> Plugins 是无法搜索到该插件,但在其他IDE(Android Studio、intellij idea)是正常能搜索安装的,既然在线无法安装,我们可以去JetBrains 插件应用市场手动下载后,离线安装。
打开网址https://plugins.jetbrains.com/idea 搜索下载
下载后是一个压缩包无需解压,然后在DevEco Studio -> Settings -> Pulgins 安装离线包,选择 Install plugin from Disk选项,选择下载的压缩包进行安装,完成安装后需要重启DevEco Studio才会生效,如下图所示:
安装成功后会在DevEco Studio的左侧边栏,现在应该多出了一个DB Browser工具,然后接着回到Device File Browser工具栏打开数据库user.db、user.db-shm和user.db-wal三个文件,右击→Save As,将它从移动设备导出到你的计算机的任意位置。在DB Browser中选择SQLite,如下图所示:
最后在弹出窗中选择刚才导出的user.db数据库文件,然后点击OK完成配置。
完成配置后,在IDE左侧的DB Browser工具栏可以USER表的信息,如下图所示:
添加数据
对数据库的操作无非就是CRUD,C代表添加(create),R代表查询(retrieve),U代表更新(update),D代表删除(delete);每个操作都有对应的SQL语句,其中添加数据是insert,查询数据是select,更新数据是update,删除数据是delete。
使用RdbStore的insert()方法添加参数,第一个参数是表名,第二参数是要插入到表中的数据,是ValuesBucket对象,需要将表中每一列设置对应的值,是一个异步方法,插入成功则返回的数据在表中的行数,插入失败则返回-1,代码所下所示:
static insert() {let item: relationalStore.ValuesBucket = {name: 'lili',age: 18,sex: 0,height: 160,weight: 45,};let item2: relationalStore.ValuesBucket = {name: 'hzw',age: 28,sex: 1,height: 180,weight: 60,};// 插入数据DBUtils.rdbStore?.insert(DBUtils.tableName, item).then((r) => {console.log(TAG, 'insert success: ', r);DBUtils.rdbStore?.commit()}).catch((e: Error) => {console.log(TAG, 'insert err: ', e.message);});DBUtils.rdbStore?.insert(DBUtils.tableName, item2).then((r) => {console.log(TAG, 'insert success: ', r);DBUtils.rdbStore?.commit()}).catch((e: Error) => {console.log(TAG, 'insert err: ', e.message);});}
在上面代码添加了两条数据,首先创建ValuesBucket对象,对表中的每一列赋值,你会发现我们并没有给id赋值,这是因为在创建USER表时我们将id设置了自增,然后通过DBUtils.rdbStore对象的insert方法,用来将数据item插入到名为DBUtils.tableName的表中,DBUtils.rdbStore对象是前面已提及到了,是rdbStore的实例,而DBUtils.tableName的值是USER。
接下来测试添加数据的insert()方法,给布局添加插入数据的按钮,如下图所示:
@Entry
@Component
struct Index {build() {Column() {Scroll() {Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceEvenly }) {Button('创建数据库').btnStyle(OperateType.CREATE_DB)Button('创建User表').btnStyle(OperateType.CREATE_TABLE)Button('插入数据').btnStyle(OperateType.INSERT)}.width('100%').height('30%')}}.height('100%').width('100%')}
}@Extend(Button)
function btnStyle(type: number, call?: (r: string) => void) {.margin({ top: '12vp' }).onClick(() => {switch (type) {case OperateType.CREATE_DB:DBUtils.init()breakcase OperateType.CREATE_TABLE:DBUtils.createTable()breakcase OperateType.INSERT:// 添加数据DBUtils.insert()break}})
}class OperateType {static readonly CREATE_DB: number = 0static readonly CREATE_TABLE: number = 1static readonly INSERT: number = 2static readonly DELETE: number = 3static readonly UPDATE: number = 4static readonly QUERY: number = 5
}
上面底代码运行程序后,如下图所示:
点击“插入数据”按钮,会调用 DBUtils.insert()方法,将两条数据便会添加到USER数据表中,在insert()异步方法中的then代码块中会返回数据在表中行数,则表示数据添加成功。另外我们也通过DB Browser查看,将user.db等三个数据库文件重新导出到指定目录,由于之前已连接数据库,重新导出覆盖后,点击数据USER表自动刷新重载。如下图所示:
从上图可知,我们已成功添加了两条数据到USER表中。
查询数据
在添加数据案例中,我们是通过DB Browser工具查看数据的,在实际开发中通常会通过SQL语句来查询数据,在鸿蒙中RdbStore实例提供相关query()查询数据的方法。代码如下所示:
static query() {// 创建RdbPredicates实例let predicates = new relationalStore.RdbPredicates(DBUtils.tableName);//equalTo 方法的第一个参数是列名,第二个参数是列值// 查询name=lili的数据predicates.equalTo("name", "lili")DBUtils.rdbStore?.query(predicates).then((r) => {let items: Array<relationalStore.ValuesBucket> = []// 遍历查询结果while (r.goToNextRow()) {// 获取当前行的数据const row = r.getRow()items.push(row)}console.log(TAG, 'query success: ', JSON.stringify(items, null, 2));// 关闭查询结果集r.close()}).catch((e: Error) => {console.log(TAG, 'query err: ', e.message);})// 执行SQL语句 查询USER表的所有数据DBUtils.rdbStore?.querySql(`SELECT * FROM ${DBUtils.tableName}`).then((r) => {let items: Array<relationalStore.ValuesBucket> = []// 遍历查询结果while (r.goToNextRow()) {// 获取当前行的数据const row = r.getRow()items.push(row)}console.log(TAG, 'querySql success: ', JSON.stringify(items, null, 2));// 关闭查询结果集r.close()}).catch((e: Error) => {console.log(TAG, 'querySql err: ', e.message);})}
上面代码中query()和querySql()两个不同方法来查询数据,query()方法需要接收RdbPredicates实例,在RdbPredicates的构造函数设置表名USER,通过predicates对象来设置查询的条件,equalTo()方法的第一个参数是列名,第二个参数是列值,则查询name是lili值的数据。在then代码块中通过ResultSet遍历查询每个行的数据,当查询完毕后,ResultSet会调用close()方法释放所有的资源。querySql()方法的参数则是SQL语句,上面是查询所有的数据,then代码块的逻辑与query()方法的then是类似的。
下面我们在布局添加一个“查询数据”的按钮,点击按钮时通过DBUtils调用静态query()方法,代码如下所示:
Button('查询数据').btnStyle(OperateType.QUERY)@Extend(Button)
function btnStyle(type: number, call?: (r: string) => void) {.margin({ top: '12vp' }).onClick(() => {switch (type) {case OperateType.CREATE_DB:DBUtils.init()breakcase OperateType.CREATE_TABLE:DBUtils.createTable()breakcase OperateType.INSERT:DBUtils.insert()breakcase OperateType.QUERY:// 查询数据DBUtils.query()break}})
}
运行上面的程序后,如下图所示:
点击“查询数据”按钮后,便会执行查询,在控制台会输出查询结果,如下所示:
查询是一个相对复杂的操作,equalTo()方法只是RdbPredicates其中一个,系统还提供的其他查询条件的API,如下所示:
上面的例子只是一个简单的案例,在实际开发中要靠自己去慢慢摸索。
更新数据
在学习完添加和查询数据后,更新和删除的数据变更就可以通过查询方式来观察变化,就不需要将数据库文件导出这样繁琐操作了。RdbStore提供了update()方法来更新数据。代码如下所示:
static update() {// 设置更新的列值,这里设置了age列的值为25let valueBucket: relationalStore.ValuesBucket = {age: 25}// 创建RdbPredicates实例let predicates = new relationalStore.RdbPredicates(DBUtils.tableName);// 设置查询的条件,name列的值为lili的数据predicates.equalTo("name", "lili")// 执行更新操作DBUtils.rdbStore?.update(valueBucket, predicates).then((r: number) => {DBUtils.rdbStore?.commit() // 打印更新的行数console.log(TAG, 'update success: ', r)}).catch((e: Error) => {console.log(TAG, 'update err: ', e.message)})}
上面代码是将lili名称的age值由原来的18改成25,接着我们在布局添加一个“更新数据”的按钮,点击按钮时通过DBUtils调用静态update()方法,代码如下所示:
Button('更新数据')
.btnStyle(OperateType.UPDATE)@Extend(Button)
function btnStyle(type: number, call?: (r: string) => void) {.margin({ top: '12vp' }).onClick(() => {switch (type) {case OperateType.UPDATE:DBUtils.update()break}})
}
运行上面的程序后,如下图所示:
点击“更新数据”按钮后,便会执行更新对应的数据,然后点击查询更新后的数据,在控制台会输出更新后的结果,如下所示:
从日志可知,在执行update()更新操作后,我们更新值是生效了,age值由原来的18变成了25.
删除数据
在知道了查询数据后,删除数据则相对很简单了,RdbStore提供了delete()方法来删除数据,只接收一个参数RdbPredicates,前面已经使用过多次了,已经熟能生巧了就不多讲了,直接上代码了。
static delete() {// 创建RdbPredicates实例,用于设置查询条件 ,指定查询的表名let predicates = new relationalStore.RdbPredicates(DBUtils.tableName);// 设置删除的条件,name列的值为hzw的数据predicates.equalTo("name", "hzw")DBUtils.rdbStore?.delete(predicates).then((r: number) => {DBUtils.rdbStore?.commit() // 打印删除的行数console.log(TAG, 'delete success: ', r)}).catch((e: Error) => {console.log(TAG, 'delete err: ', e.message)})}
上面代码是删除name值为hzw的数据,接着我们在布局添加一个“删除数据”的按钮,点击按钮时通过DBUtils调用静态delete()方法,代码如下所示:
Button('删除数据').btnStyle(OperateType.DELETE)
@Extend(Button)
function btnStyle(type: number, call?: (r: string) => void) {.margin({ top: '12vp' }).onClick(() => {switch (type) {case OperateType.DELETE:DBUtils.delete()break}})
}
运行上面的程序后,如下图所示:
点击“删除数据”按钮后,便会删除对应的数据,然后点击查询删除后的数据,在控制台会输出删除后的结果,如下所示:
从日志可知,在执行delete()方法执行删除操作后,name为hzw的这条数据记录已经被删除了,不存在USER表中了。
升级数据库
什么情况下需要升级升级库呢?比如我们的应用1.0版本已成功上线了,产品在规划2.0版本时,用户信息新增一个staffId字段,接着在3.0版本时又删除一个weight字段,此时数据库就要升级,确保在应用版本升级的过程中本地数据库的数据不会丢失。
在初始化数据库配置的getRdbStore()方法的then代码块中进行数据库版本升级。当数据库创建时,数据库默认版本是0,此时通常会创建需要的表,同时将数据库版本设为1,相当于从0升级到1,代码如下:
// 数据库版本号是0时,创建数据表语句的SQL语句
const CREATE_TABLE_USER ='CREATE TABLE IF NOT EXISTS USER(ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL, sex INTEGER NOT NULL, height REAL, weight REAL)'relationalStore.getRdbStore(context, STORE_CONFIG).then((store: relationalStore.RdbStore) => {DBUtils.rdbStore = storeconsole.log(TAG, 'db ver: ',store.version)// 升级数据库// 当数据库创建时,数据库默认版本为0if (store.version == 0) {store.executeSql(CREATE_TABLE_USER)// 将版本设置为1 相当于版本号从0升级到1store.version = 1}}).catch((err: Error) => {console.error(`${TAG} db create error: `, err.message)
})
此时随着应用版本迭代升级,USER表新增了一个staffId字段,创建USER表的SQL语句则需要增加一个staffId字段,代码如下所示:
// 新增staffId字段,创建数据表语句的SQL语句
const CREATE_TABLE_USER_1 ='CREATE TABLE IF NOT EXISTS USER(ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL, sex INTEGER NOT NULL, height REAL, weight REAL, staffId INTEGER)'relationalStore.getRdbStore(context, STORE_CONFIG).then((store: relationalStore.RdbStore) => {DBUtils.rdbStore = storeconsole.log(TAG, 'db ver: ',store.version)// 升级数据库const oldVersion = store.version// 当数据库创建时,数据库默认版本为0if (store.version == 0) {store.executeSql(CREATE_TABLE_USER_1)// 设置数据库最高版本2 ,这里始终设置成最高版本号store.version = 2}// 如果是数据库版本从1 升级到 2,则需要新增staffId字段 if (store.version == 1) {store.executeSql('alter table USER add column staffId integer')// 数据库版本升级为2store.version = 2 }}).catch((err: Error) => {console.error(`${TAG} db create error: `, err.message)
})
如果是一个新用户初次安装应用,数据库默认版本号是0,会调用executeSql()方法按照最新的SQL语句(CREATE_TABLE_USER_1
)创建USER表,同时将数据库版本号设置成最高版本2,这样就不会执行后面if升级逻辑,如果这个用户的数据库版本是1,则会通过executeSql()方法执行alter table USER add column staffId integer
SQL语句新增staffId字段,同时将数据库版本号升级为2。
当数据库由版本1升级成2时,我们去查询数据,会发现数据表中有staffId字段了,如下:
DBUtils query success: [{"ID": 1,"age": 18,"height": 160,"name": "lili","sex": 0,"staffId": null,"weight": null
}]
接着后面产品又说,USER表中不需要weight字段了,开发者此时从中表中去除,就需要修改创建USER表的语句,这样数据库又要升级,由版本2升级为3,代码如下:
// 去除weight字段,创建数据表语句的SQL语句
const CREATE_TABLE_USER_2 ='CREATE TABLE IF NOT EXISTS USER(ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL, sex INTEGER NOT NULL, height REAL, staffId INTEGER)'relationalStore.getRdbStore(context, STORE_CONFIG).then((store: relationalStore.RdbStore) => {DBUtils.rdbStore = storeconsole.log(TAG, 'db ver: ',store.version)// 升级数据库const oldVersion = store.version// 当数据库创建时,数据库默认版本为0if (store.version == 0) {store.executeSql(CREATE_TABLE_USER_2)// 这里始终设置为最高版本 3store.version = 3}// 如果是数据库版本从1 升级到 2,则需要新增staffId字段if (store.version == 1) {store.executeSql('alter table USER add column staffId integer')store.version = 2}// 如果是数据库版本从2 升级到 3,则需要去除weight字段if (store.version == 2) {store.executeSql('alter table USER drop column weight')store.version = 3}}).catch((err: Error) => {console.error(`${TAG} db create error: `, err.message)
})
这里的升级逻辑与升级到版本2的逻辑是类似的,就不多述了。当版本升级为3时我们去查询数据,weight字段则不存在了,如下所示:
DBUtils query success: [{"ID": 1,"age": 18,"height": 160,"name": "lili","sex": 0,"staffId": null,
}]
使用这种if方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据完全不会丢失。
使用事务
SQLite数据库是支持事务的,事务是指一系列操作,要么全部完成,那么全部不完成,是原子性操作。比如我们常用的转账功能,A账户向B账户转账,可以分为两个步骤,从A账户扣钱,然后再往B账户打入等量的金额,这两个动作是独立的操作,可能存在一个成功,一个失败,比如A账户扣钱成功了,B账户没有收到钱,出现这种情况是很危险的,如何确保两个独立操作要么全部失败,要么全部成功,当某个失败时,就回滚到初始状态,此时事务就派上用场了。
在鸿蒙中如何使用事务,rdbStore提供了beginTransaction() 和rollBack()方法来保证事务,确保操作时原子性。下面以一个简单案例为例,代码如下:
static async transaction(isError: boolean = true){// 开启事务DBUtils.rdbStore?.beginTransaction()try {// 删除hzw的数据let predicates = new relationalStore.RdbPredicates(DBUtils.tableName);predicates.equalTo("name", "hzw")let rowNum = await DBUtils.rdbStore?.delete(predicates)DBUtils.rdbStore?.commit()console.log(TAG, 'delete success: ', rowNum)if (isError) {// 制造一个异常,让事务失败throw new Error('error')}let xml: relationalStore.ValuesBucket = {name: 'xml',age: 28,sex: 0,height: 165,};const num = await DBUtils.rdbStore?.insert(DBUtils.tableName, xml)DBUtils.rdbStore?.commit()console.log(TAG, 'insert success: ', num)} catch (e) {// 回滚console.log(TAG, '回滚');DBUtils.rdbStore?.rollBack()}
}
上面代码的原子性逻辑是先删除hzw的数据,然后添加xml名称的数据。
首先在执行SQL前通过beginTransaction()方法开启事务,接着删除hzw的数据,此时已经执行删除的SQL语句,但当isError为true时,这人为制造一个异常中断整个流程,导致事务的失败,但添加数据的操作还未执行。不过由于在catch代码块中,调用了rollBack()方法回滚到开启事务处,因此hzw数据是删除不了的。
参考
- https://blog.csdn.net/K346K346/article/details/114085663
相关文章:
鸿蒙应用开发—数据持久化之SQLite
文章目录 SQLite简介创建数据库添加数据查询数据更新数据删除数据升级数据库使用事务参考 SQLite简介 SQLite是一个轻量级关系数据库,占用资源很少,只有几百KB的大小,无需服务器支撑,是一个零配置、事务性的SQL数据库引擎。 相对…...
Docker Compose 部署 steamcmd 安装奈斯服务端
由于打算在云端服务器部署奈斯启示录服务端跟朋友们一起玩, 所以在云端搭建服务器, 顺便写下本文章记录搭建的过程。 博主博客 https://blog.uso6.comhttps://blog.csdn.net/dxk539687357 要使用 Docker Compose 部署 steamcmd(Steam 命令行…...
K8s 1.27.1 实战系列(八)Service
一、Service介绍 1、Service 的作用与核心功能 Service 是 Kubernetes 中用于抽象一组 Pod 并提供稳定访问入口的资源。它解决了以下问题: Pod IP 不固定:Pod 可能因故障、扩缩容或更新导致 IP 变化,Service 通过 ClusterIP(虚拟 IP)提供固定访问地址。负载均衡:自动…...
Scala编程_实现Rational的基本操作
在Scala中实现一个简单的有理数(Rational)类,并对其进行加法、比较等基本操作. 有理数的定义 有理数是可以表示为两个整数的比值的数,通常形式为 n / d,其中 n 是分子,d 是分母。为了确保我们的有理数始终…...
Android15 Camera框架中的StatusTracker
StatusTracker介绍 StatusTracker是Android15 Camera框架中用来协调Camera3各组件之间状态转换的类。 StatusTracker线程名:std::string("C3Dev-") mId "-Status" Camera3 StatusTracker工作原理 StatusTracker实现批处理(状态…...
Manus 演示案例:谷歌公司运营模拟器游戏体验
一、项目背景与愿景 在科技行业蓬勃发展的当下,谷歌作为行业巨头,其成长历程充满了无数值得深入探究的决策智慧。这些决策不仅塑造了谷歌的辉煌,也为全球企业的发展提供了宝贵的借鉴。本项目旨在打造一款以谷歌公司发展为蓝本的运营模拟器游戏…...
【大模型基础_毛玉仁】2.1 大数据+大模型→新智能
【大模型基础_毛玉仁】2.1 大数据大模型→新智能 2.大语言模型架构2.1 大数据大模型→新智能2.1.1 大数据大模型→能力增强1)Kaplan-McCandlish 扩展法则2)Chinchilla 扩展法则 2.1.2 大数据大模型→能力扩展 2.大语言模型架构 大语言模型(L…...
20天 - TCP 和 UDP 有什么区别?说说 TCP 的三次握手?TCP 是用来解决什么问题?
TCP 和 UDP 有什么区别? TCP(传输控制协议)和 UDP(用户数据报协议)都是传输层的网络协议,它们的主要区别如下: 连接方式 TCP:面向连接的协议,类似于打电话,…...
【设计模式】掌握建造者模式:如何优雅地解决复杂对象创建难题?
概述 将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。 由于实现了构建和装配的解耦。…...
【网络安全工程】任务11:路由器配置与静态路由配置
目录 一、概念 二、路由器配置 三、配置静态路由CSDN 原创主页:不羁https://blog.csdn.net/2303_76492156?typeblog 一、概念 1、路由器的作用:通过路由表进行数据的转发。 2、交换机的作用:通过学习和识别 MAC 地址,依据 M…...
10 【HarmonyOS NEXT】 仿uv-ui组件开发之Avatar头像组件开发教程(一)
温馨提示:本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦! 目录 第一篇:Avatar 组件基础概念与设计1. 组件概述2. 接口设计2.1 形状类型定义2.2 尺寸类型定义2.3 组件属性接口 3. 设计原则4. 使用…...
蓝桥杯备赛-差分-重新排序
问题描述 给定一个数组 AA 和一些查询 Li,RiLi,Ri, 求数组中第 LiLi 至第 RiRi 个元素之和。 小蓝觉得这个问题很无聊, 于是他想重新排列一下数组, 使得最终每个查 询结果的和尽可能地大。小蓝想知道相比原数组, 所有查询结果的总和最多可 以增加多少? 输入格式 输…...
①Modbus TCP转Modbus RTU/ASCII网关同步采集无需编程高速轻松组网
Modbus TCP转Modbus RTU/ASCII网关同步采集无需编程高速轻松组网https://item.taobao.com/item.htm?ftt&id784749793551 MODBUS TCP 通信单元 MODBUS TCP 转 RS485 MS-A1-50X1 系列概述 MS-A1-50X1 系列概述 MS-A1-50X1系列作为MODBUS TCP通信的服务器进行动作。可通…...
2025年四川烟草工业计算机岗位备考详细内容
四川烟草工业计算机岗位备考详细内容(持续更新) 文章目录 四川烟草工业计算机岗位备考详细内容(持续更新)一、计算机基础(一)计算机发展与组成计算机发展历程计算机系统组成软件系统 (二&#x…...
Git 设置全局代理
Git 设置全局代理或项目代理 git config: 全局配置,设置git代理服务器 # 设置 HTTP 代理 git config --global http.proxy http://127.0.0.1:7897# 设置 HTTPS 代理 git config --global https.proxy http://127.0.0.1:7897# 设置所有协议的代理&…...
【Java开发指南 | 第三十四篇】IDEA没有Java Enterprise——解决方法
读者可订阅专栏:Java开发指南 |【CSDN秋说】 文章目录 1、新建Java项目2、单击项目名,并连续按两次shift键3、在搜索栏搜索"添加框架支持"4、勾选Web应用程序5、最终界面6、添加Tomcat 1、新建Java项目 2、单击项目名,并连续按两次…...
ROS实践(二)构建Gazebo机器人模型文件urdf
目录 一、基础语法 1. urdf文件组成 2. robot根标签 3. link 和 joint标签 4. sensor标签 二、 实验:使用launch文件启动rviz查看机器人模型 1. 编写机器人模型的urdf文件。 2. 编写launch文件。 3. 运行launch,查看效果。 URDF(Unifi…...
论文阅读-秦汉时期北方边疆组织的空间互动模式与直道的定位(中国)
论文英文题目:A spatial interaction model of Qin-Han Dynasty organisation on the northern frontier and the location of the Zhidao highway (China) 发表于:journal of archaeological science,影响因子:3.030 论文主要是…...
【MySQL_04】数据库基本操作(用户管理--配置文件--远程连接--数据库信息查看、创建、删除)
文章目录 一、MySQL 用户管理1.1 用户管理1.11 mysql.user表详解1.12 添加用户1.13 修改用户权限1.14 删除用户1.15 密码问题 二、MySQL 配置文件2.1 配置文件位置2.2 配置文件结构2.3 常用配置参数 三、MySQL远程连接四、数据库的查看、创建、删除4.1 查看数据库4.2 创建、删除…...
设计模式之建造者模式:原理、实现与应用
引言 建造者模式(Builder Pattern)是一种创建型设计模式,它通过将复杂对象的构建过程分解为多个简单的步骤,使得对象的创建更加灵活和可维护。建造者模式特别适用于构建具有多个组成部分的复杂对象。本文将深入探讨建造者模式的原…...
2025最新群智能优化算法:山羊优化算法(Goat Optimization Algorithm, GOA)求解23个经典函数测试集,MATLAB
一、山羊优化算法 山羊优化算法(Goat Optimization Algorithm, GOA)是2025年提出的一种新型生物启发式元启发式算法,灵感来源于山羊在恶劣和资源有限环境中的适应性行为。该算法旨在通过模拟山羊的觅食策略、移动模式和躲避寄生虫的能力&…...
Apache Log4j 2
目录 1. Apache Log4j 2 简介 1.1 什么是Log4j 2? 1.2 Log4j 2 的主要特性 2. Log4j 2 的核心组件 2.1 Logger 2.2 Appender 2.3 Layout 2.4 Filter 2.5 Configuration 3. Log4j 2 的配置 4. Log4j 2 的使用示例 4.1 Maven 依赖 4.2 示例代码 4.3 输出…...
ArcGIS Pro字段编号相关代码
一、引言 在地理信息系统(GIS)的数据管理与分析中,字段操作是不可或缺的一环。 SHP文件作为常见的地理数据存储格式,其字段的灵活运用对于数据的组织、展示和分析具有重要意义。 在实际工作中,常常需要对字段进行编…...
ubuntu22.04机器人开发环境配置
1. ros2环境配置(humble) #配置源 # https://docs.ros.org/en/humble/Installation/Ubuntu-Install-Debs.html sudo apt install software-properties-common sudo add-apt-repository universe sudo apt update && sudo apt install curl -y# …...
万字深度剖析——JS数据结构(上)
数组本质是对象,键就是索引,值就是元素。 push /unshift 在数组最后/最前添加 pop /shift 把数组最后/最前的元素删除,返回的是被删除的元素 splice(0,2,5)从第0给位置开始删除2个元素,并添加一个元素 数组自带的…...
golang dlv调试工具
golang dlv调试工具 在goland2022.2版本 中调试go程序报错 WARNING: undefined behavior - version of Delve is too old for Go version 1.20.7 (maximum supported version 1.19) 即使你go install了新的dlv也无济于事 分析得出Goland实际使用的是 Goland安装目录下dlv 例…...
【算法 C/C++】二维前缀和
2025 - 03 - 08 - 第 70 篇 Author: 郑龙浩 / 仟濹 【二维前缀和】 文章目录 前缀和与差分 - 我的博客前缀和(二维)1 基本介绍(1) **sum[i][j] 表示什么???**(2) **前缀和怎么求???计算 sum[i][j]…...
如何使用postman来测试接口
一、postman的介绍与下载 可参考: https://blog.csdn.net/freeking101/article/details/80774271 二、api获取网站 阿里云API应用市场 地址:云市场_镜像市场_软件商店_建站软件_服务器软件_API接口_应用市场 - 阿里云 三、具体测试过程 可模拟浏览…...
olmOCR:高效精准的 PDF 文本提取工具
在日常的工作和学习中,是否经常被 PDF 文本提取问题困扰?例如: 想从学术论文 PDF 中提取关键信息,却发现传统 OCR 工具识别不准确或文本格式混乱?需要快速提取商务合同 PDF 中的条款内容,却因工具不给力而…...
Vue项目通过内嵌iframe访问另一个vue页面,获取token适配后端鉴权(以内嵌若依项目举例)
1. 改造子Vue项目进行适配(ruoyi举例) (1) 在路由文件添加需要被外链的vue页面配置 // 若依项目的话是 router/index.js文件 {path: /contrast,component: () > import(/views/contrast/index),hidden: true },(2) 开放白名单 // 若依项目的话是 permission.js 文件 cons…...
请谈谈 HTTP 中的重定向,如何处理 301 和 302 重定向?
HTTP重定向深度解析:301与302的正确使用姿势 一、重定向本质解析 重定向就像快递员送快递时发现地址变更,新地址会写在包裹单的"改派地址"栏。 浏览器收到3xx状态码时,会自动前往Location头指定的新地址。 常用状态码对比&…...
隧道定向号角喇叭为隧道安全保驾护航
隧道广播系统的搭建:科技赋能,打造安全高效的隧道环境。隧道作为现代交通网络的重要组成部分,其安全管理和信息传递的效率直接关系到整个交通系统的运行。然而,隧道环境的特殊性——封闭、狭窄、回声干扰多,使得传统的…...
RuleOS:区块链开发的“破局者”,开启Web3新纪元
RuleOS:区块链开发的“破冰船”,驶向Web3的星辰大海 在区块链技术的浩瀚宇宙中,一群勇敢的探索者正驾驶着一艘名为RuleOS的“破冰船”,冲破传统开发的冰层,驶向Web3的星辰大海。这艘船,正以一种前所未有的姿…...
C#程序结构及基本组成说明
C# 程序的结构主要由以下几个部分组成,以下是对其结构的详细说明和示例: 1. 基本组成部分 命名空间 (Namespace) 用于组织代码,避免命名冲突。通过 using 引入其他命名空间。 using System; // 引入 System 命名空间类 (Class) C# 是面向对象的语言,所有代码必须定义在类或…...
Django与数据库
我叫补三补四,很高兴见到大家,欢迎一起学习交流和进步 今天来讲一讲alpha策略制定后的测试问题 mysql配置 Django模型体现了面向对象的编程技术,是一种面向对象的编程语言和不兼容类型能相互转化的编程技术,这种技术也叫ORM&#…...
力扣热题 100:二叉树专题进阶题解析(后7道)
系列文章目录 力扣热题 100:哈希专题三道题详细解析(JAVA) 力扣热题 100:双指针专题四道题详细解析(JAVA) 力扣热题 100:滑动窗口专题两道题详细解析(JAVA) 力扣热题 100:子串专题三道题详细解析(JAVA) 力…...
Linux——system V共享内存
共享内存区是最快的IPC(进程内通信)形式,不再通过执行进入内核的系统调用来传递彼此的数据 1.共享内存的原理 IPC通信的本质是让不同的进程先看到同一份资源,然后再进行通信,所以想要通过共享内存进行通信,那么第一步一定是让两个…...
【C语言】指针篇
目录 C 语言指针概述指针的声明和初始化声明指针初始化指针 指针的操作解引用操作指针算术运算 指针的用途动态内存分配作为函数参数 指针与数组数组名作为指针通过指针访问数组元素指针算术和数组数组作为函数参数指针数组和数组指针指针数组数组指针 函数指针函数指针的定义和…...
XGBoost介绍
XGBoost:是eXtreme Gradient Boosting(极端梯度提升)的缩写,是一种强大的集成学习(ensemble learning)算法,旨在提高效率、速度和高性能。XGBoost是梯度提升(Gradient Boosting)的优化实现。集成学习将多个弱模型组合起来,形成一个…...
力扣:找到一个数字的 K 美丽值(C++)
一个整数 num 的 k 美丽值定义为 num 中符合以下条件的 子字符串 数目: 子字符串长度为 k 。子字符串能整除 num 。 给你整数 num 和 k ,请你返回 num 的 k 美丽值。 注意: 允许有 前缀 0 。0 不能整除任何值。 一个 子字符串 是一个字符串里…...
数据结构:有序表的合并
前文介绍了《有序表的插入》,本文介绍有序表的合并。这两种对有序表的操作,是数据结构中常考的内容,特别是在 408 考卷中,在算法设计的题目中,有可能会考查对有序表的操作。那么,这两篇文章中的方法就是能够…...
AI写论文提示词指令大全,快速写论文
目录 一、十大学术写作提示词1、研究主题2、研究问题3、论文架构4、学术论证5、文献关键要素6、专业文本可读性转换7、学术语言规范化8、提高语言准确性9、多维度、深层论证10、优化文本结构 二、快速写论文提示词1、确认研究选题2、整理相关资料3、快速完成论文大纲4、整合文献…...
物联网IoT系列之MQTT协议基础知识
文章目录 物联网IoT系列之MQTT协议基础知识物联网IoT是什么?什么是MQTT?为什么说MQTT是适用于物联网的协议?MQTT工作原理核心组件核心机制 MQTT工作流程1. 建立连接2. 发布和订阅3. 消息确认4. 断开连接 MQTT工作流程图MQTT在物联网中的应用 …...
【从零开始学习计算机科学】计算机组成原理(七)存储器与存储器系统
【从零开始学习计算机科学】计算机组成原理(七)存储器与存储器系统 存储器存储器相关概念存储器分类存储器系统存储器性能指标存储器层次概述程序访问的局部性原理SRAM存储器存储器的读写周期DRAM存储器DRAM控制器高性能的主存储器存储器扩展只读存储器ROM光擦可编程只读存储…...
ctf-WEB: 关于 GHCTF Message in a Bottle plus 与 Message in a Bottle 的非官方wp解法
Message in a Bottle from bottle import Bottle, request, template, runapp Bottle()# 存储留言的列表 messages [] def handle_message(message):message_items "".join([f"""<div class"message-card"><div class"me…...
Java集合_八股场景题
Java集合 在Java开发中,集合框架是面试和实际开发中非常重要的内容。以下是一些常见的Java集合八股文问题和场景题,以及详细答案和示例代码。 1. Java集合框架的结构是什么? 答案: Java集合框架主要分为三大接口:Col…...
Scaled_dot_product_attention(SDPA)使用详解
在学习huggingFace的Transformer库时,我们不可避免会遇到scaled_dot_product_attention(SDPA)这个函数,它被用来加速大模型的Attention计算,本文就详细介绍一下它的使用方法,核心内容主要参考了torch.nn.functional中该函数的注释…...
SpringBoot(一)--搭建架构5种方法
目录 一、⭐Idea从spring官网下载打开 2021版本idea 1.打开创建项目 2.修改pom.xml文件里的版本号 2017版本idea 二、从spring官网下载再用idea打开 三、Idea从阿里云的官网下载打开 编辑 四、Maven项目改造成springboot项目 五、从阿里云官网下载再用idea打开 Spri…...
初识大模型——大语言模型 LLMBook 学习(一)
1. 大模型发展历程 🔹 1. 早期阶段(1950s - 1990s):基于规则和统计的方法 代表技术: 1950s-1960s:规则驱动的语言处理 早期的 NLP 主要依赖 基于规则的系统,如 Noam Chomsky 提出的 生成语法&…...
Array and string offset access syntax with curly braces is deprecated
警告信息 “Array and string offset access syntax with curly braces is deprecated” 是 PHP 中的一个弃用警告(Deprecation Notice),表明在 PHP 中使用花括号 {} 来访问数组或字符串的偏移量已经被标记为过时。 背景 在 PHP 的早期版本…...