Android数据存储技术

news2025/1/19 13:15:16

一、文件存储

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Type something here"
        />
</LinearLayout>
package com.jpc.filepersistencetest

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.EditText
import android.widget.Toast
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.lang.StringBuilder

class MainActivity : AppCompatActivity() {
    private lateinit var editText: EditText
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        editText = findViewById<EditText>(R.id.editText)
        val fileData = load()
        if (fileData.isNotEmpty()) {
            // 调用EditText的setText()方法将内容填充到EditText
            //里,并调用setSelection()方法将输入光标移动到文本的末尾位置以便继续输入
            editText.setText(fileData)
            editText.setSelection(fileData.length)
            Toast.makeText(this, "数据已恢复", Toast.LENGTH_SHORT).show()
        }
    }

    // 在销毁时保存数据到文件中
//    override fun onDestroy() {
//        super.onDestroy()
//        val inputText = editText.text.toString()
//        save(inputText)
//    }

    private fun save(inputText: String){
        // 保存数据到TXT文件中,文件名data
        val output = openFileOutput("data", Context.MODE_PRIVATE)
        val writer = BufferedWriter(OutputStreamWriter(output))
        writer.use {
            it.write(inputText)
        }
    }
    // 从文件中加载数据
    private fun load():String {
        val builder = StringBuilder()
        val input = openFileInput("data")
        val reader = BufferedReader(InputStreamReader(input))
        reader.use {
            reader.forEachLine {
                builder.append(it)
            }
        }
        return builder.toString()
    }
}

二、SharedPreferences存储

不同于文件的存储方式,SharedPreferences是使用键值对的方式来存储数据的。当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把相应的值取出来。SharedPreferences还支持多种不同的数据类型存储,如果存储的数据类型是整型,那么读取出来的数据也是整型的;如果存储的数据是一个字符串,那么读取出来的数据仍然是字符串。

<Button
        android:id="@+id/btn_save_SharedPreferences"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="保存数据"
        />
// 指定SharedPreferences的文件名为data,并得到了
        //SharedPreferences.Editor对象。接着向这个对象中添加了3条不同类型的数据,最
        //后调用apply()方法进行提交,从而完成了数据存储的操作
        val btnSave: Button = findViewById<Button>(R.id.btn_save_SharedPreferences)
        btnSave.setOnClickListener{
            val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
            editor.putString("name", "Tom")
            editor.putInt("age", 28)
            editor.putBoolean("married", false)
            editor.apply()
        }
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="name">Tom</string>
    <boolean name="married" value="false" />
    <int name="age" value="28" />
</map>

SharedPreferences对象中提供了一系列的get方法,用于读取存储的数据,每种get方法都对应了SharedPreferences.Editor中的一种put方法,比如读取一个布尔型数据就使用getBoolean()方法,读取一个字符串就使用getString()方法。这些get方法都接收两个参数:第一个参数是键,传入存储数据时使用的键就可以得到相应的值了;第二个参数是默认值,即表示当传入的键找不到对应的值时会以什么样的默认值进行返回。

val btnRestore = findViewById<Button>(R.id.btn_restore_SharedPreferences)
        btnRestore.setOnClickListener{
            val prefs = getSharedPreferences("data", Context.MODE_PRIVATE)
            val name = prefs.getString("name", "")
            val age = prefs.getInt("age", 0)
            val married = prefs.getBoolean("married", false)
            Toast.makeText(this, "我的信息:${name}-${age}-${married}", Toast.LENGTH_SHORT).show()
        }
1、记住密码功能
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="match_parent"
 android:layout_height="match_parent">
 <LinearLayout
 android:orientation="horizontal"
 android:layout_width="match_parent"
 android:layout_height="wrap_content">
 <CheckBox
 android:id="@+id/rememberPass"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content" />
 <TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:textSize="18sp"
 android:text="Remember password" />
 </LinearLayout>
 <Button
 android:id="@+id/login"
 android:layout_width="match_parent"
 android:layout_height="60dp"
 android:text="Login" />
