Android入门(12)| 数据持久化

文章目录

  • 数据持久化
  • 文件存储
    • 将数据存储进文件
      • 实例
    • 从文件中读取数据
      • 实例
  • SharedPreferences存储
    • 将数据存储进文件
      • 实例
    • 从文件中读取数据
      • 实例
    • 实现记住密码的功能
  • SQLite数据库存储
    • 创建自己的帮助类
    • 调用自己的帮助类
    • 补全 onUpgrade() 方法
    • 增删查改
      • 增:SQLiteDatabase.insert()
      • 改:SQLiteDatabase.update()
      • 删:SQLiteDatabase.delete()
      • 查:SQLiteDatabase.query()
    • 通过 SQL语句 实现增删查改


数据持久化

保存在内存中的数据是属于瞬时状态的,而保存在存储设备中的数据上处于持久状态的,持久化技术提供了一种可以让数据在瞬时状态和持久状态之间转换的机制。

Android系统中主要提供了3种常用方式用于简单地实现数据持久化功能,即文件存储SharedPreference存储以及数据库存储


文件存储

将数据存储进文件

Context类 中提供了一个 openFileOutput 方法,用于将数据存储到指定的文件中。这个方法接收两个参数:

  • 第一个参数是文件名:在文件创建的时候使用的就是这个名称,文件名不可以包含路径,因为所有的文件都是默认存储到 /data/data/<packagename>/files/ 目录下的。
  • 第二个参数是文件的操作模式:主要有两种模式可以选,MODE_PRIVATE 默认的操作模式,写入的内容会覆盖原文件的内容; MODE_APPEND 则表示如果该文件已经存在,就往文件里面追加内容,不存在创建新文件

该方法返回一个 FileOutputStream 对象,得到了这个对象之后就可以使用 Java流 的方式将数据写入到文件中了。

实例

在布局文件中添加输入框 EditText 控件:
在这里插入图片描述
在活动文件中,定义不同生命周期的不同行为:

  • onCreate 方法:获取 EditText 实例;
  • onDestroy 方法:获取 EditText 中的内容,并通过自定义方法 save 保存到名为 data 的文件中。
public class SecondActivity extends AppCompatActivity {private EditText editText;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.second_layout);editText = findViewById(R.id.edit);}@Overrideprotected void onDestroy() {super.onDestroy();String inputText = editText.getText().toString();save(inputText);}public void save(String inputText){FileOutputStream out = null; // 文件字节输出流,继承OutputStream类BufferedWriter writer = null; // 将文本写入字符输出流try {// 获得一个 字节输出流对象,规定数据存储到名为data的文件中,文件的操作模式为MODE_PRIVATEout = openFileOutput("data", Context.MODE_PRIVATE);// 借助out构建OutputStreamWriter临时对象,作为从字符流到字节流的桥接// 再通过临时对象构建 字符输出流对象 以便将文本内容写入到字节流中writer = new BufferedWriter(new OutputStreamWriter(out));// 将文本内容写入到字符流中writer.write(inputText);} catch (IOException e){e.printStackTrace();} finally {try {if(writer != null){writer.close();}} catch (IOException e){e.printStackTrace();}}}
}

PS:对于上述将文本存入文件的流程,一开始我理解错了,顺着代码顺寻看以为是字节流转成字符流再写入文件,把 inputText 当保存文本的文件了。。。

其实正确逻辑是:
在这里插入图片描述

运行结果:

在文本框内输入内容:
在这里插入图片描述
退出程序后,在AS中通过如下操作打开文件页面:
在这里插入图片描述
在下图路径中找到 data 文件,查看其内容:
在这里插入图片描述


从文件中读取数据

Context 类中还提供了一个 openFileInput 方法,用于从文件中读取数据。这个方法只接受一个参数:

  • 要读取的文件名:然后系统会自动到 /data/data/<packagename>/files 目录下去加载这个文件。

该方法返回一个 FileInputStream 对象,得到了这个对象之后再通过 Java流 的方式就可以将数据读取出来了。

实例

EditText 为空则将文件中的文本读入到 EditText 中:

public class SecondActivity extends AppCompatActivity {private EditText editText;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.second_layout);editText = findViewById(R.id.edit);String inputText = load();if(!TextUtils.isEmpty(inputText)){editText.setText(inputText);editText.setSelection(inputText.length());Toast.makeText(this, "loading successed", Toast.LENGTH_LONG).show();}}public String load(){FileInputStream in = null;BufferedReader reader = null;StringBuilder content = new StringBuilder();try {in = openFileInput("data"); // 文本字节输入流// InputStreamReader作为字符流到字节流的桥接reader = new BufferedReader(new InputStreamReader(in));String line = "";// 从字符流中读取数据,每次读取文件的一行while((line = reader.readLine()) != null){content.append(line);}} catch (IOException e){e.printStackTrace();} finally {if(reader != null){try {// 处理完文本后关闭流reader.close();} catch (IOException e){e.printStackTrace();}}}return content.toString();}
}

PS:在判空时使用了 TextUtils.isEmpty() 而非 String.isEmpty(),这是因为:

  • String 类下的 isEmpty() 返回的只是 字符串的长度是否为0,如果 字符串为null 就会直接报 空指针。源码如下:
public boolean isEmpty() { return count == 0; }
  • TextUtils.isEmpty() 会对 null长度 进行判断,所以 不会报空指针。源码如下:
public static boolean isEmpty(CharSequence str) { if (str == null || str.length() == 0) return true; else return false; 
}

此时,一打开界面即可显示:
在这里插入图片描述


SharedPreferences存储

将数据存储进文件

大致分为两步,第一步,获取对象:

SharedPreferences 通过 键值对 的方式来存储数据的。要想存储数据,需要先获取 SharedPreferences对象,Android 中主要提供了三种方法用于得到 SharedPreferences 对象:

  • Context类 中的 getSharedPreferences 方法:此方法接受两个参数,
    1. 第一个参数用于指定 SharedPreferences 文件的名称,如果文件不存在则创建一个。文件都存放在 /data/data/<packagename>/shared_prefs/ 目录下。
    2. 第二个参数用于指定 操作模式,目前只有 MODE_PRIVATE 这种默认的操作模式可选,和直接传入 0 效果是相同的,表示只有当前的应用程序才可以对这个 SharedPreferences 文件进行读写
  • Acitvity类 中的 getPreferences 方法:只有一个参数——操作模式自动使用当前活动的类名来作为 SharedPreferences 的文件名。
  • PreferenceManager类 中的 getDefaultSharedPreferences 方法:静态方法,接收一个 Context 参数,并自动使用当前应用程序的包名作为前缀来命名 SharedPreferences 文件。

第二步,存储数据:

  1. 调用 SharedPreferences对象 的 edit方法 来获取一个 SharedPreferences.Editor对象
  2. 向 SharedPreferences.Editor对象 中添加数据,比如添加一个布尔型数据就使用 putBoolean 方法,添加一个字符串则使用 putString 方法。
  3. 调用 apply 方法将添加的数据提交,从而完成数据存储操作。

实例

实现点击按钮保存数据的功能:

        Button button_share = findViewById(R.id.button_share);button_share.setOnClickListener((View v)->{SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();editor.putString("name", "cmy");editor.putInt("weight", 120);editor.putBoolean("married", false);editor.apply();Toast.makeText(this, "share over", Toast.LENGTH_LONG).show();});

点击红框所示按钮:
在这里插入图片描述
即可将数据存在如下图所示的路径中:
在这里插入图片描述


从文件中读取数据

第一步仍是获取对象,上文已经讲过,这里不再赘述。

第二步,通过对应的 get**()方法 获取对应类型数据,如字符串使用 getString() 方法,这些 get 方法都接受两个参数:

  • 第一个参数是:也就是 KV模型 中的 Key
  • 第二个参数是默认值:当传入的找不到对应值时,以默认值返回。

实例

点击 get sharePreferences 按钮从 SharedPreferences文件 中读取数据:
在这里插入图片描述

再通过 Toast 显示出来:

在这里插入图片描述

