Bootstrap

鸿蒙——数据持久化存储(AppStorage、PersitentStoreage、数据库、首选项)

Localstorage-内存化存储- 局部可用

AppStorage-内存化存储- 全局可用

PersitentStoreage-写入磁盘(沙箱) 全局可用

首选项-写入磁盘-全局可用

关系型数据库-写入磁盘

1.用户首选项:

获取Preferences实例、保存/更新数据、获取数据

用户首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询

Key-Value 是什么结构?AppStorage.setOrCreate(key,value)

作用:当用户希望有一个全局唯一存储数据的地方,可以采用用户首选项来进行存储。

应用首选项的持久化文件保存在应用沙箱内部,可以通过context获取其路径。

首选项适用于存储小型配置项数据,例如应用的用户个性化设置(字体大小、是否开启夜间模式等)。

限制约束:

  • Key键为string类型,要求非空且长度不超过80个字节。
  • 如果Value值为string类型,请使用UTF-8编码格式,不为空时长度不超过8192个字节。
  • 存储的数据量应该是轻量级的,建议存储的数据不超过一万条,否则会在内存方面产生较大的开销。

步骤:

  1. 初始化首选项实例
import { preferences } from '@kit.ArkData';  // 导入首选项api
const KEY = 'searchStore' // 获得首选项实例
const preferencesInstance = preferences.getPreferencesSync(getContext(), { name: KEY })

2.向首选项写入数据put 并持久化flush

// 第一个参数KEY:要写入数据的key
// 第二个参数value:要写入的数据
preferencesInstance.put(KEY, 要保存的值)

// 将数据写入到文件中
preferencesInstance.flush()

3.获取首选项数据 get

// 利用首选项实例的get方法可以读取KEY中的数据
// 第一个参数KEY:要获取数据的key
// 第二个参数defValue:如果没有读取到数据,则返回默认数据
preferencesInstance.get(KEY, 默认值)
  1. 删除指定KEY的数据 deleteSync
preferencesInstance.deleteSync(KEY);

查看数据文件:

 

2.AppStorage-页面内使用:

用法:

  • 初始化: AppStorage.SetOrCreate(key,value)
  • 单向 @StorageProp('user') 组件内可变
  • 双向 @StorageLink('user') 全局均可变
// 需要通过 key 来指定对应的数据
// 获取数据:
// AppStorage.get<ValueType>(key)

// 覆盖数据
// AppStorage.set<ValueType>(key , value)
class User {
  name: string = ''
  age: number = 0
}

AppStorage.setOrCreate <User> ('user',  { name: 'jack', age: 18 } )

3.PersistentStorage

LocalStorage和AppStorage都是运行时的内存,在应用退出后就没有了,如果要在应用退出后再次启动依然能保存选定的结果,这就需要用到PersistentStorage。

用法:

        PersistentStorage.PersistProp('属性名', 值)

        后续直接通过 AppStorage 的 Api 来获取并修改即可,AppStorage 的修改会自动同步到PersistentStorage中

3.1 保存基本类型:

        number, string, boolean, enum 等简单类型都支持

  1. 初始化PersistentStorage
  2. 通过 AppStorage 获取并修改数据
PersistentStorage.persistProp<string>('info','你好啊!!')

@Entry
@Component
struct PersistentStoragePage01 {
  @StorageLink('info')
  info:string=''