</LinearLayout>
class LoginActivity : BaseActivity() {
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 setContentView(R.layout.activity_login)
 val prefs = getPreferences(Context.MODE_PRIVATE)
www.blogss.cn
 val isRemember = prefs.getBoolean("remember_password", false)
 if (isRemember) {
 // 将账号和密码都设置到文本框中
 val account = prefs.getString("account", "")
 val password = prefs.getString("password", "")
 accountEdit.setText(account)
 passwordEdit.setText(password)
 rememberPass.isChecked = true
 }
 login.setOnClickListener {
 val account = accountEdit.text.toString()
 val password = passwordEdit.text.toString()
 // 如果账号是admin且密码是123456,就认为登录成功
 if (account == "admin" && password == "123456") {
 val editor = prefs.edit()
 if (rememberPass.isChecked) { // 检查复选框是否被选中
 editor.putBoolean("remember_password", true)
 editor.putString("account", account)
 editor.putString("password", password)
 } else {
 // 如果没有被选中,就简单地调用一下clear()方法,
// 将SharedPreferences文件中的数据全部清除掉。
 editor.clear()
 }
 editor.apply()
 val intent = Intent(this, MainActivity::class.java)
 startActivity(intent)
 finish()
 } else {
 Toast.makeText(this, "account or password is invalid",
 Toast.LENGTH_SHORT).show()
 }
 }
 }
}

三、 SQLite数据库存储

显然,文件存储和SharedPreferences存储只适用于保存一些简单的数据和键值对。
但是,Android系统是内置了数据库的,SQLite是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百KB的内存就足够了,因而特别适合在移动设备上使用。SQLite不仅支持标准的SQL语法,还遵循了数据库的ACID事务。

1、创建数据库
package com.jpc.filepersistencetest

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toast

class MyDatabaseHelper(private val context: Context, name: String, version: Int):
    SQLiteOpenHelper(context, name, null, version) {

        // 建表语句
        private val createBook = "create table Book (" +
                "id integer primary key autoincrement," +
                "name text," +
                "author text," +
                "price real," +
                "pages integer)"
    override fun onCreate(db: SQLiteDatabase?) {
        db?.execSQL(createBook)
        Toast.makeText(context, "建表成功", Toast.LENGTH_SHORT).show()
    }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {

    }
}
// 构建了一个MyDatabaseHelper对象,并且通过构造函数的参
        //数将数据库名指定为BookStore.db,版本号指定为1,然后在“Create Database”按钮的点击
        //事件里调用了getWritableDatabase()方法。
        val btnCreateDataBase = findViewById<Button>(R.id.btn_create_database)
        val databaseHelper = MyDatabaseHelper(this, "BookStore.db", 1)
        btnCreateDataBase.setOnClickListener{
            databaseHelper.writableDatabase
        }
2、升级数据库

SQLiteOpenHelper的构造方法里接收的第四个参数,它表示当前数据库的版本号,之前我们传入的是1,现在只要传入
一个比1大的数,就可以让onUpgrade()方法得到执行了。

package com.jpc.filepersistencetest

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toast

class MyDatabaseHelper(private val context: Context, name: String, version: Int):
    SQLiteOpenHelper(context, name, null, version) {

        // 建表语句
        private val createBook = "create table Book (" +
                "id integer primary key autoincrement," +
                "name text," +
                "author text," +
                "price real," +
                "pages integer)"

    private val createCategory = "create table Category (" +
            "id integer primary key autoincrement," +
            "category_name text," +
            "category_code integer)"

    override fun onCreate(db: SQLiteDatabase?) {
        db?.execSQL(createBook)
        db?.execSQL(createCategory)
        Toast.makeText(context, "建表成功", Toast.LENGTH_SHORT).show()
    }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        // 先删除已经存在的表
        db?.execSQL("drop table if exists Book")
        db?.execSQL("drop table if exists Category")
        // 再重新创建
        onCreate(db)
    }
}
// 第一次创建数据库,版本version为1
        // val databaseHelper = MyDatabaseHelper(this, "BookStore.db", 1)
        // 升级数据库,将版本改为比上一次大的数值,就会调用onUpgrade方法
        val databaseHelper = MyDatabaseHelper(this, "BookStore.db", 2)
        btnCreateDataBase.setOnClickListener{
            databaseHelper.writableDatabase
        }
