6.2 数据库

news/发布时间2024/5/15 1:13:03

本节介绍Android的数据库存储方式--SQLite的使用方法,包括:SQLite用到了哪些SQL语法,如何使用数据库管理操纵SQLitem,如何使用数据库帮助器简化数据库操作,以及如何利用SQLite改进登录页面的记住密码功能。

6.2.1  SQL的基本语法

SQL本质上是一种编程语言,它的学名叫做“结构化查询语言”(全称为Structured Query Language,简称SQL)。不过SQL语言并非通用的编程语言,它专用于数据库的访问和处理,更像是一种操作命令,所以常说SQL语句而不说SQL代码。标准的SQL语句分为3类:数据定义,数据操纵和数据控制。但不同的数据库往往有自己的实现。

SQLite是一种小巧的嵌入式数据库,使用方便,开发简单。如同MYSQL,Oracle那样,SQLite也采用SQL语句管理数据,由于它属于轻型数据库,不涉及复杂的数据控制操作,因此App开发只用到数据定义和数据操纵两类SQL语句。此外,SQLite的SQL语法与通用的SQL语法略有不同,接下来介绍的两类SQL语法全部基于SQLite。

    1.  数据定义语言

    数据定义语言(全称Data Definition Language,简称DDL)描述了怎样变更数据实体的框架结构。就SQLite而言,DDL语言主要包括3种操作:创建表格,删除表格,修改表结构,分别说明如下。

    (1)创建表格

    表格的创建动作由create命令完成,格式为“CREATE TABLE IF NOT EXISTS 表格名称(以逗号分隔的名字段定义);”。以用户信息表为例,它的建表语句如下:

CREATE TABLE IF NOT EXISTS user_info(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,name VARCHAR NOT NULL, age INTEGER NOT NULL,height LONG NOT NULL, weight FLOAT NOT NULL,married INTEGER NOT NULL, update_time VARCHAR NOT NULL);

上面的SQL语法与其他数据库的SQL语法有所出入,相关的注意点说明如下:

①  SQL语句不区分大小写,无论是create与table这类关键词,还是表格名称,字段名称都不区分大小写。唯一区分大小写的是被单引号括起来的字符串值。

②  为避免重复建表,应加上IF NOT EXISTS关键词,例如CREATE TABLE IF NOT EXISTS 表格名称 ▪▪▪▪

③  SQLite支持整型INTEGER,长整型LONG,字符串VARCHAR,浮点型FLOAT,但不支持布尔类型。布尔类型的数据要使用整型保存,如果直接保存布尔数据,在入库时SQLite会自动将它转为0或1,其中0表示false,1表示true。

④  建表时需要唯一标识字段,它的字段名为_id。创建新表都要加上该字段定义,例如_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL。

    (2)删除表格

    表格的删除动作由drop命令完成,格式为“DROP TABLE IF EXISTS 表格名称;”。下面是删除用户信息的SQL语句例子:

DROP TABLE IF EXISTS user_info;

    (3)修改表结构

    表格的修改动作由alter命令完成,格式为“ALTER TABLE 表格名称 修改操作;”。不过SQLite只支持增加字段,不支持修改字段,也不支持删除字段。对于字段增加操作,需要在alter之后补充add命令,具体格式如“ALTER TABLE 表格名称 ADD COLUMN 字段名称 字段类型;”。下面是给用户信息表增加手机号字段的SQL语句例子:

ALTER_TABLE user_info ADD COLUMN phone VARCHAR;
2.  数据操纵语言

数据操纵语言(全称Data Manipulation Language,简称DML)描述了怎样处理数据实体的内部记录。表格记录的操作类型包括添加,删除,修改,查询4类,分别说明如下:

(1)添加记录

    记录的添加动作由insert命令完成,格式为“INSERT INTO 表格名称(以逗号分隔的字段名列表)VALUES(以逗号分隔的字段值列表);”。下面是往用户信息表插入一条记录的SQL语句例子:

INSERT INTO user_info (name,age,height,weight,married,update_time)
VALUES ('张三',20,170,50,0,'20200504');

(2)删除记录

    记录的删除动作由delete命令完成,格式为“DELETE FROM 表格名称 WHERE 查询条件;”,其中查询条件的表达式形如“字段名=字段值”,多个字段的条件交集通过“AND”连接,条件并集通过“OR”连接。下面是从用户信息表删除指定记录的SQL语句例子:

DELETE FROM user_info WHERE name='张三';

(3)修改记录

    记录的修改动作由update命令完成,格式为“UPDATE 表格名称 SET 字段名=字段值 WHERE 查询条件;”。下面是对用户信息表更新指定记录的SQL语句例子:

UPDATE user_info SET married=1 WHERE name='张三';

(4)查询记录

    记录的查询动作由select命令完成,格式为“SELECT 以逗号分隔的字段名列表 FROM 表格名称 WHERE 查询条件;”。如果字段名列表填星号(*),则表示查询该表的所有字段。下面是从用户信息表查询指定记录的SQL语句例子:

SELECT*FROM user_info ORDER BY age ASC;

6.2.2  数据库管理器  SQLiteDatabase

    SQL语句毕竟只是SQL命令,若要在Java代码中操纵SQLite,还需专门的工具类。SQLiteDatabase便是Android提供的SQLite数据库管理器,开发者可以在活动页面代码中调用openOrCreateDatabase方法获得数据库实例,参考代码如下:

            SQLiteDatabase db = openOrCreateDatabase(mDatabaseName, Context.MODE_PRIVATE,null);String desc = String.format("数据库%s创建%s", db.getPath(), (db!=null)?"成功":"失败");tv_database.setText(desc);

 完整代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".DatabaseActivity"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><Buttonandroid:id="@+id/btn_database_create"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="创建数据库"android:textColor="@color/black"android:textSize="17sp" /><Buttonandroid:id="@+id/btn_database_delete"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="删除数据库"android:textColor="@color/black"android:textSize="17sp" /></LinearLayout><TextViewandroid:id="@+id/tv_database"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="5dp"android:textColor="@color/black"android:textSize="17sp" /></LinearLayout>