        Button button_getShare = findViewById(R.id.button_getShare);button_getShare.setOnClickListener((View v)->{SharedPreferences preferences = getSharedPreferences("data", MODE_PRIVATE);String name = preferences.getString("name", "");int weight = preferences.getInt("weight", 0);boolean married = preferences.getBoolean("married", false);String res = name+" "+String.valueOf(weight)+" "+String.valueOf(married);Toast.makeText(this, res, Toast.LENGTH_LONG).show();});

实现记住密码的功能

之前在本博客里实现过登陆界面,这里为登陆界面新加入一个记住密码的功能。

修改布局文件,添加以下代码,实现右侧所示布局:
在这里插入图片描述

这里使用到了一个新控件 复选框:CheckBox ,用户可以通过点击来进行选中/取消,以表是否需要记住密码。

修改 LoginActivity.java 代码,结合 SharedPreferences 实现记住密码的功能:

在这里插入图片描述
增添的内容主要是:

  • 三个相关对象 CheckBox、SharedPreferences、SharedPreferences.Editor 的创建和实例化;
  • 初始化布尔型对象 isRemember 作为 判断记住密码功能是否生效 的辅助变量;
    1. 一开当然不存在 remember_password 这个键对应的值,isRemember 为默认值 false
    2. 成功登陆一次后,remember_password 这个键对应的值就是 true 了。

以及登陆成功后的操作:
在这里插入图片描述

  • 调用 CheckBoxisChecked() 方法检查复选框是否被选中,被选中则返回 true
  • true 时表示用户希望记住密码,此时:
    1. remember_password 对应的值设为 true
    2. accountpassword 对应的值都存入到 SharedPreferences 文件中并提交。
  • false 表示用户并不想记住密码,此时要调用 clean() 方法清楚掉 SharedPreferences 文件中的所有数据。

运行结果:

  • 输入正确的账户和密码,并选中记住密码,点击登录:
    在这里插入图片描述
  • 通过强制下线跳转回登陆界面,此时发现账号密码已经自动填充了。
  • 如果此时取消选中复选框,再点击登录:
    在这里插入图片描述
  • 再次返回登陆界面就不会被填充了:
    在这里插入图片描述
    PS:这里只做示例,实际项目中不能将密码以明文形式存储到 SharedPreferences 文件中,因为会被轻易盗取,必须结合加密算法对密码进行加密。

SQLite数据库存储

为了管理数据库,安卓专门提供了一个 SQLiteOpenHelper 帮助类,这是个抽象类,如果要使用它,需要创建一个 自己的帮助类 去继承它。下面列举几个该类中常用的方法:

  • 两个抽象方法onCreateonUpgrade,我们必须在 自己的帮助类 里重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
  • 两个实例方法getReadableDatabasegetWritableDatabase。这两个方法都可以创建或者打开一个现有的数据库,数据库文件存放在 /data/data/<packagename>/databases/ 目录下,并返回一个可对数据库进行读写的对象。不同的是,当数据库不可写入的时候,getReadableDatabase 方法返回的对象会用只读的方式打开数据库,而 getWritableDatabase 会出现异常
  • 两个构造函数:常用的一个构造方法接收4个参数:第一个是Context;第二个是数据库名;第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null;第四个参数表示当前数据库的版本号,可以用于升级数据库。

创建自己的帮助类

public class MyDatabaseHelper extends SQLiteOpenHelper {public static final String CREATE_STUDENT = "create table Student ("+ "id integer primary key autoincrement,"+ "gender text,"+ "weight real,"+ "age integer,"+ "name text)";private Context context;public MyDatabaseHelper( Context context,  String name,SQLiteDatabase.CursorFactory factory, int version) {super(context, name, factory, version);this.context = context;}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL(CREATE_STUDENT);Toast.makeText(context, "create succeeded", Toast.LENGTH_LONG).show();}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
  • 把建表语句定义成一个字符串常量,integer 表示整型,real 表示浮点型,text 表示文本类型,blob 表示二进制类型。此外,使用了 primary key 将 id 设置为主键,并且用 autocrement 关键字表示 id 列是自增长的。
  • onCreate 方法中有调用了 SQLiteDatabaseexecSQL 方法去执行这条建表语句。

调用自己的帮助类

创建一个活动 SQLiteActivity,其布局内有一个按钮,点击即可创建 Student.db 数据库:

public class SQLiteActivity extends AppCompatActivity {private MyDatabaseHelper myDatabaseHelper;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.sqlite_layout);myDatabaseHelper = new MyDatabaseHelper(this, "Student.db", null, 1);Button button_create = findViewById(R.id.button_create);button_create.setOnClickListener((View v)->{myDatabaseHelper.getWritableDatabase();Toast.makeText(this, "创建数据库成功", Toast.LENGTH_LONG).show();});}
}
  • 第一次点击按钮时,会检测到当前程序并没有 Student.db 这个数据库,于是会创建该数据库并调用 MyDatabaseHelper 中的 onCreate 方法,得以创建 Student 表。
  • 之后点击按钮就不会再调用 MyDatabaseHelper 中的 onCreate 方法了,因为 Student.db 已经存在了。