3、添加数据

调用SQLiteOpenHelper的getReadableDatabase()或
getWritableDatabase()方法是可以用于创建和升级数据库的,不仅如此,这两个方法还都会返回一个SQLiteDatabase对象,借助这个对象就可以对数据进行CRUD操作了。
SQLiteDatabase中提供了一个 insert() 方法,专门用于添加数据。它接收3个参数:第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字;第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般我们用不到这个功能,直接传入null即可;第三个参数是一个ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。

<Button
        android:id="@+id/btn_add_data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="向表添加数据"
        />
// 添加数据
        val btnAddData = findViewById<Button>(R.id.btn_add_data)
        btnAddData.setOnClickListener{
            val db = databaseHelper.writableDatabase
            val data1 = ContentValues().apply {
                // 第一条数据
                // 不需要给id赋值,因为id自增
                put("name", "The Da Vinci Code")
                put("author", "Dan Brown")
                put("pages", 454)
                put("price", 16.96)
            }
            // 插入第一条数据
            db.insert("Book", null, data1)
            val values2 = ContentValues().apply {
                // 开始组装第二条数据
                put("name", "The Lost Symbol")
                put("author", "Dan Brown")
                put("pages", 510)
                put("price", 19.95)
            }
            db.insert("Book", null, values2) // 插入第二条数据
        }
4、修改数据

SQLiteDatabase中提供了一个非常好用的update()方法,用于对数据进行更新。这个方法接收4个参数:第一个参数和insert()方法一样,也是表名,指定更新哪张表里的数据;第二个参数是ContentValues对象,要把更新数据在这里组装进去;第三、第四个参数用于约束更新某一行或某几行中的数据,不指定的话默认会更新所有行。

// 修改数据
        val btnUpdate = findViewById<Button>(R.id.btn_update_data)
        btnUpdate.setOnClickListener{
            val db = databaseHelper.writableDatabase
            val contentValues = ContentValues()
            contentValues.put("price", 10.99)
            // 第三、第四个参数来指定具体更新
            //哪几行。第三个参数对应的是SQL语句的where部分,表示更新所有name等于?的行,而?是一
            //个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应
            //的内容,arrayOf()方法是Kotlin提供的一种用于便捷创建数组的内置方法
            db.update("Book", contentValues, "name = ?", arrayOf("The Da Vinci Code"))
        }
5、删除数据

SQLiteDatabase中提供了一个delete()方法,专门用于删除数据。这个方法接收3个参数:第一个参数仍然是表名,这个没什么好说的;第二、第三个参数用于约束删除某一行或某几行的数据,不指定的话默认会删除所有行。

// 删除数据
        val btnDelete = findViewById<Button>(R.id.btn_delete_data)
        btnDelete.setOnClickListener{
            val db = databaseHelper.writableDatabase
            // 指定删除条件,即where字句,如果不指定就会删除表中所有的数据
            db.delete("Book", "pages > ?", arrayOf("500"))
        }
6、查询数据

SQLiteDatabase中还提供了一个query()方法用于对数据进行查询。这个方法的参数非常复杂,最短的一个方法重载也需要传入7个参数。那我们就先来看一下这7个参数各自的含义吧。第一个参数不用说,当然还是表名,表示我们希望从哪张表中查询数据。
第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。第三、第四个参数用于约束查询某一行或某几行的数据,不指定则默认查询所有行的数据。第五个参数用于指定需要去group by的列,不指定则表示不对查询结果进行group by操作。第六个参数用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。调用query()方法后会返回一个Cursor对象,查询到的所有数据都将从这个对象中取出。
在这里插入图片描述

        // 查询数据
        val btnQuery = findViewById<Button>(R.id.btn_query_data)
        btnQuery.setOnClickListener{
            val db = databaseHelper.writableDatabase
            // 查询Book表中的所有数据
            val cursor = db.query("Book", null, null, null, null, null, null)
            if (cursor.moveToFirst()) {
                do {
                    // 遍历Cursor对象,取出数据并打印
                    // 要检查参数,不然报错
                    var index1 = if (cursor.getColumnIndex("name") >= 0) cursor.getColumnIndex("name") else 0
                    val name = cursor.getString(index1)
                    index1 = if (cursor.getColumnIndex("author") >= 0) cursor.getColumnIndex("author") else 0
                    val author = cursor.getString(index1)
                    index1 = if (cursor.getColumnIndex("pages") >= 0) cursor.getColumnIndex("pages") else 0
                    val pages = cursor.getInt(index1)
                    index1 = if (cursor.getColumnIndex("price") >= 0) cursor.getColumnIndex("price") else 0
                    val price = cursor.getDouble(index1)
                    Toast.makeText(this, "查询到数据:${name}-${author}-${pages}-${price}", Toast.LENGTH_SHORT).show()
                }while (cursor.moveToNext())
            }
            // 需要关闭Cursor对象
            cursor.close()
        }
