Android---Jetpack之Room

目录

应用实现

数据库升级

异常处理

Schema 文件

销毁和重建策略

 预填充数据库


Android 采用 SQLite 作为数据库存储,开源社区常见的 ORM(Object Relational Mapping)库有ORMLite、GreenDAO等。Room 和其它库一样,也是在 SQLite 上提供了一层封装。

 \bullet Entity:实体类,对应的是数据库的一张表结构,使用注解 @Entity 标记。相当于 JavaBean

\bullet Dao:包含访问数据库的一些列方法,使用注解 @Dao 标记。

\bullet DataBase:数据库持有者,作为与应用持久化相关数据的底层连接的主要接入点。使用注解 @Database 标记,另外需满足以下条件:定义的类必须是一个继承于 RoomDatabase 的抽象类,在注解中需要定义与数据库相关联的实体类列表。包含一个没有参数的抽象方法并且返回一个 Dao对象。

优化:当数据发生变化时,通过 LiveData 通知 View 层,实现数据自动更新。 

 Room + ViewModel + LiveData

应用实现

步骤1:使用 room,在build.gradle(app级)添加 room 依赖。

implementation 'androidx.room:room-runtime:2.3.0'
annotationProcessor 'androidx.room:room-compiler:2.3.0'

 注意:当你添加了 room 依赖后,可能出现如下错误

3 issues were found when checking AAR metadata:

原因是你的 room 版本过高或者 Gradle 版本过低,所以可以通过提升 Gradle 版本或者使用低版本的 room。 

步骤2:布局文件

avtivity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.11" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.22" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="205dp" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="添加"
        android:onClick="mInsert"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toStartOf="@+id/guideline4"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="mDelete"
        android:text="删除"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline4"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="修改"
        android:onClick="mUpdate"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintEnd_toStartOf="@+id/guideline4"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2" />

    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="清空"
        android:onClick="mClear"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline4"
        app:layout_constraintTop_toTopOf="@+id/guideline2" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="409dp"
        android:layout_height="584dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline3"
        app:layout_constraintVertical_bias="0.428" />
</androidx.constraintlayout.widget.ConstraintLayout>

item.xml(对学生信息的展示是用的 RecyclerView) 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="70dp">

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="19"
        android:textSize="20sp"
        app:layout_constraintBottom_toTopOf="@+id/guideline5"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline7"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Jack"
        android:textSize="24sp"
        app:layout_constraintBottom_toTopOf="@+id/guideline5"
        app:layout_constraintEnd_toStartOf="@+id/guideline7"
        app:layout_constraintStart_toStartOf="@+id/guideline6"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="1.0" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="102dp" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="300dp" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="1"
        android:textSize="20sp"
        app:layout_constraintBottom_toTopOf="@+id/guideline5"
        app:layout_constraintEnd_toStartOf="@+id/guideline6"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

步骤3:创建 Entity 实体类

Student.java

package com.example.room2;

import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;

@Entity(tableName = "student")
public class Student {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
    public int id;

    @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)
    public String name;

    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
    public int age;

//    @ColumnInfo(name = "sex", typeAffinity = ColumnInfo.INTEGER)
//    public int sex;

    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    /**
     *TODO room 只需要一个构造方法,当我们有多个的时候,打上 @Ignore 标签,Room 就不会管它了
     * 上面的构造方法给 Room 用,下面的构造方法就可以给我们自己用
     */

    @Ignore
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Ignore
    public Student(int id) {
        this.id = id;
    }
}

步骤4: Dao 类

StudentDao.java

package com.example.room2;

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;

import java.util.List;

@Dao
public interface StudentDao {
    @Insert
    void insertStudent(Student... students);

    @Delete
    void deleteStudent(Student... students);

    @Query("DELETE FROM student")
    void deleteAllStudent();

    @Update
    void updateStudent(Student... students);

    @Query("SELECT * FROM STUDENT")
    LiveData<List<Student>> getAllStudentsLive();

    @Query("SELECT * FROM student WHERE id = :id")
    List<Student> getStudentById(int id);
}

 步骤5:Database 能够返回一个 Dao 对象

package com.example.room2;

import android.content.Context;

import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;

/**
 * 通过 DataBase 我们可以拿到 Dao
 */