  build() {
    Row() {
      Column() {
        Text(this.info)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(()=>{
            this.info+='!'
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}
3.2 保存复杂类型:

支持的复杂类型:

  • 可以被JSON.stringify( )和JSON.parse(  )重构的对象。例如Date, Map, Set等内置类型则不支持,以及对象的属性方法不支持持久化。
  • 自己定义的 class、interface 基本都是支持的

不允许的类型和值有:

  • 不支持嵌套对象(对象数组,对象的属性是对象等)。因为目前框架无法检测AppStorage中嵌套对象(包括数组)值的变化,所以无法写回到PersistentStorage中。
  • 不支持 undefined 和 null

        持久化变量最好是小于2kb的数据,如果开发者需要存储大量的数据,建议使用数据库api

interface FoodInfo{
  name:string
  price:number
}
PersistentStorage.persistProp<FoodInfo[]>('foods',[
  {name:'芒果',price:10},
  {name:'草莓',price:15},
  {name:'樱桃',price:14},
])

@Entry
@Component
struct PersistentStoragePage02 {
  @StorageLink('foods')
  foods:FoodInfo[]=[]

  build() {
    Row() {
      Column() {
        Text(JSON.stringify(this.foods))
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .onClick(()=>{
            // this.foods[0].name='西瓜'
            this.foods[0] = {
              name:'西瓜',
              price:29
            }
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

总结:LocalStorage & AppStorage & PersistentStorage区别?

        1.localStorage是页面级数据存储,在页面中创建实例,组件中使用@LocalStorageLink和@LocalStorageProp装饰器修饰对应的状态变量,绑定对应的组件使用比状态属性更灵活。

        appStorage是进程级数据存储,进程启动时自动创建了唯一实例,在各个页面组件中@StorageProp和@StorageLink装饰器修饰对应的状态变量

        localStorage和appStorage数据存取都是在主线程进行的,且api只提供了同步接口,存取数据时要注意数据的大小

        2.LocalStorage、AppStorage和PersistentStorage是用于应用状态管理的不同机制,它们的区别如下:

  • LocalStorage:是页面级的 UI 状态存储。可以在页面内或 UIAbility 实例内共享状态,通常用于在页面间传递数据。它是内存中的非持久化存储,应用程序决定其生命周期,当没有引用时会被垃圾回收。
  • AppStorage由 UI 框架在应用程序启动时创建,为应用程序 UI 状态属性提供中央存储。它可以在应用程序的不同页面和组件之间共享数据。
  • PersistentStorage用于持久化存储 UI 状态。它通常与AppStorage配合使用,将AppStorage中选择的数据写入磁盘,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。

        总的来说,LocalStorage和AppStorage主要用于在应用程序运行时存储和共享 UI 状态,而PersistentStorage则用于将特定的 UI 状态持久化到磁盘中,以便在应用程序重新启动后恢复。

4.LocalStorage -页面内使用:

用法:

  • 创建 LocalStorage 实例:
    • const storage = new LocalStorage({ key: value })
    • 设置给@Entry的参数:@Entry(storage)
  • 单向 @LocalStorageProp('user') 类似于@Prop,
  • 双向 @LocalStorageLink('user') 类似于@Link,全局均可变

5.数据库存储:

   

        5.2应用数据持久化:

        是指应用将 内存中的数据通过文件或数据库的形式保存到设备上。

        内存中的数据形态通常是任意的数据结构或数据对象,存储介质上的数据形态可能是文本、数据库、二进制文件等。
        HarmonyOS标准系统支持典型的存储数据形态,包括用户首选项、键值型数据库、关系型数据库。


●用户首选项(Preferences):

        通常用于保存应用的配置信息。数据通过文本的形式保存在设备中,应用使用过程中会将文本中的数据全量加载到内存中,所以访问速度快、效率高,但不适合需要存储大量数据的场景。
●键值型数据库(KV-Store):

        一种非关系型数据库,其数据以“键值”对的形式进行组织、索引和存储,其中“键”作为唯一标识符。适合很少数据关系和业务关系的业务数据存储,同时因其在分布式场景中降低了解决数据库版本兼容问题的复杂度,和数据同步过程中冲突解决的复杂度而被广泛使用。相比于关系型数据库,更容易做到跨设备跨版本兼容。
●关系型数据库(RelationalStore):

        一种关系型数据库,以行和列的形式存储数据,广泛用于应用中的关系型数据的处理,包括一系列的增、删、改、查等接口,开发者也可以运行自己定义的SQL语句来满足复杂业务场景的需要。

        5.3关系型数据库RDB:
        是一种基于关系模型来管理数据的数据库。关系型数据库基于SQLite组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。不支持Worker线程。

        ArkTS侧支持的基本数据类型:number、string、二进制类型数据、boolean。为保证插入并读取数据成功,建议一条数据不要超过2M。超出该大小,插入成功,读取失败。

        该模块提供以下关系型数据库相关的常用功能:

  • RdbPredicates: 数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项主要用来定义数据库的操作条件。
  • RdbStore提供管理关系数据库(RDB)方法的接口
  • ResultSet:提供用户调用关系型数据库查询接口之后返回的结果集合
        5.4隐私笔记数据库操作封装:
import { relationalStore, ValuesBucket } from '@kit.ArkData'

// 隐私笔记的类型
export interface PrivacyNoteDBInfo extends ValuesBucket {
  id: number | null
  title: string
  content: string
  date_added: number
}

class PrivacyNoteDB {
  tableName = 'PRIVACY_NOTE'
  // // 创建数据库的语句
  sqlCreate = `CREATE TABLE IF NOT EXISTS ${this.tableName} (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        content TEXT NOT NULL,
        date_added INTEGER NOT NULL
      )`
  // 操作数据库的实例
  store: relationalStore.RdbStore | null = null

  // 获取管理数据库的对象
  async getStoreInstance() {
    if (this.store) {
      // 如果 store 已经存在,直接返回使用
      return this.store
    }
    // 获取管理数据库的对象
    const store = await relationalStore.getRdbStore(getContext(), {
      name: this.tableName + '.db',
      securityLevel: relationalStore.SecurityLevel.S1
    })
    // store 保存起来,方便下次直接获取
    this.store = store
    store.executeSql(this.sqlCreate) // 创建数据库的表
    return store
  }

  // 新增数据
  async insert(item: PrivacyNoteDBInfo) {
    const store = await this.getStoreInstance()
    const rowId = await store.insert(this.tableName, item) //新增成功后返回 id
    // Promise.resolve 表示成功
    // Promise.reject  表示失败,失败可通过 try catch 捕获错误信息
    return rowId > 0 ? Promise.resolve(rowId) : Promise.reject('insert error')
  }

  // 查询总数
  async queryCount() {
    const store = await this.getStoreInstance() // 获取管理数据库的对象
    const predicates = new relationalStore.RdbPredicates(this.tableName) // 谓词(条件)
    const resultSet = await store.query(predicates) // 查询,返回结果集

    // 补充:新创建的数据库可能会返回 -1,通过三元表达式修正错误
    return resultSet.rowCount > 0 ? resultSet.rowCount : 0
  }

  // 查询数据:
  // 1. 不传入 id 查询所有数据  2. 传入 id 查询一条数据
  // 3. 查询结果根据 id 倒序排列:orderByDesc 倒序(大到小),orderBy 正序(小到大)
  async query(id?: number) {
    const store = await this.getStoreInstance() // 获取管理数据库的对象
    const predicates = new relationalStore.RdbPredicates(this.tableName) // 谓词(条件)

    // 如果有id,增加id条件
    if (id) {
      predicates.equalTo('id', id)
    }
    predicates.orderByDesc('id') // 倒序(大到小)
    const resultSet = await store.query(predicates) // 查询,返回结果集

    // --准备数组,用于存储查询结果
    const list: PrivacyNoteDBInfo[] = []
    // 循环结果集
    while (resultSet.goToNextRow()) {
      list.push({
        // 提取数据
        id: resultSet.getLong(resultSet.getColumnIndex('id')),
        title: resultSet.getString(resultSet.getColumnIndex('title')),
        content: resultSet.getString(resultSet.getColumnIndex('content')),
        date_added: resultSet.getLong(resultSet.getColumnIndex('date_added')),
      })
    }
    // 释放结果集内存空间(性能优化)
    resultSet.close()
    return list // 循环结束后,返回结果
  }

  // 根据 id 删除数据
  async delete(id: number) {
    const store = await this.getStoreInstance()
    const predicates = new relationalStore.RdbPredicates(this.tableName)
    // 根据id查找
    predicates.equalTo('id', id)
    // 删除完成,返回受影响的行数
    const rowCount = await store.delete(predicates)
    return rowCount ? Promise.resolve() : Promise.reject('delete error')
  }

  // 根据id更新数据
  async update(item: PrivacyNoteDBInfo) {
    // 如果没有 id 直接退出
    if (!item.id) {
      return Promise.reject('id is null')
    }
    const store = await this.getStoreInstance()
    const predicates = new relationalStore.RdbPredicates(this.tableName)

    // 根据id更新数据
    predicates.equalTo('id', item.id)
    // 更新完成,返回受影响的行数
    const rowCount = await store.update(item, predicates)
    return rowCount ? Promise.resolve(rowCount) : Promise.reject('update error')
  }


}

// 导出操作隐私笔记数据库的类
export const privacyNoteDB = new PrivacyNoteDB()

6.AppStorage、PersitentStoreage、数据库、首选项区别?

6.1AppStorage:

  • AppStorage 是鸿蒙操作系统中用于存储应用全局 UI 状态的机制。它是与应用的进程绑定的,由 UI 框架在应用程序启动时创建,为应用程序 UI 状态属性提供中央存储。
  • AppStorage 是在应用启动时会被创建的单例,目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorage 将在应用运行过程保留其属性,属性通过唯一的键字符串值访问。

6.2PersistentStorage:

  • PersistentStorage 是鸿蒙操作系统中用于持久化存储选定的 AppStorage 属性的机制。
  • 它的作用是确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。PersistentStorage 允许的类型和值有限,例如 number, string, boolean, enum 等简单类型,以及可以被 JSON.stringify() 和 JSON.parse() 重构的对象。
  • 需要注意的是,PersistentStorage 的持久化变量最好是小于2kb的数据,因为其写入磁盘的操作是同步的,大量的数据本地化读写会同步在 UI 线程中执行,可能会影响 UI 渲染性能。如果开发者需要存储大量数据,建议使用数据库 API。

6.3数据库:

  • 鸿蒙操作系统支持使用数据库来存储和管理数据。
  • 数据库是一种结构化的数据存储解决方案,采用表格的形式存储数据,支持复杂的查询和关联操作。
  • 数据库适用于存储大量数据,并且需要进行复杂查询或多表关联的场景。它提供了事务、索引、约束等功能,用于保证数据的完整性和一致性。

6.4首选项:

  • 首选项(Preferences)是鸿蒙系统提供的一种轻量级的本地存储解决方案,
  • 适用于存储少量的简单数据。
  • 它以键值对的形式存储数据,支持基本的增删改查操作。

区别总结:

  • AppStorage 是应用全局的 UI 状态存储,与应用进程绑定,适合存储应用级别的状态数据。
  • PersistentStorage 用于持久化存储选定的 AppStorage 属性,确保数据在应用重启后保持不变,适合存储小量数据。
  • 数据库 是结构化的数据存储解决方案,适用于存储大量数据和进行复杂查询,适合需要数据完整性和一致性保障的场景。
  • 首选项 是轻量级的本地存储解决方案,适用于存储少量简单数据,适合作为应用配置数据的存储方式。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;