7、使用SQL语句操作数据库
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
 arrayOf("The Da Vinci Code", "Dan Brown", "454", "16.96")
)
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
 arrayOf("The Lost Symbol", "Dan Brown", "510", "19.95")
)

db.execSQL("update Book set price = ? where name = ?", arrayOf("10.99", "The Da Vinci Code"))

db.execSQL("delete from Book where pages > ?", arrayOf("500"))

val cursor = db.rawQuery("select * from Book", null)
8、使用事务
class MainActivity : AppCompatActivity() {
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 setContentView(R.layout.activity_main)
 val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
 ...
 replaceData.setOnClickListener {
 val db = dbHelper.writableDatabase
 db.beginTransaction() // 开启事务
 try {
 db.delete("Book", null, null)
if (true) {
 // 手动抛出一个异常,让事务失败
 throw NullPointerException()
 }
 val values = ContentValues().apply {
 put("name", "Game of Thrones")
 put("author", "George Martin")
 put("pages", 720)
 put("price", 20.85)
 }
 db.insert("Book", null, values)
 db.setTransactionSuccessful() // 事务已经执行成功
 } catch (e: Exception) {
 e.printStackTrace()
 } finally {
 db.endTransaction() // 结束事务
 }
 }
 }
}
9、升级数据库的最佳写法
class MyDatabaseHelper(val context: Context, name: String, version: Int):
 SQLiteOpenHelper(context, name, null, version) {
 private val createBook = "create table Book (" +
 " id integer primary key autoincrement," +
 "author text," +
 "price real," +
 "pages integer," +
 "name text," +
 "category_id integer)"
 private val createCategory = "create table Category (" +
 "id integer primary key autoincrement," +
 "category_name text," +
 "category_code integer)"
 override fun onCreate(db: SQLiteDatabase) {
 db.execSQL(createBook)
 db.execSQL(createCategory)
 }
 override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
 if (oldVersion <= 1) {
 db.execSQL(createCategory)
 }
 if (oldVersion <= 2) {
 db.execSQL("alter table Book add column category_id integer")
 }
 }
}

可以看到,首先我们在Book表的建表语句中添加了一个category_id列,这样当用户直接安装第3版的程序时,这个新增的列就已经自动添加成功了。然而,如果用户之前已经安装了某一版本的程序,现在需要覆盖安装,就会进入升级数据库的操作中。在onUpgrade()方法里,我们添加了一个新的条件,如果当前数据库的版本号是2,就会执行alter命令,为Book表新增一个category_id列。这里请注意一个非常重要的细节:每当升级一个数据库版本的时候,onUpgrade()方法里都一定要写一个相应的if判断语句。为什么要这么做呢?这是为了保证App在跨版本升级的时候,每一次的数据库修改都能被全部执行。比如用户当前是从第2版升级到第3版,那么只有第二条判断语句会执行,而如果用户是直接从第1版升级到第3版,那么两条判断语句都会执行。使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据完全不会丢失。

10、简化写法
// 简化后的写法
            getSharedPreferences("data", Context.MODE_PRIVATE).edit{
                putString("name", "Tom")
                putInt("age", 28)
                putBoolean("married", false)
            }