@Database(entities = {Student.class}, version = 1, exportSchema = false)
public abstract class MyDataBase extends RoomDatabase {

    private static final String DATABASE_NAME = "my_db.db";
    private static MyDataBase mInstance; // 单例

    //private MyDataBase(){}// Room 里不需要,有了反而会报错

    public static synchronized MyDataBase getInstance(Context context){
        if (mInstance == null) {
            //mInstance = new MyDataBase();// room 不这么写
            mInstance = Room.databaseBuilder(context.getApplicationContext(),
                            MyDataBase.class,
                            DATABASE_NAME)
                            .build();
        }
        return mInstance;
    }


    /**
     * 返回一个 Dao
     */
    public abstract StudentDao getStudentDao();

}

 步骤6:Repository 与 Room 打交道

studentRepository.java

package com.example.room2;

import android.content.Context;
import android.os.AsyncTask;

import androidx.lifecycle.LiveData;

import java.util.List;

/**
 * 持有 DAO 与 ROOM 打交道
 */
public class StudentRepository {
    private StudentDao studentDao;

    public StudentRepository(Context context) {
        MyDataBase dataBase = MyDataBase.getInstance(context);
        this.studentDao = dataBase.getStudentDao();
    }

    /**
     * 插入
     * @param students 可变参数
     */
    public void insertStudents(Student... students){
        new insertStudentsTask(studentDao).execute(students);
    }
    // 异步操作
    class insertStudentsTask extends AsyncTask<Student, Void, Void> {

        private StudentDao studentDao;

        public insertStudentsTask(StudentDao studentDao) {
            this.studentDao = studentDao;
        }

        @Override
        protected Void doInBackground(Student... students) {
            studentDao.insertStudent(students);
            return null;
        }
    }

    /**
     * 更新
     * @param students 可变参数
     */
    public void updateStudent(Student... students){
        new updateStudentsTask(studentDao).execute(students);
    }
    //异步操作
    class updateStudentsTask extends AsyncTask<Student, Void, Void>{
        private StudentDao studentDao;

        public updateStudentsTask(StudentDao studentDao) {
            this.studentDao = studentDao;
        }
        @Override
        protected Void doInBackground(Student... students) {
            studentDao.updateStudent(students);
            return null;
        }
    }

    /**
     * 删除
     * @param students 可变参数
     */
    public void deleteStudent(Student... students){
        new deleteStudentsTask(studentDao).execute(students);
    }
    //异步操作
    class deleteStudentsTask extends AsyncTask<Student, Void, Void>{
        private StudentDao studentDao;

        public deleteStudentsTask(StudentDao studentDao) {
            this.studentDao = studentDao;
        }
        @Override
        protected Void doInBackground(Student... students) {
            studentDao.deleteStudent(students);
            return null;
        }
    }

    /**
     * 删除所以数据
     */
    public void deleteAllStudents(){
        new deleteAllStudentsTask(studentDao).execute();
    }
    //异步操作
    class deleteAllStudentsTask extends AsyncTask<Void, Void, Void>{
        private StudentDao studentDao;

        public deleteAllStudentsTask(StudentDao studentDao) {
            this.studentDao = studentDao;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            studentDao.deleteAllStudent();
            return null;
        }
    }

    /**
     * 获取所以数据,
     * LiveData 的方式与 UI 界面进行数据绑定
     */
    public LiveData<List<Student>> getAllStudentsLive(){
        return studentDao.getAllStudentsLive();
    }
}

 步骤7:ViewModel,与 Repository 打交道

StudentViewModel.java

package com.example.room2;

import android.app.Application;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;

import java.util.List;

/**
 * 使用 AndroidViewModel 就可以有上下文
 */
public class StudentViewModel extends AndroidViewModel {

    private StudentRepository studentRepository;

    public StudentViewModel(@NonNull Application application) {
        super(application);
        studentRepository = new StudentRepository(application);
    }

    /**
     * 插入
     */
    public void insertStudents(Student... students){
        studentRepository.insertStudents(students);
    }
    /**
     * 更新
     */
    public void deleteStudent(Student... students){
        studentRepository.deleteStudent(students);
    }
    /**
     * 删除
     */
    public void deleteAllStudents(){
        studentRepository.deleteAllStudents();
    }
    /**
     * 删除所以
     */
    public void updateStudent(Student... students){
        studentRepository.updateStudent(students);
    }