package com.example.datastorage;import androidx.appcompat.app.AppCompatActivity;import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;public class DatabaseActivity extends AppCompatActivity implements View.OnClickListener {private TextView tv_database; // 声明一个文本视图对象private String mDatabaseName;// 包含完整路径的数据库名称@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_database);tv_database=findViewById(R.id.tv_database);findViewById(R.id.btn_database_create).setOnClickListener(this);findViewById(R.id.btn_database_delete).setOnClickListener(this);// 生成一个测试数据库的完整路径mDatabaseName=getFilesDir()+"/test.db";}@Overridepublic void onClick(View view) {if (view.getId()==R.id.btn_database_create){// 创建或打开数据库。数据库如果不存在就创建它,如果存在就打开它SQLiteDatabase db = openOrCreateDatabase(mDatabaseName, Context.MODE_PRIVATE,null);String desc = String.format("数据库%s创建%s", db.getPath(), (db!=null)?"成功":"失败");tv_database.setText(desc);} else if (view.getId()==R.id.btn_database_delete) {boolean result = deleteDatabase(mDatabaseName);// 删除数据库String desc = String.format("数据库%s删除%s", mDatabaseName, result?"成功":"失败");tv_database.setText(desc);}}
}

    首次运行测试App,调用openOrCreateDatabase方法会自动创建数据库,并返回该数据库的管理器实例,创建结果如图所示。

 

    获得数据库实例之后,就能对该数据库开展各项操作了。数据库管理器SQLiteDatabase提供了若干操作数据表的API,常用的方法有3类,列举如下:

    1.  管理类,用于数据库层面的操作

    ●  openDatabase:打开指定路径的数据库。

    ●  isOpen:判断数据库是否已打开。

    ●  close:关闭数据库。

    ●  getVersion:获取数据库的版本号。

    ●  setVersion:设置数据库的版本号。

    2.  事务类,用于事务层面的操作

    ●  beginTransaction:开始事务。

    ●  setTransactionSuccessful:设置事务的成功标志。

    ●  endTransaction:结束事务。执行本方法时,系统会判断之前是否调用了                                        setTransactionSuccessful方法,如果之前已调用该方法就提交事务,如果没有调用该方法就          回滚事务。

    3.  数据处理类,用于数据表层面的操作

    ●  execSQL:执行拼接好的SQL控制语句。一般用于建表,删表,变更表结构。

    ●  delete:删除符合条件的记录。

    ●  update:更新符合条件的记录信息。

    ●  insert:插入一条记录。

    ●  query:执行查询操作,并返回结果集的游标。

    ●  rawQuery:执行拼接好的SQL查询语句,并返回结果集的游标。

    在实际开发中,经常用到的是查询语句,建议写好查询操作的select语句,再调用rawQuery方法执行查询语句。

6.2.3  数据库帮助器  SQLiteOpenHelper

    由于SQLiteDatabase存在局限性,一不小心就会重复打开数据库,处理数据库的升级也不方便,因此Android提供了数据库帮助器SQLiteOpenHelper,帮助开发者合理使用SQLite。

    SQLiteOpenHelper的具体使用步骤如下:

01  新建一个继承SQLiteOpenHelper的数据库操作类,按提示重写onCreate和onUpgrade两个方          法。其中,onCreate方法只在第一次打开数据库时执行,在此可以创建表结构;而onUpgrade        方法在数据库版本升高时执行,在此可以根据新旧版本号变更表结构。

02  为保证数据库的安全使用,需要封装几个必要方法,包括获取单例对象,打开数据库连接,关        闭数据库连接,说明如下:

      ●  获取单例对象,确保在App运行过程中数据库只会打开一次,避免重复打开引起错误。

      ●  打开数据库连接:SQLite有锁机制,即读锁和写锁的处理,故而数据库连接也分两种,读              连接可调用getReadableDatabase方法获得,写连接可调用getWritableDatabase方法获得。

      ●  关闭数据库连接,数据库操作完毕,调用数据库实例的close方法关闭连接。

03  提供对表记录增加,删除,修改,查询的操作方法。

    能被SQLite直接使用的数据结构是ContentValues类,它类似于映射Map,也提供了put和get方法存取键值对。区别之处在于:ContentValues的键只能是字符串,不能是其他类型。ContentValues主要用于增加记录和更新记录,对应数据库的insert和update方法。

    记录的查询操作用到了游标类Cursor,调用query和rawQuery方法返回的都是Cursor对象,若要获取全部的查询结果,则需要根据游标的指示一条一条遍历结果集合。Cursor的常用方法可分为3类,说明如下:

    1.  游标控制类方法,用于指定游标的状态

    ●  close:关闭游标。

    ●  isClosed:判断游标是否关闭。

    ●  isFirst:判断游标是否在开头。

    ●  isLast:判断游标是否在末尾。

    2.  游标移动类方法,把游标移动到指定位置

    ●  moveToFirst:移动游标到开头。

    ●  moveToLast:移动游标到末尾。

    ●  moveToNext:移动游标到下一条记录。

    ●  moveToPrevious:移动游标到上一条记录。

    ●  move:往后移动游标若干条记录。

    ●  moveToPosition:移动游标到指定位置的记录。

    3.  获取记录类方法,可获取记录的数量,类型以及取值

    ●  getCount:获取结果记录的数量。

    ●  getInt:获取指定字段的整型值。

    ●  getLong:获取指定字段的长整数型值。

    ●  getFloat:获取指定字段的浮点数值。

    ●  getString:获取指定字段的字符串值。

    ●  getType:获取指定字段的字段类型。

    鉴于数据库操作的特殊性,不方便单独演示某个功能,接下来从创建数据库开始介绍,完整演示一下数据库的读写操作。用户注册信息的演示页面包括两个,分别是记录保存页面和记录读取页面,其中记录保存页面通过insert方法向数据库添加用户信息,完整代码如下:

package com.example.datastorage;public class UserInfo {public long rowid; // 行号public int xuhao; // 序号public String name; // 姓名public int age; // 年龄public long height; // 身高public float weight; // 体重public boolean married; // 婚否public String update_time; // 更新时间public String phone; // 手机号public String password; // 密码public UserInfo() {rowid = 0L;xuhao = 0;name = "";age = 0;height = 0L;weight = 0.0f;married = false;update_time = "";phone = "";password = "";}
}
package com.example.datastorage;import static android.provider.SyncStateContract.Helpers.update;
import static androidx.core.content.ContentResolverCompat.query;import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;import java.util.ArrayList;
import java.util.List;public class UserDBHelper extends SQLiteOpenHelper {private static final String TAG = "UserDBHelper";private static final String DB_NAME = "user.db"; // 数据库的名称private static final int DB_VERSION = 1; // 数据库的版本号private static UserDBHelper mHelper = null; // 数据库帮助器的实例private SQLiteDatabase mDB = null; // 数据库的实例public static final String TABLE_NAME = "user_info"; // 表的名称private UserDBHelper(Context context) {super(context, DB_NAME, null, DB_VERSION);}private UserDBHelper(Context context, int version) {super(context, DB_NAME, null, version);}// 利用单例模式获取数据库帮助器的唯一实例public static UserDBHelper getInstance(Context context, int version) {if (version > 0 && mHelper == null) {mHelper = new UserDBHelper(context, version);} else if (mHelper == null) {mHelper = new UserDBHelper(context);}return mHelper;}// 打开数据库的读连接public SQLiteDatabase openReadLink() {if (mDB == null || !mDB.isOpen()) {mDB = mHelper.getReadableDatabase();}return mDB;}// 打开数据库的写连接public SQLiteDatabase openWriteLink() {if (mDB == null || !mDB.isOpen()) {mDB = mHelper.getWritableDatabase();}return mDB;}// 关闭数据库连接public void closeLink() {if (mDB != null && mDB.isOpen()) {mDB.close();mDB = null;}}// 创建数据库,执行建表语句@Overridepublic void onCreate(SQLiteDatabase sqLiteDatabase) {Log.d(TAG, "onCreate");String drop_sql = "DROP TABLE IF EXISTS " + TABLE_NAME + ";";Log.d(TAG, "drop_sql:" + drop_sql);sqLiteDatabase.execSQL(drop_sql);String create_sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("+ "_id INTEGER PRIMARY KEY  AUTOINCREMENT NOT NULL,"+ "name VARCHAR NOT NULL," + "age INTEGER NOT NULL,"+ "height INTEGER NOT NULL," + "weight FLOAT NOT NULL,"+ "married INTEGER NOT NULL," + "update_time VARCHAR NOT NULL"//演示数据库升级时要先把下面这行注释+ ",phone VARCHAR" + ",password VARCHAR"+ ");";Log.d(TAG, "create_sql:" + create_sql);sqLiteDatabase.execSQL(create_sql); // 执行完整的SQL语句}// 升级数据库,执行表结构变更语句@Overridepublic void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {Log.d(TAG, "onUpgrade oldVersion=" + i + ", newVersion=" + i1);if (i1 > 1) {//Android的ALTER命令不支持一次添加多列,只能分多次添加String alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "phone VARCHAR;";Log.d(TAG, "alter_sql:" + alter_sql);sqLiteDatabase.execSQL(alter_sql);alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "password VARCHAR;";Log.d(TAG, "alter_sql:" + alter_sql);sqLiteDatabase.execSQL(alter_sql); // 执行完整的SQL语句}}// 根据指定条件删除表记录public int delete(String condition) {// 执行删除记录动作,该语句返回删除记录的数目return mDB.delete(TABLE_NAME, condition, null);}// 删除该表的所有记录public int deleteAll() {// 执行删除记录动作,该语句返回删除记录的数目return mDB.delete(TABLE_NAME, "1=1", null);}// 往该表添加一条记录public long insert(UserInfo info) {List<UserInfo> infoList = new ArrayList<UserInfo>();infoList.add(info);return insert(infoList);}// 往该表添加多条记录public long insert(List<UserInfo> infoList) {long result = -1;for (int i = 0; i < infoList.size(); i++) {UserInfo info = infoList.get(i);List<UserInfo> tempList = new ArrayList<UserInfo>();// 如果存在同名记录,则更新记录// 注意条件语句的等号后面要用单引号括起来if (info.name != null && info.name.length() > 0) {String condition = String.format("name='%s'", info.name);tempList = query(condition);if (tempList.size() > 0) {update(info, condition);result = tempList.get(0).rowid;continue;}}// 如果存在同样的手机号码,则更新记录if (info.phone != null && info.phone.length() > 0) {String condition = String.format("phone='%s'", info.phone);tempList = query(condition);if (tempList.size() > 0) {update(info, condition);result = tempList.get(0).rowid;continue;}}// 不存在唯一性重复的记录,则插入新记录ContentValues cv = new ContentValues();cv.put("name", info.name);cv.put("age", info.age);cv.put("height", info.height);cv.put("weight", info.weight);cv.put("married", info.married);cv.put("update_time", info.update_time);cv.put("phone", info.phone);cv.put("password", info.password);// 执行插入记录动作,该语句返回插入记录的行号result = mDB.insert(TABLE_NAME, "", cv);if (result == -1) { // 添加成功则返回行号,添加失败则返回-1return result;}}return result;}// 根据条件更新指定的表记录public int update(UserInfo info, String condition) {ContentValues cv = new ContentValues();cv.put("name", info.name);cv.put("age", info.age);cv.put("height", info.height);cv.put("weight", info.weight);cv.put("married", info.married);cv.put("update_time", info.update_time);cv.put("phone", info.phone);cv.put("password", info.password);// 执行更新记录动作,该语句返回更新的记录数量return mDB.update(TABLE_NAME, cv, condition, null);}public int update(UserInfo info) {// 执行更新记录动作,该语句返回更新的记录数量return update(info, "rowid=" + info.rowid);}// 根据指定条件查询记录,并返回结果数据列表public List<UserInfo> query(String condition) {String sql = String.format("select rowid,_id,name,age,height," +"weight,married,update_time,phone,password " +"from %s where %s;", TABLE_NAME, condition);Log.d(TAG, "query sql: " + sql);List<UserInfo> infoList = new ArrayList<UserInfo>();// 执行记录查询动作,该语句返回结果集的游标Cursor cursor = mDB.rawQuery(sql, null);// 循环取出游标指向的每条记录while (cursor.moveToNext()) {UserInfo info = new UserInfo();info.rowid = cursor.getLong(0); // 取出长整型数info.xuhao = cursor.getInt(1); // 取出整型数info.name = cursor.getString(2); // 取出字符串info.age = cursor.getInt(3); // 取出整型数info.height = cursor.getLong(4); // 取出长整型数info.weight = cursor.getFloat(5); // 取出浮点数//SQLite没有布尔型,用0表示false,用1表示trueinfo.married = (cursor.getInt(6) == 0) ? false : true;info.update_time = cursor.getString(7); // 取出字符串info.phone = cursor.getString(8); // 取出字符串info.password = cursor.getString(9); // 取出字符串infoList.add(info);}cursor.close(); // 查询完毕,关闭数据库游标return infoList;}// 根据手机号码查询指定记录public UserInfo queryByPhone(String phone) {UserInfo info = null;List<UserInfo> infoList = query(String.format("phone='%s'", phone));if (infoList.size() > 0) { // 存在该号码的登录信息info = infoList.get(0);}return info;}}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="5dp"tools:context=".SQLiteWriteActivity"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="56dp"><TextViewandroid:id="@+id/tv_name"android:layout_width="wrap_content"android:layout_height="match_parent"android:text="姓名:"android:gravity="center"android:textSize="17sp"/><EditTextandroid:id="@+id/et_name"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginBottom="3dp"android:layout_marginTop="3dp"android:layout_toRightOf="@+id/tv_name"android:background="@drawable/editext_selector"android:gravity="left|center"android:hint="请输入姓名"android:inputType="text"android:maxLength="12"android:textColor="@color/black"android:textSize="17sp" /></RelativeLayout><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="40dp" ><TextViewandroid:id="@+id/tv_age"android:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:text="年龄:"android:textColor="@color/black"android:textSize="17sp" /><EditTextandroid:id="@+id/et_age"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginBottom="3dp"android:layout_marginTop="3dp"android:layout_toRightOf="@+id/tv_age"android:background="@drawable/editext_selector"android:gravity="left|center"android:hint="请输入年龄"android:inputType="number"android:maxLength="2"android:textColor="@color/black"android:textSize="17sp" /></RelativeLayout><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="40dp" ><TextViewandroid:id="@+id/tv_height"android:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:text="身高:"android:textColor="@color/black"android:textSize="17sp" /><EditTextandroid:id="@+id/et_height"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginBottom="3dp"android:layout_marginTop="3dp"android:layout_toRightOf="@+id/tv_height"android:background="@drawable/editext_selector"android:gravity="left|center"android:hint="请输入身高"android:inputType="number"android:maxLength="3"android:textColor="@color/black"android:textSize="17sp" /></RelativeLayout><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="40dp" ><TextViewandroid:id="@+id/tv_weight"android:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:text="体重:"android:textColor="@color/black"android:textSize="17sp" /><EditTextandroid:id="@+id/et_weight"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginBottom="3dp"android:layout_marginTop="3dp"android:layout_toRightOf="@+id/tv_weight"android:background="@drawable/editext_selector"android:gravity="left|center"android:hint="请输入体重"android:inputType="numberDecimal"android:maxLength="5"android:textColor="@color/black"android:textSize="17sp" /></RelativeLayout><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="40dp" ><CheckBoxandroid:id="@+id/ck_married"android:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:checked="false"android:text="已婚"android:textColor="@color/black"android:textSize="17sp" /></RelativeLayout><Buttonandroid:id="@+id/btn_save"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="保存到数据库"android:textColor="@color/black"android:textSize="17sp" /><Buttonandroid:id="@+id/btn_jump"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="跳转到数据库"android:textColor="@color/black"android:textSize="17sp" />
</LinearLayout>
package com.example.datastorage;import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Toast;public class SQLiteWriteActivity extends AppCompatActivity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {private UserDBHelper mHelper; // 声明一个用户数据库帮助器的对象private EditText et_name; // 声明一个编辑框对象private EditText et_age; // 声明一个编辑框对象private EditText et_height; // 声明一个编辑框对象private EditText et_weight; // 声明一个编辑框对象private boolean isMarried = false;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_sqlite_write);et_name = findViewById(R.id.et_name);et_age = findViewById(R.id.et_age);et_height = findViewById(R.id.et_height);et_weight = findViewById(R.id.et_weight);CheckBox ck_married = findViewById(R.id.ck_married);ck_married.setOnCheckedChangeListener(this);findViewById(R.id.btn_save).setOnClickListener(this);findViewById(R.id.btn_jump).setOnClickListener(this);}@Overrideprotected void onStart() {super.onStart();// 获得数据库帮助器的实例mHelper = UserDBHelper.getInstance(this,1);mHelper.openWriteLink();// 打开数据库帮助器的写连接}@Overrideprotected void onStop() {super.onStop();mHelper.closeLink();// 关闭数据库连接}@Overridepublic void onClick(View view) {if (view.getId()==R.id.btn_save){String name = et_name.getText().toString();String age = et_age.getText().toString();String height = et_height.getText().toString();String weight = et_weight.getText().toString();if (TextUtils.isEmpty(name)) {Toast.makeText(this, "请先填写姓名",Toast.LENGTH_LONG).show();return;} else if (TextUtils.isEmpty(age)) {Toast.makeText(this, "请先填写年龄",Toast.LENGTH_LONG).show();return;} else if (TextUtils.isEmpty(height)) {Toast.makeText(this, "请先填写身高",Toast.LENGTH_LONG).show();return;} else if (TextUtils.isEmpty(weight)) {Toast.makeText(this, "请先填写体重",Toast.LENGTH_LONG).show();return;}// 以下声明一个用户信息对象,并填写它的各字段值UserInfo info = new UserInfo();info.name = name;info.age = Integer.parseInt(age);info.weight = Float.parseFloat(weight);info.married = isMarried;info.update_time = DateUtil.getNowDateTime("yyyy-MM-dd HH:mm:ss");mHelper.insert(info);// 执行数据库帮助器的插入操作Toast.makeText(this, "数据已写入SQLite数据库",Toast.LENGTH_LONG).show();} else if (view.getId()==R.id.btn_jump) {Intent intent = new Intent(this, SQLiteReadActivity.class);startActivity(intent);}}@Overridepublic void onCheckedChanged(CompoundButton compoundButton, boolean b) {isMarried = b;}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".SQLiteReadActivity"><Buttonandroid:id="@+id/btn_delete"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="删除所有记录"android:textSize="17sp"/><TextViewandroid:id="@+id/tv_sqlite"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="5dp"android:textColor="@color/black"android:textSize="17sp" /></LinearLayout>
package com.example.datastorage;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;import java.util.List;public class SQLiteReadActivity extends AppCompatActivity implements View.OnClickListener {private UserDBHelper mHelper; // 声明一个用户数据库帮助器的对象private TextView tv_sqlite; // 声明一个文本视图对象@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_sqlite_read);tv_sqlite = findViewById(R.id.tv_sqlite);findViewById(R.id.btn_delete).setOnClickListener(this);}@Overrideprotected void onStart() {super.onStart();// 获得数据库帮助器的实例mHelper = UserDBHelper.getInstance(this, 1);mHelper.openReadLink(); // 打开数据库帮助器的读连接readSQLite(); // 读取数据库中保存的所有用户记录}@Overrideprotected void onStop() {super.onStop();mHelper.closeLink(); // 关闭数据库连接}// 读取数据库中保存的所有用户记录private void readSQLite() {if (mHelper == null) {Toast.makeText(this, "数据库连接为空", Toast.LENGTH_SHORT).show();return;}// 执行数据库帮助器的查询操作List<UserInfo> userList = mHelper.query("1=1");String desc = String.format("数据库查询到%d条记录,详情如下:", userList.size());for (int i = 0; i < userList.size(); i++) {UserInfo info = userList.get(i);desc = String.format("%s\n第%d条记录信息如下:", desc, i + 1);desc = String.format("%s\n 姓名为%s", desc, info.name);desc = String.format("%s\n 年龄为%d", desc, info.age);desc = String.format("%s\n 身高为%d", desc, info.height);desc = String.format("%s\n 体重为%f", desc, info.weight);desc = String.format("%s\n 婚否为%b", desc, info.married);desc = String.format("%s\n 更新时间为%s", desc, info.update_time);}if (userList.size() <= 0) {desc = "数据库查询到的记录为空";}tv_sqlite.setText(desc);}@Overridepublic void onClick(View view) {if (view.getId() == R.id.btn_delete) {mHelper.closeLink(); // 关闭数据库连接mHelper.openWriteLink(); // 打开数据库帮助器的写连接mHelper.deleteAll(); // 删除所有记录mHelper.closeLink(); // 关闭数据库连接mHelper.openReadLink(); // 打开数据库帮助器的读连接readSQLite(); // 读取数据库中保存的所有用户记录Toast.makeText(this, "已删除所有记录", Toast.LENGTH_SHORT).show();}}
}

运行测试App,先打开记录保存页面,依次录入信息并将两个用户的注册信息保存至数据库,如图所示。

6.2.4  优化记住密码功能 

    在“6.1.2  实现记住密码功能”中,虽然使用共享参数实现了记住密码的功能,但是该方案只能记住一个用户的登录信息,并且手机号码跟密码没有对应关系,如果换个手机号码登录,前一个用户的登录信息就被覆盖了。真正的记住密码功能应当是这样的:先输入手机号码,然后根据手机号码匹配保存的密码,一个手机号码对应一个密码,从而实现具体手机号码的密码记忆功能。

    现在运用数据库技术分条存储各用户的登录信息,并支持根据手机号查找登录信息,从而同时记住多个手机号的密码。具体的改造主要有下列3点:

    (1)声明一个数据库的帮助器对象,然后再活动页面的onResume方法中打开数据库连接,在onPasue方法中关闭数据库连接,示例代码如下:

    private CheckBox ck_remember; // 声明一个复选框对象@Overrideprotected void onResume() {super.onResume();mHelper = UserDBHelper.getInstance(this, 1); // 获得用户数据库帮助器的实例mHelper.openWriteLink(); // 恢复页面,则打开数据库连接}@Overrideprotected void onPause() {super.onPause();mHelper.closeLink(); // 暂停页面,则关闭数据库连接}

    (2)登录成功时,如果用户勾选了“记住密码”复选框,就将手机号码及其密码保存至数据库。也就是在loginSuccess方法中增加如下代码:

        // 如果勾选了“记住密码”,则把手机号码和密码保存为数据库的用户表记录if (isRemember) {UserInfo info = new UserInfo(); // 创建一个用户信息对象info.phone = et_phone.getText().toString();info.password = et_password.getText().toString();info.update_time = DateUtil.getNowDateTime("yyyy-MM-dd HH:mm:ss");mHelper.insert(info); // 往用户数据库添加登录成功的用户信息}

    (3)再次打开登录页面,用户输入手机号后点击密码框时,App根据手机号到数据库查找登录信息,并将记录结果中的密码填入密码框。其中根据手机号码查找登录信息,要求在帮助器代码中添加以下方法,用于找到指定手机的登录密码:

public UserInfo queryByPhone(String phone){UserInfo info = null;List<UserInfo> infoList = query(String.format("phone='%s'",phone));if(infoList.size()>0){//存在该号码的登录信息info = infoList.get(0);}return info;
}

    此外,上面第3点的点击密码框触发查询操作,用到了编辑框的焦点变更事件,有关焦点变更监听器的详细用法参见第5章的“5.3.2  焦点变更监听器”。就本案例而言,光标切到密码框触发焦点变更事件,具体处理逻辑要求重写监听器的onFocusChange方法,重写后的方法代码如下:

    @Overridepublic void onFocusChange(View view, boolean b) {String phone = et_phone.getText().toString();// 判断是否是密码编辑框发生焦点变化if (view.getId() == R.id.et_password) {// 用户已输入手机号码,且密码框获得焦点if (phone.length() > 0 && b) {// 根据手机号码到数据库中查询用户记录UserInfo info = mHelper.queryByPhone(phone);if (info != null) {// 找到用户记录,则自动在密码框中填写该用户的密码et_password.setText(info.password);}}}}

    重新运行测试App,先打开登录页面,勾选“记住密码”复选框,并确保本次登录成功。然后再次进入登录页面,输入手机号码后光标还停留在手机框,如图所示。

    接着点击密码框,光标随之跳转到密码框,此时密码框自动填入了该号码对应的密码串,如图所示。由效果图可见,这次实现了真正意义上的记住密码功能。

完整代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="5dp"tools:context=".LoginSQLiteActivity"><RadioGroupandroid:id="@+id/rg_login"android:layout_width="match_parent"android:layout_height="50dp"android:orientation="horizontal" ><RadioButtonandroid:id="@+id/rb_password"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:checked="true"android:gravity="left|center"android:text="密码登录"android:textColor="@color/black"android:textSize="17sp" /><RadioButtonandroid:id="@+id/rb_verifycode"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:checked="false"android:gravity="left|center"android:text="验证码登录"android:textColor="@color/black"android:textSize="17sp" /></RadioGroup><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="50dp" ><TextViewandroid:id="@+id/tv_phone"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_alignParentLeft="true"android:gravity="center"android:text="手机号码:"android:textColor="@color/black"android:textSize="17sp" /><EditTextandroid:id="@+id/et_phone"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginBottom="5dp"android:layout_marginTop="5dp"android:layout_toRightOf="@+id/tv_phone"android:background="@drawable/editext_selector"android:gravity="left|center"android:hint="请输入手机号码"android:inputType="number"android:maxLength="11"android:textColor="@color/black"android:textSize="17sp" /></RelativeLayout><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="50dp" ><TextViewandroid:id="@+id/tv_password"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_alignParentLeft="true"android:gravity="center"android:text="登录密码:"android:textColor="@color/black"android:textSize="17sp" /><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_toRightOf="@+id/tv_password" ><EditTextandroid:id="@+id/et_password"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginBottom="5dp"android:layout_marginTop="5dp"android:background="@drawable/editext_selector"android:gravity="left|center"android:hint="请输入密码"android:inputType="numberPassword"android:maxLength="6"android:textColor="@color/black"android:textSize="17sp" /><Buttonandroid:id="@+id/btn_forget"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_alignParentRight="true"android:gravity="center"android:text="忘记密码"android:textColor="@color/black"android:textSize="17sp" /></RelativeLayout></RelativeLayout><CheckBoxandroid:id="@+id/ck_remember"android:layout_width="match_parent"android:layout_height="wrap_content"android:checked="false"android:padding="10dp"android:text="记住密码"android:textColor="@color/black"android:textSize="17sp" /><Buttonandroid:id="@+id/btn_login"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="登  录"android:textColor="@color/black"android:textSize="20sp" /></LinearLayout>
package com.example.datastorage;import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;import java.util.Random;public class LoginSQLiteActivity extends AppCompatActivity implements View.OnClickListener, View.OnFocusChangeListener {private RadioGroup rg_login; // 声明一个单选组对象private RadioButton rb_password; // 声明一个单选按钮对象private RadioButton rb_verifycode; // 声明一个单选按钮对象private EditText et_phone; // 声明一个编辑框对象private TextView tv_password; // 声明一个文本视图对象private EditText et_password; // 声明一个编辑框对象private Button btn_forget; // 声明一个按钮控件对象private CheckBox ck_remember; // 声明一个复选框对象private int mRequestCode = 0; // 跳转页面时的请求代码private boolean isRemember = false; // 是否记住密码private String mPassword = "111111"; // 默认密码private String mVerifyCode; // 验证码private UserDBHelper mHelper; // 声明一个用户数据库的帮助器对象@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login_sqlite);rg_login = findViewById(R.id.rg_login);rb_password = findViewById(R.id.rb_password);rb_verifycode = findViewById(R.id.rb_verifycode);et_phone = findViewById(R.id.et_phone);tv_password = findViewById(R.id.tv_password);et_password = findViewById(R.id.et_password);btn_forget = findViewById(R.id.btn_forget);ck_remember = findViewById(R.id.ck_remember);// 给rg_login设置单选监听器rg_login.setOnCheckedChangeListener(new RadioListener());// 给ck_remember设置勾选监听器ck_remember.setOnCheckedChangeListener((buttonView, isChecked) -> isRemember = isChecked);// 给et_phone添加文本变更监听器et_phone.addTextChangedListener(new HideTextWatcher(et_phone, 11));// 给et_password添加文本变更监听器et_password.addTextChangedListener(new HideTextWatcher(et_password, 6));btn_forget.setOnClickListener(this);findViewById(R.id.btn_login).setOnClickListener(this);// 给密码编辑框注册一个焦点变化监听器,一旦焦点发生变化,就触发监听器的onFocusChange方法et_password.setOnFocusChangeListener(this);}// 定义登录方式的单选监听器private class RadioListener implements RadioGroup.OnCheckedChangeListener {@Overridepublic void onCheckedChanged(RadioGroup group, int checkedId) {if (checkedId == R.id.rb_password) { // 选择了密码登录tv_password.setText("登录密码:");et_password.setHint("请输入密码");btn_forget.setText("忘记密码");ck_remember.setVisibility(View.VISIBLE);} else if (checkedId == R.id.rb_verifycode) { // 选择了验证码登录tv_password.setText(" 验证码:");et_password.setHint("请输入验证码");btn_forget.setText("获取验证码");ck_remember.setVisibility(View.GONE);}}}// 定义一个编辑框监听器,在输入文本达到指定长度时自动隐藏输入法private class HideTextWatcher implements TextWatcher {private EditText mView; // 声明一个编辑框对象private int mMaxLength; // 声明一个最大长度变量public HideTextWatcher(EditText v, int maxLength) {super();mView = v;mMaxLength = maxLength;}// 在编辑框的输入文本变化前触发public void beforeTextChanged(CharSequence s, int start, int count, int after) {}// 在编辑框的输入文本变化时触发public void onTextChanged(CharSequence s, int start, int before, int count) {}// 在编辑框的输入文本变化后触发public void afterTextChanged(Editable s) {String str = s.toString(); // 获得已输入的文本字符串// 输入文本达到11位(如手机号码),或者达到6位(如登录密码)时关闭输入法if ((str.length() == 11 && mMaxLength == 11)|| (str.length() == 6 && mMaxLength == 6)) {ViewUtil.hideOneInputMethod(LoginSQLiteActivity.this, mView); // 隐藏输入法软键盘}}}@Overridepublic void onClick(View v) {String phone = et_phone.getText().toString();if (v.getId() == R.id.btn_forget) { // 点击了“忘记密码”按钮if (phone.length() < 11) { // 手机号码不足11位Toast.makeText(this, "请输入正确的手机号", Toast.LENGTH_SHORT).show();return;}if (rb_password.isChecked()) { // 选择了密码方式校验,此时要跳到找回密码页面// 以下携带手机号码跳转到找回密码页面Intent intent = new Intent(this, LoginForgetActivity.class);intent.putExtra("phone", phone);startActivityForResult(intent, mRequestCode); // 携带意图返回上一个页面} else if (rb_verifycode.isChecked()) { // 选择了验证码方式校验,此时要生成六位随机数字验证码// 生成六位随机数字的验证码mVerifyCode = String.format("%06d", new Random().nextInt(999999));// 以下弹出提醒对话框,提示用户记住六位验证码数字AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setTitle("请记住验证码");builder.setMessage("手机号" + phone + ",本次验证码是" + mVerifyCode + ",请输入验证码");builder.setPositiveButton("好的", null);AlertDialog alert = builder.create();alert.show(); // 显示提醒对话框}} else if (v.getId() == R.id.btn_login) { // 点击了“登录”按钮if (phone.length() < 11) { // 手机号码不足11位Toast.makeText(this, "请输入正确的手机号", Toast.LENGTH_SHORT).show();return;}if (rb_password.isChecked()) { // 密码方式校验if (!et_password.getText().toString().equals(mPassword)) {Toast.makeText(this, "请输入正确的密码", Toast.LENGTH_SHORT).show();} else { // 密码校验通过loginSuccess(); // 提示用户登录成功}} else if (rb_verifycode.isChecked()) { // 验证码方式校验if (!et_password.getText().toString().equals(mVerifyCode)) {Toast.makeText(this, "请输入正确的验证码", Toast.LENGTH_SHORT).show();} else { // 验证码校验通过loginSuccess(); // 提示用户登录成功}}}}// 从下一个页面携带参数返回当前页面时触发@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == mRequestCode && data != null) {// 用户密码已改为新密码,故更新密码变量mPassword = data.getStringExtra("new_password");}}// 从修改密码页面返回登录页面,要清空密码的输入框@Overrideprotected void onRestart() {super.onRestart();et_password.setText("");}@Overrideprotected void onResume() {super.onResume();mHelper = UserDBHelper.getInstance(this, 1); // 获得用户数据库帮助器的实例mHelper.openWriteLink(); // 恢复页面,则打开数据库连接}// 校验通过,登录成功private void loginSuccess() {String desc = String.format("您的手机号码是%s,恭喜你通过登录验证,点击“确定”按钮返回上个页面",et_phone.getText().toString());// 以下弹出提醒对话框,提示用户登录成功AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setTitle("登录成功");builder.setMessage(desc);builder.setPositiveButton("确定返回", (dialog, which) -> finish());builder.setNegativeButton("我再看看", null);AlertDialog alert = builder.create();alert.show();// 如果勾选了“记住密码”,则把手机号码和密码保存为数据库的用户表记录if (isRemember) {UserInfo info = new UserInfo(); // 创建一个用户信息对象info.phone = et_phone.getText().toString();info.password = et_password.getText().toString();info.update_time = DateUtil.getNowDateTime("yyyy-MM-dd HH:mm:ss");mHelper.insert(info); // 往用户数据库添加登录成功的用户信息}}@Overrideprotected void onPause() {super.onPause();mHelper.closeLink(); // 暂停页面,则关闭数据库连接}// 焦点变更事件的处理方法,hasFocus表示当前控件是否获得焦点。// 为什么光标进入密码框事件不选onClick?因为要点两下才会触发onClick动作(第一下是切换焦点动作)@Overridepublic void onFocusChange(View view, boolean b) {String phone = et_phone.getText().toString();// 判断是否是密码编辑框发生焦点变化if (view.getId() == R.id.et_password) {// 用户已输入手机号码,且密码框获得焦点if (phone.length() > 0 && b) {// 根据手机号码到数据库中查询用户记录UserInfo info = mHelper.queryByPhone(phone);if (info != null) {// 找到用户记录,则自动在密码框中填写该用户的密码et_password.setText(info.password);}}}}
}

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

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

相关文章

【C++】类与对象—— 初始化列表 、static 静态成员、

类与对象 1 再谈构造函数1.1 构造函数体赋值1.2 初始化列表语法&#xff1a;建议&#xff1a;初始化顺序&#xff1a;注意&#xff1a; 1.3 explicit关键字 2 static 静态成员2.1 概念2.2 声明成员变量2.3 使用类的静态成员2.4 定义静态成员总结 Thanks♪(&#xff65;ω&#…

js设计模式:享元模式

作用: 当需要处理很多非常类似操作的时候,可以创建可以共享的对象,并暴露接口供其他对象调用 这个对象内包含这些操作的处理逻辑,可以优化性能 示例: const Ipad function(){const apps {}const useApp function(appName,appFun){if(apps[appName]){return apps[appName]…

Java script 检测手机端,检测UA, js unicode转码 附赠网站

目录 检测是不是手机短检测是不是从百度来的多个搜索引擎java script unicode 转码可以用来写一个标签 检测是不是手机短 如果是手机端则跳转百度 如果不是手机端则不跳转 if (/(Android|IOS|iPhone|iPad|iPod|WindowsPhone|webOS|BlackBerry)/i.test(navigator.userAgent)) …

红队评估四靶场

文章目录 环境搭建1.设置所需网卡2.更改win7设置3.DC设置4.web设置开启docker服务5.kali网段`渗透启动`1.确认对方靶机的IP地址2.端口探测3.web探测`2001端口``2002端口`Tomcat/8.5.19漏洞复现`2003端口`4.docker逃逸5.ssh密钥爆破`域渗透启动`1.提权2.隧道搭建各项配置文件内容…

【Python】OpenCV-图片添加水印处理

图片添加水印处理 1. 引言 图像处理中的水印添加是一种常见的操作&#xff0c;用于在图片上叠加一些信息或标识。本文将介绍如何使用OpenCV库在图片上添加水印&#xff0c;并通过详细的代码注释来解释每一步的操作。 2. 代码示例 以下是一个使用OpenCV库的简单代码示例&…

《TCP/IP详解 卷一》第5章 Internet协议

5.1 引言 TCP UDP ICMP IGMP协议都需要IP封装。 IPv4头部格式&#xff1a; 版本&#xff1a;IP协议版本&#xff0c;值为4。 IHL&#xff1a;头长度&#xff0c;最大值为15&#xff0c;即头部最长4*15字节&#xff0c;一般无IP选项时值为5&#xff0c;即IP头为20字节。 DSEC…

Java智慧工地云综合管理平台SaaS源码 助力工地实现精细化管理

目录 智慧工地系统介绍 1、可视化大屏 2、视频监控 3、Wi-Fi安全教育 4、环境监测 5、高支模监测 6、深基坑监测 7、智能水电监测 8、塔机升降安全监测 智慧工地系统功能模块 1、基础数据管理 2、考勤管理 3、安全隐患管理 4、视频监控 5、塔吊监控 6、升降机监…

c语言经典测试题1

1.题1 int x5,y7; void swap() { int z; zx; xy; yz; } int main() { int x3,y8; swap(); printf("%d,%d\n"&#xff0c;x, y); return 0; } A: 5,7 B: 7,5 C: 3,8 D: 8,3 大家思考一下选哪一个呢&#xff1f; 我们来分析一下&#xff1a;上述代码中我们创建了4…

2.23数据结构

单向循环链表 创建单向循环链表&#xff0c;创建节点 &#xff0c;头插&#xff0c;按位置插入&#xff0c;输出&#xff0c;尾删&#xff0c;按位置删除功能 //main.c #include "loop_list.h" int main() {loop_p Hcreate_head();insert_head(H,12);insert_head(…

Qt开发:MAC安装qt、qtcreate(配置桌面应用开发环境)

安装qt-creator brew install qt-creator安装qt brew install qt查看qt安装路径 brew info qtzhbbindembp ~ % brew info qt > qt: stable 6.6.1 (bottled), HEAD Cross-platform application and UI framework https://www.qt.io/ /opt/homebrew/Cellar/qt/6…

Clickhouse系列之连接工具连接、数据类型和数据库

基本操作 一、使用连接工具连接二、数据类型1、数字类型IntFloatDecimal 2、字符串类型StringFixedStringUUID 3、时间类型DateTimeDateTime64Date 4、复合类型ArrayEnum 5、特殊类型Nullable 三、数据库 一、使用连接工具连接 上一篇介绍了clickhouse的命令行登录&#xff0c…

vue-利用属性(v-if)控制表单(el-form-item)显示/隐藏

表单控制属性 v-if 示例&#xff1a; 通过switch组件作为开关&#xff0c;控制表单的显示与隐藏 <el-form-item label"创建数据集"><el-switch v-model"selectFormVisible"></el-switch></el-form-item><el-form-item label&…

【C语言】内存操作,内存函数篇---memcpy,memmove,memset和memcmp内存函数的使用和模拟实现【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为​【C语言】内存操作&#xff0c;内存函数篇---memcpy&#xff0c;memmove&#xff0c;memset和memcmp内存函数的使用和模拟实现【图文详解】&#xff0c;图文讲解四种内存函数&#xff0c;带大家更深刻理解C语言中内存函数的操作&…

C++ 二维差分 二维前缀和逆运算 差分矩阵

输入一个 n 行 m 列的整数矩阵&#xff0c;再输入 q 个操作&#xff0c;每个操作包含五个整数 x1,y1,x2,y2,c &#xff0c;其中 (x1,y1) 和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。 每个操作都要将选中的子矩阵中的每个元素的值加上 c 。 请你将进行完所有操作后的…

Qt应用-音乐播放器实例

本文讲解Qt音乐播放器应用实例。 实现主要功能 声音播放、暂停,拖动控制、声音大小调节; 播放列表控制; 歌词显示; 界面设计 pro文件中添加 # 播放媒体 QT += multimedia 头文件 #ifndef FRMMUSICPLAYER_H #define FRMMUSICPLAYER_H#include <QWidget> #include…

gnss尾矿库安全监测系统是什么

【TH-WY1】GNSS尾矿库安全监测系统是一种利用全球导航卫星系统&#xff08;GNSS&#xff09;技术对尾矿库进行安全监测的系统。尾矿库是矿山企业的重要设施之一&#xff0c;用于存放矿山开采过程中产生的尾矿。由于尾矿库具有高能势和复杂的地质环境&#xff0c;存在溃坝、滑坡…

电脑如何连接蓝牙音响?快速连接指南分享!

“我想将电脑连接蓝牙音响&#xff0c;但是不知道怎么操作&#xff0c;大家有什么比较好的连接方法可以分享吗&#xff1f;” 随着科技的发展&#xff0c;蓝牙技术已经成为我们日常生活中不缺的一部分。无论是手机、平板还是电脑&#xff0c;通过蓝牙技术&#xff0c;我们都能够…

CAN_相关的测试用例+测试方法+测试工具使用+输出测试报告

测试类型: 第一:通信测试 第二:间接网络管理测试 第三:AUTOSAR网络管理测试 第四:诊断协议栈Diva测试 第五:诊断协议补充测试 第六:Bootloader测试 第七:网…

24-k8s的附件组件-Metrics-server组件与hpa资源pod水平伸缩

一、概述 Metrics-Server组件目的&#xff1a;获取集群中pod、节点等负载信息&#xff1b; hpa资源目的&#xff1a;通过metrics-server获取的pod负载信息&#xff0c;自动伸缩创建pod&#xff1b; 参考链接&#xff1a; 资源指标管道 | Kubernetes https://github.com/kuberne…

免费搭建个人网盘

免费搭建一个属于个人的网盘。 服务端 详情请参考原网站的服务端下载和安装虚拟磁盘Fuse4Ui可以支持把网盘内容挂载成系统的分区&#xff1b; 挂载工具效果图&#xff1a;应用端应用端的下载 效果图
推荐文章