// 使用更新的函数
            val values = contentValuesOf("name" to "Game of Thrones", "author" to "George Martin",
                "pages" to 720, "price" to 20.85)
            db.insert("Book", null, values)

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

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

相关文章

【C++】c++11新特性(一)

目录 { }列表初始化 内置类型---对单值变量及数组的初始化 列表初始化时进行的类型转换 自定义类型---对类对象或结构的初始化 initializer_list 1. 定义接受 initializer_list 参数的构造函数 2. 在函数中使用 initializer_list 参数 3. 使用 initializer_list 与 vect…

C#将Console写至文件,且文件固定最大长度

参考文章 将C#的Console.Write同步到控制台和log文件输出 业务需求 在生产环境中&#xff0c;控制台窗口不便展示出来。 为了在生产环境中&#xff0c;完整记录控制台应用的输出&#xff0c;选择将其输出到文件中。 但是&#xff0c;一次性存储所有输出的话&#xff0c;文件会…

基于SSM+Jsp+Mysql的二手车交易网站

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

5G智慧地铁数字孪生可视化平台,推进铁路行业数字化转型

随着科技的快速发展&#xff0c;5G智慧地铁数字孪生可视化平台正逐渐成为铁路行业数字化转型的重要推动力。巨蟹数科数字孪生平台集成了5G通信技术、大数据分析、云计算和人工智能等先进技术&#xff0c;通过构建数字孪生模型&#xff0c;实现对地铁运营全过程的实时监控、预测…

【智能排班系统】基于SpringSecurity实现登录验证、权限验证

文章目录 SpringSecurity介绍sss-security实现依赖工具类Jwt工具JSON响应工具加密工具类 用户上下文用户信息实体类用户上下文 自定义重写自定义无权限的报错自定义密码加密自定义用户类 过滤器登录过滤器权限过滤器 Service登录Service 配置类说明登录验证权限验证IP流量限制 …

JDK安全剖析之安全处理入门

0.前言 Java 安全包括大量 API、工具以及常用安全算法、机制和协议的实现。Java 安全 API 涵盖了广泛的领域&#xff0c;包括加密、公钥基础设施、安全通信、身份验证和访问控制。Java 安全技术为开发人员提供了编写应用程序的全面安全框架&#xff0c;还为用户或管理员提供了…

蓝桥集训之斐波那契数列

蓝桥集训之斐波那契数列 核心思想&#xff1a;矩阵乘法 将原本O(n)的递推算法优化为O(log2n) 构造1x2矩阵f和2x2矩阵a 发现f(n1) f(n) * a 则f(n1) f(1) * an可以用快速幂优化 #include <iostream>#include <cstring>#include <algorithm>using na…

跨站请求伪造漏洞(CSRF)

什么是CSRF CSRF&#xff08;Cross-Site Request Forgery&#xff09;&#xff0c;也被称为 one-click attack 或者 session riding&#xff0c;即跨站请求伪造攻击。 漏洞原理 跨站请求伪造漏洞的原理主要是利用了网站对用户请求的验证不严谨。攻击者会在恶意网站中构造一个…

RAG知识分享

文章目录 博客详细讲解视频点击查看高清脑图 1.为什么要做RAG1.1. 解决幻觉问题1.1.1 直接输入问题1.1.2. 问题 相关知识 2. 什么是RAG2.1. 基本概念2.2. 基本RAG方法2.2.1. 知识预处理2.2.2. 知识检索2.2.3. 答案生成 3. RAG 与 Long Context3.1. Long Context3.2. RAG 与Lon…

你为什么选择程序员这个职业?

注意&#xff0c;今天的这篇文章&#xff0c;我只是对程序员这份工作所需要面对的问题挑选一些有意思的话题讲讲我的理解&#xff0c;并不是对程序员的职业规划进行分享。本文分为入行前和入行后两个部分&#xff0c;分别聊聊。 入行前 所谓入行前&#xff0c;其实就是指还是学…

力扣面试150: O(1) 时间插入、删除和获取随机元素 HashMap结合数组