    /**
     * 获取所以数据
     */
    public LiveData<List<Student>> getAllStudentsLive(){
        return studentRepository.getAllStudentsLive();
    }
}

步骤8: 给 RecyclerView 添加适配器

StudentRecyclerViewAdapter.java

package com.example.room2;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class StudentRecyclerViewAdapter extends RecyclerView.Adapter {

    List<Student> students;

    public StudentRecyclerViewAdapter(List<Student> students) {
        this.students = students;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View root = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
        return new MyViewHolder(root);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Student student = students.get(position);
        TextView tvId = holder.itemView.findViewById(R.id.textView);
        tvId.setText(String.valueOf(student.id));
        TextView tvName = holder.itemView.findViewById(R.id.textView2);
        tvName.setText(student.name);
        TextView tvAge = holder.itemView.findViewById(R.id.textView3);
        tvAge.setText(String.valueOf(student.age));
    }

    @Override
    public int getItemCount() {
        return students == null ? 0 : students.size();
    }

    static class MyViewHolder extends RecyclerView.ViewHolder{

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }

    public void setStudents(List<Student> students){
        this.students = students;
    }
}

步骤9:MainActivity.java 

package com.example.room2;

import android.os.Bundle;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    StudentViewModel viewModel;
    private StudentRecyclerViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        List<Student> students = new ArrayList<>();
        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new StudentRecyclerViewAdapter(students);
        recyclerView.setAdapter(adapter);

        viewModel = new ViewModelProvider(this,
                new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(StudentViewModel.class);
        //TODO 监听 LiveData 数据的改变 --> 更新 UI
        viewModel.getAllStudentsLive().observe(this, new Observer<List<Student>>() {
            @Override
            public void onChanged(List<Student> students) {
                adapter.setStudents(students);
                adapter.notifyDataSetChanged();
            }
        });
    }

    public void mInsert(View view) {
        Student s1 = new Student("Jack", 18);
        Student s2 = new Student("Rose", 20);
        viewModel.insertStudents(s1, s2);
    }


    public void mDelete(View view) {
        Student s1 = new Student(2);
        viewModel.deleteStudent(s1);
    }


    public void mUpdate(View view) {
        Student s1 = new Student(3, "Json", 20);
        viewModel.updateStudent(s1);
    }


    public void mClear(View view) {
        viewModel.deleteAllStudents();
    }
}

数据库升级

使用 Migration 升级数据库

问题:如果用户设备上数据库版本为1,而当前要安装的 App 数据库版本为3,怎么办?

Room 会先判断当前有没有直接从1到3的方案,如果有,就直接执行从1到3的升级方案,如果没有,那么 Room 会按照顺序先后执行 Migration(1,2)、Migration(2,3)以完成升级。

在MyDataBase 里添加如下代码:

    /**
     * 数据库升级
     */
    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE student ADD COLUMN sex INTEGER NOT NULL DEFAULT 1");
        }
    };

上面代码是在 student 表里添加了一个字段,所以我们的 Student.java 实体类也要添加这个属性

    @ColumnInfo(name = "sex", typeAffinity = ColumnInfo.INTEGER)
    public int sex;

最后,在 MyDataBase.java修改如下两个地方,即课完成版本的升级。

异常处理

假设我们将数据库版本升级到4,却没有为此写相应的 Migration,则会出现一个 IllegalStateException 异常,加入 fallbackToDestructiveMigration(),该方法在出现升级异常时,重建数据表,同时数据也会丢失

Schema 文件

Room 在每次数据库升级过程中,都会导出一个 Schema 文件,这是一个 json 格式的文件,其中包含了数据库的基本信息,有了该文件,开发者能清楚的知道数据库的历次变更情况,极大的方便了开发者排查问题。

销毁和重建策略

在 SQLite 中修改表结构比较麻烦,例如,我们想将 Student 表中 sex 字段类型从 INTEGER 修改为 TEXT,最好的方式是采用销毁与重建策略,大致分为以下步骤:

  \bullet 创建一张符合表结构要求的临时表 temp_student

  \bullet 将数据从旧表 student 复制到临时表 temp_student

  \bullet 删除旧表 student

  \bullet 将临时表 temp_student 重命名为 student

 