布局文件 sqlite_layout.xml

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/button_create"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="create database"/>
</LinearLayout>

在这里插入图片描述

运行结果:

create succeeded 只会在数据库首次创建时出现:
在这里插入图片描述

创建数据库成功 会在每次点击按钮后出现:
在这里插入图片描述

查看数据库和表的创建情况

在环境变量中添加好 platform-tools 后:
在这里插入图片描述
在这里插入图片描述
打开 cmd,输入 adb shell 进入设备控制台:
在这里插入图片描述
通过 su 获取管理员权限,否则无法进入 /data/data/<packagename>/databases/ 路径:
在这里插入图片描述
通过 cd 命令进入数据库文件所在目录:
在这里插入图片描述
该目录下有两个数据库文件,一个是我们创建的 Student.db ;一个是为了让支持事务的临时日志文件 Student.db-journal

打开数据库:
在这里插入图片描述
查看数据库中有哪些表:
在这里插入图片描述
PS:android_metadata 是每个数据库自动生成的。

查看建表语句:
在这里插入图片描述
通过 .exit.quit 退出数据库:
在这里插入图片描述


补全 onUpgrade() 方法

该方法用于升级数据库,目前项目中已经有了一张 Student 表用于存放学生的各种详细数据,如果想再添加一张 Class 表用于记录学生的班级,怎么做呢?

将建表语句添加到自己的帮助类 MyDatabaseHelper 中:
在这里插入图片描述
该如上图所示在 onCreate 阶段执行一次 Class 的建表语句吗?

不是的,正如上一个实例中,数据库创建完成后,我们再点击按钮,只会弹出 创建数据库成功 而不会弹出 create succeeded 一样,两者的根本原因都是 onCreate 方法只会在创建数据库时执行一次,创建成功后不会再次执行。

因此无法在 Student.db 存在的情况下通过 onCreate 方法添加新表,而应通过 onUpgrade 方法添加新表。 具体做法如下:

@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {//  如果数据库中存在 Student 表或 Class表,就将他们删除。db.execSQL("drop table if exists Student");db.execSQL("drop table if exists Class");// 然后调用 onCreate 方法重新创建onCreate(db);}

PS:之所以 不跳过删除已有表直接调用 onCreate ,是因为 创建表时如果该表已存在会报错

接下来重新调用 SQLiteOpenHelper 的构造方法,使第四个参数——数据库版本号大于之前传入的 1 即可让 onUpgrade 执行:
在这里插入图片描述

运行结果:

在这里插入图片描述

PS:使用 AS 时也可以通过一下流程查看数据库及建表情况:
在这里插入图片描述
在这里插入图片描述


增删查改

CRUD 操作当然可以通过 SQL 语言实现,但 Android 也提供了一系列辅助方法,前面提到 getReadableDatabasegetWriteableDatabase 方法是会返回一个 SQLiteDatabase 对象,借助这个对象就可以对数据进行操作了。

增:SQLiteDatabase.insert()

该方法有三个参数:

  1. 表名
  2. 用于在未指定添加数据的情况下给某些可为空的列自动赋值为 null
  3. ContentValues 对象,它提供了一系列的 put 方法重载,用于向 ContentValues 中添加数据,只需要将表中的每个列名待添加数据传入即可。

在布局中添加了一个按钮用于增加数据:
在这里插入图片描述
SQLiteActivityonCreate 方法中添加以下代码:
在这里插入图片描述

点击两次按钮的运行结果:

在这里插入图片描述
在这里插入图片描述
两张表各添加了两行数据。


改:SQLiteDatabase.update()

该方法有四个参数:

  1. 表名;
  2. ContentValues 对象;
  3. 第三个、第四个参数用于约束更新某一行或者某几行的数据,不指定的话默认更新所有行

在布局中添加了一个按钮用于更新数据:
在这里插入图片描述
SQLiteActivityonCreate 方法中添加以下代码:

        Button button_update = findViewById(R.id.button_update);button_update.setOnClickListener((View v)->{SQLiteDatabase db = myDatabaseHelper.getReadableDatabase();ContentValues values = new ContentValues();// 第一条数据values.put("weight", 90);values.put("name", "zj");db.update("Student", values, "id = ?", new String[] {"2"});// 第二条数据values.put("class_name", "电子");values.put("class_num", 183);db.update("Class", values, "id = ?", new String[] {"1"});Toast.makeText(this, "更新完成", Toast.LENGTH_LONG).show();});

以第一条数据为例:

  • values 用以更新 weightname 两项属性的值;
  • 第三、第四个参数指定更新 id=2 的行。

点击按钮后的运行结果:

在这里插入图片描述
在这里插入图片描述


删:SQLiteDatabase.delete()

该方法接受三个参数:

  1. 表名;
  2. 第二、三个用于约束删除某几行的数据,不指定则删除所有行

SQLiteActivityonCreate 方法中添加以下代码:

        Button button_delete = findViewById(R.id.button_delete);button_delete.setOnClickListener((View v)->{SQLiteDatabase db = myDatabaseHelper.getReadableDatabase();db.delete("Student", "weight < ?", new String[] {"100"});Toast.makeText(this, "删除完成", Toast.LENGTH_LONG).show();});
  • 删除 weight < 100 的行。

点击按钮后的运行结果:

在这里插入图片描述


查:SQLiteDatabase.query()

该方法比前三个复杂一些,最短的一个重载方法也需要传入七个参数:

  1. 表名;
  2. 用于指定查询哪几列;
  3. 三四个参数用于约束查询某几行的数据,不指定则默认查询所有行的数据
  4. 第五个参数用于指定需要去 group by 的列,不指定则不对查询结果进行分组
  5. 第六个参数用于对 group by 之后的数据进一步过滤;
  6. 第七个参数用于指定查询结果的排序方式。

在这里插入图片描述
调用该方法后会返回一个 Cursor 对象,查询到的所有数据都将从这个对象中取出。

        Button button_query = findViewById(R.id.button_query);button_query.setOnClickListener((View v)->{SQLiteDatabase db = myDatabaseHelper.getReadableDatabase();// 查询 Class 表中所有数据Cursor cursor = db.query("Class", null, null, null,null, null, null);if(cursor.moveToFirst()){do{String res = "";res += cursor.getString(cursor.getColumnIndex("class_name")) + " ";res += cursor.getString(cursor.getColumnIndex("class_num"));Toast.makeText(this, res, Toast.LENGTH_LONG).show();}while(cursor.moveToNext());}cursor.close();});
  • query方法 首参数设置为 Class,其余参数设置为 null,表示查询 Class表 所有数据。
  • 调用 moveToFirst方法 将数据指针移动到第一行的位置;
  • 通过 getColumnIndex方法 获取位置索引以遍历每一行数据,并将之通过 Toast 打印到屏幕上;
  • 通过 moveToNext方法 移动数据指针遍历下一行数据,如果指针已经到达了数据指针集的尾后位置,此方法将返回 false

通过 SQL语句 实现增删查改

除了查询语句通过 db.rawQuery() 执行之外,其他三种操作都可以通过 db.execSQL() 执行。

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

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

相关文章

Android入门(13)| Android权限 与 内容提供器

文章目录普通权限与危险权限运行时申请权限内容提供器运用安卓封装好的内容提供器自实现的内容提供器概念实现普通权限与危险权限 主要用于不同应用程序之间在保证被访数据的安全性的基础上&#xff0c;实现数据共享的功能。 在 Android 6.0 开始引入了运行时权限的功能&…

Android入门(14)| 通知

文章目录创建通知点击效果其它小功能实例创建通知 创建通知的步骤&#xff1a; 管理通知的 NotificationManager&#xff0c;通常通过当前 Context 的 getSystemService() 获取实例。它接受一个字符串参数用于确定获取系统的什么服务。Android 8.0(O) 版本后需要通知通道&…

Android开发(3) | 权限和内容提供器的应用——调用相机和相册

文章目录拍照并保存到 ImageView 控件布局文件 notice_layout.xml按钮 button_takePhoto 的点击操作隐式 Intent 启动后的回调AndroidManifest.xml从相册选取照片并在 ImageView 控件中显示布局文件 notice_layout.xml按钮 button_takePhoto 的点击操作自定义打开相册的方法 op…

Android开发(4) | 系统权限、MediaPlayer类 和 VideoView类 的应用——播放多媒体文件

文章目录MediaPlayer类播放音频的实例VideoView类播放视频的实例MediaPlayer类 对多种格式的音频文件提供了全面的控制方法&#xff1a; 如何获得MediaPlayer实例&#xff1f; 通过构造函数&#xff1a; MediaPlayer mp new MediaPlayer();调用 MediaPlayer.create() 方法&…

Android入门(15)| 网络

文章目录WebViewHTTP使用HttpURLConnection使用OkHttp封装网络操作封装HttpURLConnection封装OkHttpWebView WebView 可以在 应用程序中&#xff08;而不是浏览器&#xff09; 展示一些网页。 布局文件 web_layout.xml&#xff1a; <LinearLayoutxmlns:android"http…

Android入门(16)| 服务

文章目录概念Android 多线程继承 Thread继承 Runable 接口匿名类异步消息处理AsyncTask使用服务框架启动/停止服务绑定/解绑服务服务的生命周期前台服务IntentService完整版下载示例下载过程的回调接口&#xff1a;DownloadListener继承 AsyncTask 实现下载功能&#xff1a;Dow…

2020德勤面试开始了吗_2020国考面试开始,近期面试公告汇总,附结构化小组面试流程...

2020年国家公务员考试面试环节逐步恢复考试&#xff0c;各个招录部门已经发布面试考察公告&#xff0c;对于进入面试环节的国考考生来说&#xff0c;有必要了解近期国考面试的招录动态&#xff0c;提前做好面试准备。2020国考国家统计局机关面试面试确认&#xff1a;请进入面试…

项目积压需求项目计划_需求变更频繁,项目经理如何做好需求管理?

项目实施过程中&#xff0c;项目经理常常面临一个重大挑战——需求变更。需求变更无处不在&#xff0c;市场条件变化、新业务出现、战略目标调整、客户需求修改、资源限制等&#xff0c;都会造成需求变更。需求变更会影响项目的时间、成本和质量&#xff0c;对整个项目和团队成…

Android | Sensor.TYPE_ORIENTATION被废弃后的解决办法

文章目录概述getOrientation 方法根据 旋转矩阵R 获取 设备旋转弧度getRotationMatrix 方法根据 地磁场、加速度传感器对象 获取 旋转矩阵R代码参考资料概述 Sensor.TYPE_ORIENTATION 常数在 API 8 中已弃用&#xff0c;官方推荐使用 SensorManager.getOrientation() 替代。关…

【JAVA 开发小问题】 | String操作合集

文章目录截取特定两个字符之间的字符串截取特定两个字符之间的字符串 利用正则表达式&#xff0c;图片来源

uniapp 刷新后数据都没有了_环境温度传感器都没有连接,竟然还会有数据?

福田欧曼GTL(福康发动机、康明斯2880系统)匹配ECoffit尿素泵●故障现象&#xff1a;OBD故障灯点亮&#xff0c;不烧尿素&#xff0c;油耗高&#xff0c;动力不足●故障码&#xff1a;●维修分析&#xff1a;①故障指出加热器问题&#xff0c;摸下尿素箱温度&#xff0c;发现烫手…

保姆级教学!Xcode 配置 OpenGL 环境

文章目录GLFW获取 GLFWGLAD获取 GLAD在 Xcode 中配置下载好的 GLFW 和 GLAD配置流程检测是否配置成功无关配置的题外话——Xcode 下安全的删除移动操作GLFW Graphics Library Framework&#xff08;图形库框架&#xff09;&#xff0c;可以让我们通过其封装好的 通用API 来正确…

Android入门(17)| 百度提供的 Android定位SDK

文章目录配置百度提供的 Android定位SDK用于发布的 SHA1用于测试的 SHA1使用百度定位实例配置百度提供的 Android定位SDK 详情参见官方文档&#xff0c;这里仅对获取 SHA1 做详细介绍&#xff1a; 用于发布的 SHA1 用于测试的 SHA1 使用百度定位实例 public class LocationAc…

ios 不被遮挡 阴影_为何你没见到日环食?你不知道的天象常识原来还有这么多 | 返朴...

关注风云之声提升思维层次导读说好的日环食呢&#xff0c;为什么上周很多人只等到了日偏食?日食月食的时间和种类是怎么预测的?你真的弄懂了各种日食和月食的成因吗&#xff1f;你了解它们有什么区别和联系&#xff0c;又遵循什么样的时间规律吗? 日食和月食发生的频率一样吗…

初识贝塞尔(bezier)曲线

文章目录资料援引贝塞尔曲线的用途一阶贝塞尔&#xff08;bezier&#xff09;曲线二阶贝塞尔&#xff08;bezier&#xff09;曲线三阶贝塞尔&#xff08;bezier&#xff09;曲线高阶贝塞尔&#xff08;bezier&#xff09;曲线三阶贝塞尔曲线求插值&#xff08;Slerp&#xff09…

python代码测试 vim_用 Hypothesis 快速测试你的 Python 代码

点击上方“Python编程时光”&#xff0c;选择“加为星标”第一时间关注Python技术干货&#xff01;介绍无论你使用哪种编程语言或框架&#xff0c;测试都非常重要。Hypothesis是 Python 的一个高级测试库。它允许编写测试用例时参数化&#xff0c;然后生成使测试失败的简单易懂…

Mac 下 CMake 的配置与使用

文章目录安装与配置编译单个源文件编译前的准备开始编译编译多个源文件多个源文件在同一目录下多个源文件在不同目录下math 目录下的 CMakeLists.txt根目录的 CMakeLists.txtoption 选项导入外部库本地导入&#xff08;find_package&#xff09;外部导入&#xff08;FetchConte…

五轴编程_沙井万丰数控数控编程五轴编程那个软件好用

沙井万丰数控数控编程五轴编程那个软件好用设计需要掌握很高很全面的知识和技能&#xff0c;模具做的好&#xff0c;产品质量好&#xff0c;模具结构合理&#xff0c;生产效率高&#xff0c;工厂效益好。正因如此&#xff0c;模具技术工在外打工的工资都非常的高。少则每月几千…

Linux学习:第二章-Linux安装

一虚拟机使用 VMware主要特点&#xff1a; 1、不需要分区或重新开机就能在同一台PC上使用两种以上的操作系统 2、本机系统可以与虚拟机系统网络通信 3、可以设定并且随时修改虚拟机操作系统的硬件环境 二安装方式 图形安装&#xff1a;直接回车 字符安装&#xff1a;linux tex…

keil3如何放大字体_国潮海报不会做?送你国风字体+图案笔刷+PSD素材+包装样机...

有很多朋友都问带鱼&#xff0c;国潮风的海报到底应该怎么做呢&#xff1f;首先你要知道什么是国潮风&#xff1a;国潮风就是现代文化和古代文化的碰撞&#xff0c;是年轻人的态度&#xff01;那么应该如何构图如何设计呢&#xff1f;如何配色如何搭配字体呢&#xff1f;这些方…