Problem: 380. O(1) 时间插入、删除和获取随机元素 文章目录 思路复杂度Code 思路 &#x1f469;‍&#x1f3eb; 三叶题解 复杂度 时间复杂度: O ( 1 ) O(1) O(1) 空间复杂度: O ( n ) O(n) O(n) Code class RandomizedSet {static int[] nums new int[200_010];//存…

RabbitMQ小记

参考书籍&#xff1a;朱忠华的《RabbitMQ实战指南》 一、基础概念 1.Exchange 1.1 创建方法的参数&#xff0c;exchangeDeclare() exchange&#xff1a;交换器的名称type&#xff1a;交换器的类型durable&#xff1a;是否持久化&#xff0c;true代表持久化。&#xff08;持…

iperf图形化打流工具JPerf2.0使用教程(1):相关设置介绍

0 前言 iperf是一个很常用的网络性能测试工具&#xff0c;经常来进行打流测试&#xff0c;用来查看丢包率、抖动时间等网络通信情况&#xff0c;但它是使用命令行操作的&#xff0c;使用起来不太友好。本文介绍基于iperf的图形化操作工具JPerf2.0。 1 JPerf2.0的客户端和服务…

蓝桥杯速成5-AD/DA模数转换

一、原理图 上图可知该芯片使用的是iic时序&#xff0c;而不是51单片机的xpt2046时序&#xff0c;iic我们都很熟悉了吧 并且大赛还提供了我们iic底层驱动代码 左上角有AIN0-4四个转换输入通道&#xff0c;和AOUT一个输出通道&#xff0c;由控制字节选择 地址字节&#xff1a;0x…

Stable Diffusion本地部署教程

Stable Diffusion本地部署的步骤一般包括准备环境、下载Stable Diffusion模型和依赖库、配置运行参数等。下面是一个通用的教程&#xff0c;用以在计算机上本地部署Stable Diffusion。 准备环境 1. 确保硬件满足最低要求&#xff1a; - 一块NVIDIA GPU&#xff0c;至少4GB…

LLMOps快速入门,轻松开发部署大语言模型

大家好&#xff0c;如今我们能够与ChatGPT进行轻松互动&#xff1a;只需输入提示&#xff0c;按下回车&#xff0c;就能迅速得到回应。然而&#xff0c;这个无缝互动的底层&#xff0c;是一系列复杂而有序的自动执行步骤&#xff0c;即大型语言模型运营&#xff08;LLMOps&…

【数据分析面试】11. 计算账户关闭率(SQL:评估不同查询方法的性能效率)

题目 给定一个账户状态表&#xff0c;编写一个查询以获取在2019年12月31日活跃并在2020年1月1日关闭的账户所占的百分比&#xff0c;以及在2019年12月31日活跃的总账户数。每个账户只有一条每日记录&#xff0c;显示其在当天结束时的状态。 注意&#xff1a;将结果四舍五入到…

在同一个局域网如何共享打印机和文件

1.在连接了打印机的主机上设置 1.1启用windows共享 打开网络与共享中心&#xff0c;点击“更改高级共享设置” 选择&#xff1a; “启用网络发现”“启用文件和打印机共享”“启用共享以便可以访问网络的用户可以读取和写入公用文件夹中的文件” 打开控制面板&#xff0c;选…

STM32-02基于HAL库(CubeMX+MDK+Proteus)GPIO输出案例(LED流水灯)

文章目录 一、功能需求分析二、Proteus绘制电路原理图三、STMCubeMX 配置引脚及模式&#xff0c;生成代码四、MDK打开生成项目&#xff0c;编写HAL库的GPIO输出代码五、运行仿真程序&#xff0c;调试代码 一、功能需求分析 在完成开发环境搭建之后&#xff0c;开始使用STM32GP…

python标准数据类型--列表常用方法

在Python中&#xff0c;列表&#xff08;List&#xff09;是一种非常常用的数据类型&#xff0c;用于存储一组有序的元素。Python提供了许多内置方法来操作列表&#xff0c;使得对列表的处理变得非常灵活和便捷。在本篇博客中&#xff0c;我们将介绍一些常用的列表方法&#xf…