在 MyDataBase.java 里添加如下代码:

    /**
     * 修改一张表的字段
     */
    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            // 1. 创建一张临时表
            database.execSQL("CREATE TABLE temp_student (" +
                    "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                    "name TEXT,"+
                    "age INTEGER NOT NULL,"+
                    "sex TEXT DEFAULT 'M')");
            // 2. 复制数据到临时表
            database.execSQL("INSERT INTO temp_student (name, age, sex)" +
                    "SELECT name, age, sex FROM student");
            // 3. 删除旧表 student
            database.execSQL("DROP TABLE student");
            // 4. 将临时表 temp_student 重命名为 student
            database.execSQL("ALTER TABLE temp_student RENAME TO student");
        }
    };

 

预填充数据库

有时候我们希望应用自带一些数据供我们使用,我们可以将数据库文件放入 assets 目录一起打包发布,在用户首次打开 App 时,使用 createFromAsset() 和 createFromFile() 创建 Room 数据库。

 

完整demo

上面应用的实现是在 room2 model 里。

链接:https://pan.baidu.com/s/1_Dm1BHW_hq5V_8K12sjjLQ 
提取码:4jti

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一文懂交叉熵Cross-Entropy

本文翻译自https://naokishibuya.medium.com/demystifying-cross-entropy-e80e3ad54a8 交叉熵由交叉&#xff08;Cross&#xff09;和熵&#xff08;Entropy&#xff09;两部分组成&#xff0c;在机器学习中常常被定义为损失函数的目标。在二分类任务中&#xff0c;更有二分类交…

QT学习笔记(智能家居物联网项目实验2)

物联网项目综合测试 打开 4/01_smarthome/01_smarthome/01_smarthome.pro 项目&#xff0c;此项目为智能家居物联网 UI 界面控制端。 打开 4/01_smarthome/esp8266/esp8266.pro 项目&#xff0c;此项目设备端&#xff08;被控端&#xff09;。 打开上面两个项目如下。 项…

ToBeWritten之MOST协议、Flex Rat总线、车载以太网

也许每个人出生的时候都以为这世界都是为他一个人而存在的&#xff0c;当他发现自己错的时候&#xff0c;他便开始长大 少走了弯路&#xff0c;也就错过了风景&#xff0c;无论如何&#xff0c;感谢经历 转移发布平台通知&#xff1a;将不再在CSDN博客发布新文章&#xff0c;敬…

C/C++每日一练(20230402)

目录 1. 找最大数和最小数 ※ 2. 数组排序 ※ 3. 按要求完成数据的输入输出 ※ &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 标注 ※ 为入门基础题&#xff0c;今天什么好日子CSDN…

一个有完整业务连的淘宝API接口

支持的业务类型 1、卖家平台&#xff08;包括淘宝网&#xff0c;天猫等&#xff09;&#xff1a;搜索、店铺信息维护、交易订单处理、发货管理、数据查询与统计分析。 2、买家平台&#xff08;包括淘宝&#xff0c;天猫等&#xff09;&#xff1a;搜索&#xff0c;发布信息&a…

银行数字化转型导师坚鹏:金融科技如何赋能银行数字化转型

金融科技如何赋能银行数字化转型课程背景&#xff1a; 数字化背景下&#xff0c;很多银行存在以下问题&#xff1a; 不清楚5G如何赋能银行数字化转型&#xff1f; 不清楚金融科技如何赋能银行数字化转型&#xff1f; 不了解银行数字化转型标杆成功案例&#xff1f; 课程特色…

旅游市场迎来“开门红”,VR云游带来全新体验

旅游业是一个充满活力和吸引力的行业&#xff0c;可以促进当地经济发展和提高生活水平。在清明时节&#xff0c;春暖花开&#xff0c;各地旅游市场正在回暖&#xff0c;而各大景区也纷纷推出了优惠措施&#xff0c;吸引大批的游客前来游玩&#xff0c;旅游市场迎来了“开门红”…

ServletAPI的使用

目录 一、HttpServlet 1.1 HttpServlet的核心方法 1.2 Servlet的生命周期 1.3 代码示例&#xff1a;通过postman来发送请求 1.4 代码示例&#xff1a;通过ajax来发送请求 二、HttpServletRequest 2.1 代码示例&#xff1a;打印请求信息&#xff1a; 2.2 代码示例&#…

强化学习——初探强化学习

本文引自&#xff1a;《 动手学强化学习 》 第 1 章 初探强化学习 1.1 简介 亲爱的读者&#xff0c;欢迎来到强化学习的世界。初探强化学习&#xff0c;你是否充满了好奇和期待呢&#xff1f;我们想说&#xff0c;首先感谢你的选择&#xff0c;学习本书不仅能够帮助你理解强…

COI实验室技能:python控制相机的方法——采集、处理、显示、实时

COI实验室技能&#xff1a;python控制相机的方法——采集、处理、显示、实时本文介绍如何利用python控制办公摄像头、工业相机和科研相机。将数据采集和处理统一到python代码中。   主要围绕解决采用什么库、掌握这个库的控制相机方法(参数配置、读取数据等等)、结合自己的算…

Go 反射

目录 什么是反射 反射的弊端 reflect 包 Go 提供的反射方法 type Type 类型 type Kind 类型 TypeOf ValueOf 什么是反射 ​反射&#xff08;reflection&#xff09;是在 Java 出现后迅速流行起来的一种概念&#xff0c;通过反射可以获取丰富的类型信息&#xff0c;并可…

实战!项目管理实施过程的五大难点和痛点

作为一个在项目摸爬滚打十余年的管理人员&#xff0c;对项目管理的难点和痛点深有体会&#xff0c;这就结合我自身体验来说一说。 我认为&#xff0c;项目管理实施中的难点和痛点其实可以归结为两类&#xff1a;一类是对于项目任务本身&#xff0c;另一类则涉及到团队内部的管…

2023年,转行学Java还是web前端?

2023年要想顺利入行IT建议选择Java。 理由很简单&#xff0c;前端开发岗位需求大量减少&#xff0c;大厂裁员导致大量有经验的前端开发人员或者初级后端开发人员流入就业市场&#xff1b;作为新人缺乏技能优势和项目优势&#xff0c;而用人单位也更愿意招聘熟手&#xff0c;或…

Python 自动化指南(繁琐工作自动化)第二版:八、输入验证

原文&#xff1a;https://automatetheboringstuff.com/2e/chapter8/ 输入验证代码检查用户输入的值&#xff0c;比如来自input()函数的文本&#xff0c;格式是否正确。例如&#xff0c;如果您希望用户输入他们的年龄&#xff0c;您的代码不应该接受无意义的答案&#xff0c;如负…

chatgpt大模型赋能人形机器人之我见

我个人的看法&#xff08;不涉及任何和他项目相关的细节或商业机密&#xff0c;仅仅是我个人的泛泛而谈&#xff09;&#xff1a; 1、从大趋势来说&#xff0c;人形机器人的灵活度和通用性确实非常的高&#xff0c;是有前景的。另外轮式足式也不是他独一例&#xff0c;像 ETH …

【Redis学习】Redis管道

理论简介 问题由来 客户端向服务端发送命令分四步(发送命令→命令排队→命令执行→返回结果)&#xff0c;并监听Socket返回&#xff0c;通常以阻塞模式等待服务端响应。 服务端处理命令&#xff0c;并将结果返回给客户端。 上述两步称为:Round Trip Time(简称RTT,数据包往返…

STM32基于STM32CubeMX DMA + EXTI读取DS1307数据

STM32基于STM32CubeMX DMA EXTI读取DS1307数据✨申明&#xff1a;本文章仅发表在CSDN网站&#xff0c;任何其他网站&#xff0c;未注明来源&#xff0c;见此内容均为盗链和爬取&#xff0c;请多多尊重和支持原创!&#x1f341;对于文中所提供的相关资源链接将作不定期更换。&a…

JVM垃圾回收机制简介

内存管理 Java的内存管理很大程度指的就是对象的管理&#xff0c;其中包括对象空间的分配和释放。 对象空间的分配&#xff1a;使用new关键字创建对象即可 对象空间的释放&#xff1a;将对象赋值null即可。垃圾回收器将负责所有“不可达”对象的内存空间。 垃圾回收过程 任…