当前位置: 首页 > news >正文

【Android】数据持久化——数据存储

持久化技术简介

在你打开完成了一份PPT之后关闭程序,再次打开肯定是希望之前的内容还存在在电脑上,一打开PPT,之前的内容就自动出现了。数据持久化就是将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑关机的情况下,这些数据仍然不会丢失。保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据是处于持久状态的,持久化技术则提供了一种机制可以让数据在瞬时状态和持久化状态之间进行转换。

Android系统主要提供了3种方式用于简单地实现数据持久化功能:

  • 文件存储
  • SharedPreferences存储
  • 数据库存储

文件存储

文件存储是Android中最基本的一种数据存储方式,它不对存储的内容进行任何的格式化处理,所有的数据都是原封不动地保存到文件当中,因而它比较适合用于存储一些简单的文本数据或者二进制数据。

将数据存储到文件中

Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件当中。

  • 第一个参数为文件名,在文件创建的时候使用的就是这个名称(这里的文件名不可以包含路径,因为所有的文件都是默认存储到/data/data//files/目录下)
  • 第二个参数是文件的操作模式,主要有两种模式可以选,MODE_PRIVATEMODE_APPEND

二者的相同点:当文件不存在时,都会创建一个新文件来写入数据

MODE_PRIVATE:是默认的操作模式,会截断文件,即删除文件中的现有内容,并从文件开头开始写入新数据

MODE_APPEND:新写入的数据会被追加到文件的末尾,而不是覆盖现有内容

openFileOutput()方法返回的是一个FileOutputStream对象,得到了这个对象就可以使用Java流的方式将数据写到文件中了。我们先创建一个活动设置一个EditText用来使用户输入信息,在我们关闭程序的时候将数据进行存储,代码如下:

public class MainActivity extends AppCompatActivity {private EditText editText;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);return insets;});editText = (EditText) findViewById(R.id.edit);}}@Overrideprotected void onDestroy() {super.onDestroy();String input = editText.getText().toString();save(input);}public void save(String inputText) {FileOutputStream out = null;BufferedWriter writer = null;try {out = openFileOutput("data", Context.MODE_PRIVATE);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();}}}
}

从文件中读取数据

Context类中还提供一个openFileInput()方法,用于从文件里面读取数据,它只接收一个参数,即要读取的文件名,然后系统会自动到目录下去加载这个文件,并返回一个FileInputStream对象,得到这个对象之后再通过Java流的方式将数据读取出来。

读取文件代码如下:

public String lode () {FileInputStream in = null;BufferedReader reader = null;StringBuilder content = new StringBuilder();try {in = openFileInput("data");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) {throw new RuntimeException(e);}}}return content.toString();
}

首先通过openFileInput()方法获取到一个FileInputStream对象,然后借助它又构建一个InputStreamReader对象,接着再使用InputStreamReader构建成一个BufferedReader对象,这样就可以一行行地读取,把文件地内容都读取出来。学习了读取文件的代码,就对活动进行修改,使你第一次输入的数据在退出程序之后再次进入程序数据依然保留。修改代码如下:

先将上面读取文件的代码加入活动当中,再在onCreate()方法里面补充:

String input = lode();
editText = (EditText) findViewById(R.id.edit);
if (!TextUtils.isEmpty(input)) {editText.setText(input);editText.setSelection(input.length());Toast.makeText(this,"Restoring succeeded", Toast.LENGTH_SHORT).show();
}

在Android开发中,setSelection方法用于设置文本输入框(如EditText)中光标的位置。当你调用editText.setSelection(input.length());时,你实际上是将光标移动到了文本的末尾。

现在运行程序,第一次运行没有输入任何的数据,因此打开程序的输入框是空白的,当我们输入一段文字,再次打开:

在这里插入图片描述

SharedPreferences存储

SharedPreferences是使用键值对的方式进行数据存储的,因此我们可以根据数据所对应的键将数据取出来。

将数据存储到SharedPreferences中

要想使用SharedPreferences来存储数据,首先需要获取到SharedPreferences对象,Android提供了三种方式用于得到SharedPreferences对象:

  • Context类中的getSharedPreferences()方法

    此方法接收两个参数,第一个参数用于指定SharedPreferences文件的名称,如果指定的文件不存在则会创建一个,SharedPreferences文件都是放在/data/data//shared_prefs/目录下的。第二个参数用于指定操作模式,目前只有MODE_PRIVATE这一种模式可选,它是默认的操作模式,和直接传入0是效果相同的,表示只有当前的程序才可以对这个SharedPreferences文件进行读写

  • Activity类中的getPreferences()方法

    这个方法和上面的方法很类似,但它只接收一个操作模式参数,因为这个方法会自动获取当前活动的类名作为SharedPreferences的文件名

  • PreferenceManage类中的getDefaultSharedPreferences()方法

    这是一个静态方法,只接收一个Context参数,并自动使用当前应用程序的包名作为前缀名来命名SharedPreferences文件。

接下来就进行数据的存储:

  1. 调用SharedPreferences对象的edit()方法来获取一个SharedPreferences.Editor对象
  2. SharedPreferences.Editor对象中添加数据,例如添加String类型,就使用putString()方法
  3. 调用apply()方法将添加的数据提交,从而完成数据存储操作

接下来就根据实例来体验一下吧,新建一个项目,在主活动上面设置一个按钮用来触发存储数据,为按钮注册点击事件:

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);return insets;});Button buttonsava = (Button) findViewById(R.id.savaData);buttonsava.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {SharedPreferences.Editor editor =getSharedPreferences("data", MODE_PRIVATE).edit();editor.putString("name", "tom");editor.putInt("age", 28);editor.putBoolean("married", false);editor.apply();}});}
}

当我们按下按钮就会触发点击事件,将这三个数据存储进去

从SharedPreferences中读取数据

上面提到了put方法将数据存储进去,相对应的有get方法从中读取数据。这些get方法都接收两个参数,第一个参数是键,传入存储数据时使用的键就可以得到相应的值了;第二个参数是默认值,即表示当前的键找不到对应的值时会以什么样的默认值进行返回。

仍然在上面进行修改,添加一个按钮用来触发读取数据的事件,并未按钮注册点击事件:

Button buttonget = (Button) findViewById(R.id.getData);
buttonget.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {SharedPreferences preferences = getSharedPreferences("data", MODE_PRIVATE);String name = preferences.getString("name", "");int age = preferences.getInt("age", 0);boolean marr = preferences.getBoolean("married", false);Log.d("MainActivity", "name is " + name);Log.d("MainActivity", "age is " + age);Log.d("MainActivity", "married is " + marr);}
});

按下get data按钮就可以得到信息了:
在这里插入图片描述

案例

接下来就通过一个案例让大家体会数据存储吧。我们有时在登录的时候经常遇到一个选项:是否记住密码?当我们按下这个按钮在下一次登录的时候就会自动的为我们输入数据,此时我们只需要按下登录按钮即可。想要了解的话可以看上一篇广播的博客,在结尾我们实现了一个强制下线的功能,现在就对这个小项目加上记住密码功能。因此在登录界面我们需要使使用者自由选择是否让程序记住密码:

<LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><CheckBoxandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/remember_pass"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="18sp"android:text="remember password"/>
</LinearLayout>

修改登录界面的代码:

private SharedPreferences preferences;
private SharedPreferences.Editor editor;
private CheckBox checkBox;

先根据前面的读取数据在onCreate()方法里面修改:

preferences = PreferenceManager.getDefaultSharedPreferences(this);
checkBox = (CheckBox) findViewById(R.id.remember_pass);
boolean isremember = preferences.getBoolean("remember_password", false);
if (isremember) {String account = preferences.getString("account", "");String password = preferences.getString("password", "");accountEdit.setText(account);passwordEdit.setText(password);checkBox.setChecked(true);
}

先获取上一次存储的数据是否允许记得,如果是,就将上一次的数据读取出来,显示在文本框,接下来就是登录的点击按钮会根据我们此次是否选择记住密码而进行不同的操作:

login.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {String account = accountEdit.getText().toString();String password = passwordEdit.getText().toString();if (account.equals("admin") && password.equals("123456")) {editor = preferences.edit();if (checkBox.isChecked()) {editor.putBoolean("remember_password", true);editor.putString("account", account);editor.putString("password", password);} else {editor.clear();}editor.apply();Intent intent = new Intent(LoginActivity.this, MainActivity.class);startActivity(intent);finish();} else {Toast.makeText(LoginActivity.this,"account or password is invalid", Toast.LENGTH_SHORT).show();}}
});

在我们的账户与密码正确的时候会根据我们对于记住密码的选择进行数据存储的更新。

此时运行程序,当我们选择记住密码时,到了活动界面按下按钮,强制下线之后密码与账号已经输好了,只需要按下登录按钮即可。

SQLite数据库存储

Android系统内置了数据库——SQLite。SQLite是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百KB的内存就足够了,因而特别适合在移动设备上使用。SQLite不及支持标准的SQL语法,还遵循数据库的ACID事务。前面介绍的方法都只适用一些简单的数据存储,当存储量大的时候上面的方法就很难实现了。接下来就看看Android的SQLite数据库是如何使用的吧!

创建数据库

Android为了让我们能够更加方便地管理数据库,专门提供了一个 SQLiteOpenHelper帮助类,借助这个类就可以非常简单地对数据库进行创建和升级。下面我就对 SQLiteOpenHelper的基本用法进行介绍。

SQLiteOpenHelper是一个抽象类,这意味着如果我们想要使用它的话,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper中有两个抽象方法,分别是onCreate()onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。

SQLiteOpenHelper中还有两个非常重要的实例方法:getReadableDatabase()getwritableDatabase()。这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getwritableDatabase()方法则将出现异常。

SQLiteOpenHelper中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。这个构造方法中接收4个参数:

  • 第一个参数是 Context,这个没什么好说的,必须要有它才能对数据库进行操作。

Cursor是一个从android.database.Cursor类继承而来的接口,用于访问SQLite数据库的内容。

  • 第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。
  • 第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null。
  • 第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。

构建出 SQLiteOpenHelper的实例之后,再调用它的getReadableDatabase()getwritableDatabase()方法就能够创建数据库了,数据库文件会存放在/data/data//databases/目录下。此时,重写的oncreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。

SQL知识的补充:

一个数据库通常会包含一个或多个表,每个表由一个名字标识(例如价格、id)。表包含带有数据的记录,例:

IDnameage
1Tom23
2Jack15
3Aila41

包含三条记录(每个记录对应一个人)

  1. 创建表:

create table 表名称 (

列名称1 数据类型,

列名称2 数据类型,

);

它的数据类型很简单:integer表示整型,real表示浮点型,text表示文本类型,blob表示二进制类型

接下来就实践一下:
新建MyDatabaseHelper继承于SQLiteOpenHelper

public class MyDatabaseHelper extends SQLiteOpenHelper {public static final String CREATE_BOOK = "create table book ("+ "id integer primary key autoincrement, "+ "author text, "+ "price reeal, "+ "pages integer, "+ "name text )";private Context mcontext;public MyDatabaseHelper (Context context, String name,SQLiteDatabase.CursorFactory factory, int version) {super(context, name, factory, version);mcontext = context;}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL(CREATE_BOOK);Toast.makeText(mcontext, "succeeded succeeded", Toast.LENGTH_SHORT).show();}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
  1. 我们先使用SQL语句创建了一个名为book的表
  • 在表中创建id的时候我们使用primary key,该字段是表的主键。主键是一个或多个字段的组合,用于唯一标识表中的每条记录。在数据库中,主键的值必须唯一,不能为null(除非特别指定)
  • 我们还加入了autoincrement,这是一个特定于某些数据库系统(如SQLite)的属性,表示每当表中插入一条新记录时,该字段的值会自动递增。这通常用于生成一个唯一的序列号,使得每条记录都有一个唯一的标识符。在SQLite中,autoincrement属性仅适用于整型(integer)字段。
  1. 构造出MyDatabaseHelper,传入4个参数:
  • context:应用程序的上下文,用于获取数据库文件的路径
  • name:数据库文件的名称
  • factory:游标工厂,用于创建游标对象。游标(Cursor)是一个重要的概念,它允许应用程序从数据库中检索和操作数据。游标工厂(Cursor Factory)是一个接口,用于创建游标对象
  • version:数据库的版本号
  1. 重写了MyDatabaseHelperonCreate()方法,这样在我们创建这个数据库的时候就会跟着执行onCreate()方法,从而同时完成表的创建。
  2. onUpgrade()方法:这是SQLiteOpenHelper的另一个重写方法,当数据库需要升级时调用

这样数据库的创建代码就完成了,接下来我们就在活动当中加上一个按钮,使按下按钮数据库就自动进行创建:

public class MainActivity extends AppCompatActivity {//按钮用来触发数据库的创建private Button buttonCreateData;//新建数据库MyDatabaseHelper dphelper;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);return insets;});//创建了一个MyDatabaseHelper对象dphelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);//为按钮注册点击事件buttonCreateData = (Button) findViewById(R.id.create_database);buttonCreateData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {dphelper.getWritableDatabase();}});}
}

我们先创建了一个MyDatabaseHelper对象,当你第一次点击按钮此时程序当中并没有这个数据库,因此会调用MyDatabaseHelper里的onCreate()方法进行创建,此时就会弹出Toast提示。当我们再次按的时候,由于已经创建过了,就会不会再执行了,也就没有了Toast消息提示。

升级数据库

在上面数据库类里面就有一个空的方法onUpgrade(),我们只简单提了一下是在数据库升级时调用,接下来就重点学习吧。可是很重要的!!

我们在其中加入一个表用于书的分类,我们再在onCreate()方法里面加上将这个表创建的语句:

public static final String CREATE_CATEGORY = "create table category ("+ "id integer primary key autoincrement, "+ "category_name text, "+ "category_code integer)";
public void onCreate(SQLiteDatabase db) {db.execSQL(CREATE_BOOK);db.execSQL(CREATE_CATEGORY);Toast.makeText(mcontext, "succeeded succeeded", Toast.LENGTH_SHORT).show();
}

此时运行程序,并没有Toast语句弹出,即没有执行onCreate()方法,这是因为数据库已经在之前建好了,之后无论怎样都不会执行,因此我们就要用到onUpgrade()方法,对这个方法进行修改:

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {db.execSQL("drop table if exists book");db.execSQL("drop table if exists category");onCreate(db);
}

这两个语句为当在数据库当中发现存在这两张表的时候就将表删除,之后再执行onCreate()方法,接下来就是让这个方法在创建的时候执行,上面提到构造的时候传入个参数还记得吗,最后一个就是版本号,此时我们能将版本号改为2,再次运行程序,此时就弹出了添加成功的信息。

添加数据

SQLiteDatabse 中提供了一个insert()方法,这个方法就是专门用于添加数据的:

  • 第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字
  • 第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般我们用不到这个功能,直接传入null即可
  • 第三个参数是一个ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。
Button buttonadd = (Button) findViewById(R.id.add_data);
buttonadd.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {SQLiteDatabase db = dphelper.getWritableDatabase();ContentValues values = new ContentValues();values.put("name", "Tombook");values.put("author", "Tom");values.put("price", 23.23);values.put("pages", 56);db.insert("book", null, values);values.clear();values.put("name", "Ailabook");values.put("author", "Aila");values.put("price", 56.23);values.put("pages", 99);db.insert("book", null, values);Toast.makeText(MainActivity.this, "hhhh", Toast.LENGTH_SHORT).show();}
});

更新数据

SQLite-Database中也提供了一个非常好用的 update()方法,用于对数据进行更新

  • 第一个参数和insert()方法一样,也是表名,在这里指定去更新哪张表里的数据
  • 第二个参数是ContentValues对象,要把更新数据在这里组装进去
  • 第三、第四个参数用于约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行
Button buttonupdata = (Button) findViewById(R.id.updata_data);
buttonupdata.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {SQLiteDatabase db = dphelper.getWritableDatabase();ContentValues values = new ContentValues();values.put("price", 12.12);db.update("book", values, "name = ?",new String[] {"Tombook"});}
});

第三个参数对应的是SQL语句的 where部分,表示更新所有name等于?的行,而?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。因此上述代码想表达的意图是将名字是Tombook的这本书的价格改成10.99。

删除数据

SQLite-Database中也提供了一个非常好用的 delete()方法,用于对数据进行删除

  • 第一个参数仍然为表名
  • 第二三个参数用于约束更新某一行或某几行中的数据,不指定的话默认就是删除所有行
Button buttondelete = (Button) findViewById(R.id.dalete_data);
buttondelete.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {SQLiteDatabase db = dphelper.getWritableDatabase();db.delete("book", "pages > ?", new String[] {"500"});}
});

查询数据

SQLite-Database中专门提供了一个非常好用的 query()方法,用于对数据进行查找,需要传入七个参数

  • 表名,即我们希望从哪张表中查询数据
  • 用于指定查询那几列,不指定则默认查所有列
  • 第三四个参数用于约束查询某一行或者某几行的数据,不指定则默认查询所有行的数据
  • 用于指定需要去group by的列,不指定则表示不对查询结果进行group by的操作(即进行分组)
  • 用于对进行group by之后的数据进行进一步的过滤,不指定则表示不进行过滤
  • 用于指定查询结果的排序方式,不指定则表示默认的排序方式

在这里插入图片描述

Button buttonquery = (Button) findViewById(R.id.query_data);
buttonquery.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {SQLiteDatabase db = dphelper.getWritableDatabase();Cursor cursor = db.query("book", null, null, null, null, null, null);if (cursor.moveToFirst()) {while (cursor.moveToNext()){@SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex("name"));@SuppressLint("Range") int pages = cursor.getInt(cursor.getColumnIndex("pages"));@SuppressLint("Range") double price = cursor.getDouble(cursor.getColumnIndex("price"));  }}cursor.close();}
});

文章到这里就结束了!

相关文章:

【Android】数据持久化——数据存储

持久化技术简介 在你打开完成了一份PPT之后关闭程序&#xff0c;再次打开肯定是希望之前的内容还存在在电脑上&#xff0c;一打开PPT&#xff0c;之前的内容就自动出现了。数据持久化就是将那些内存中的瞬时数据保存到存储设备中&#xff0c;保证即使在手机或电脑关机的情况下…...

如何通过谷歌外链快速增加网站流量?

利用谷歌外链提升流量的方法非常直接&#xff0c;但实际上&#xff0c;外链影响的是关键词排名&#xff0c;关键词排名提升了&#xff0c;自然就会有流量&#xff0c;所以谷歌外链不是直接能提升网站流量&#xff0c;而是间接的&#xff0c;下面&#xff0c;我会详细介绍几种有…...

vLLMcuda安装笔记

1. 引言 最近在部署Qwen模型时&#xff0c;文档上有提到强烈建议用vLLM来部署模型&#xff0c;按照公开的性能测试数据&#xff0c;用vLLM部署Qwen模型的文本推理速度要比transformers部署快3~4倍。带着这个好奇就开始安装尝试&#xff0c;但试下来这个安装过程并没有那么顺利…...

C++入门基本语法(2)

一、引用 1、基本概念与定义 引用不是新定义一个变量&#xff0c;而是给已存在的变量起一个别名&#xff0c;编译器不会为引用变量开辟内存空间&#xff0c;它和它所引用的变量公用同一块内存空间&#xff1b; 引用的写法&#xff1a;变量类型& 引用别名 变量&#xff…...

Internet Download Manager(IDM)2024中文版本有哪些新功能?6.42版本功能介绍

1. Internet Download Manager&#xff08;IDM&#xff09;是一款功能强大的下载管理器&#xff0c;支持所有流行的浏览器&#xff0c;并可提升下载速度高达5倍。 2. IDM具有智能下载逻辑加速器&#xff0c;可以设置文件下载优先级、分块下载等&#xff0c;提高下载效率。 IDM…...

深入理解 C 语言中的联合体

目录 引言 一、 联合体的定义与基本用法 1.联合体的定义 2.基本用法 二、 联合体与结构体的区别 1.结构体 2.联合体 3.对比 三、联合体的优势 1. 节省内存 2. 提高效率 3. 代码简洁性 四、联合体的存储细节 1.内存对齐 2.大小计算 五、联合体的高级用法 1.匿…...

OpenCV||超详细的几何变换

2D图像几何变换的33矩阵&#xff1a; 图像常见的几何变换&#xff1a; 图像来源&#xff1a;《OpenCV 4.5计算机视觉开发实战&#xff1a;基于Python》作者&#xff1a;朱文伟 李建英&#xff1b; 1. 平移&#xff08;Translation&#xff09; 在OpenCV中&#xff0c;平移不是…...

网络程序设计基础概述

文章目录 前言一、网络程序设计基础二、网络协议 1.IP协议2.TCP与UDP协议三、端口与套接字总结 前言 网络程序设计编写的是与其他计算机进行通信的程序代码。Java将网络程序所需要的东西封装成了不同的类。开发者只需要创建这些类的对象&#xff0c;调用相应的方法&#xff0c;…...

MySQL:数据库用户

数据库用户 在关系型数据库管理系统中&#xff0c;数据库用户&#xff08;USER&#xff09;是指具有特定权限和访问权限的登录账户。每个用户都有自己的用户名和密码&#xff0c;以便系统可以通过认证来识别他们的身份。数据库用户可以登录数据库&#xff0c;在其中执行各种类…...

用TensorFlow训练自己的第一个模型

现在学AI的一个优势就是&#xff1a;前人栽树后人乘凉&#xff0c;很多资料都已完善&#xff0c;而且有很多很棒的开源作品可以学习&#xff0c;感谢大佬们 项目 项目源码地址 视频教程地址 我在大佬的基础上基于此模型还加上了根据特征值缓存进行快速识别的方法&#xff0c;…...

MySQL数据库入门基础知识 【1】推荐

数据库就是储存和管理数据的仓库&#xff0c;对数据进行增删改查操作&#xff0c;其本质是一个软件。 首先数据有两种&#xff0c;一种是关系型数据库&#xff0c;另一种是非关系型数据库。 关系型数据库是以表的形式来存储数据&#xff0c;表和表之间可以有很多复杂的关系&a…...

Anaconda下的 jupyter notebook安装及使用

安装 打开Anaconda Powershell Prompt或Anconda Prompt 输入命令conda install jupyter notebook进行安装 启动 切换到工作目录&#xff0c;输入命令jupyter notebook等待浏览器打开网页 命令行启动jupyter notebook的链接复制到浏览器同样可以打开jupyter notebook 在Ancon…...

C语言初阶(11)

1.结构体定义 结构体就是一群数据类型的集合体。这些数据类型被称为成员变量。结构的成员可以是标量、数组、指针&#xff0c;甚至是其他结构体。 2.结构体的声明和结构体变量命名与初始化 结构体声明由以下结构组成 struct stu {char name[12];int age; }; 结构体命名有两…...

Unity获取Animator动画播放完成事件

整理了一些在日常经验中处理动画播放完成事件的方法 方法: 1.Dotween配合异步实现 2.状态机计时方法实现 3.原生动画行为方法实现 方法一&#xff1a;Dotween异步方法 using UnityEngine; using System.Threading.Tasks; using DG.Tweening;public class PlayerAnimAsync : M…...

git submodule 使用

在Git中&#xff0c;子模块&#xff08;submodule&#xff09;是一种将一个Git仓库作为另一个Git仓库的子目录嵌入的方式。这使得主仓库能够跟踪和管理对外部依赖的更改。 添加子模块 初始化父仓库&#xff1a;如果你还没有创建父仓库&#xff0c;先创建它。 添加子模块&…...

【Jenkins未授权访问漏洞 】

默认情况下 Jenkins面板中用户可以选择执行脚本界面来操作一些系统层命令&#xff0c;攻击者可通过未授权访问漏洞或者暴力破解用户密码等进入后台管理服务&#xff0c;通过脚本执行界面从而获取服务器权限。 第一步&#xff1a;使用fofa语句搜索 搜索语句&#xff1a; port&…...

前端处理 Excel 文件

引入XLSX XLSX 是一个流行的 JavaScript 库&#xff0c;用于处理 Excel 文件&#xff08;包括 .xls 和 .xlsx 格式&#xff09;。它可以在 Node.js 环境和浏览器中运行&#xff0c;提供了丰富的 API 来读取、写入、修改 Excel 文件。当你使用 import * as XLSX from xlsx; 这行…...

(vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束

(vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束 需求&#xff1a;按勾选的顺序给后端传值 难点&#xff1a;在 Element UI 的 el-cascader 组件中&#xff0c;默认的行为是根据数据的层级结构来显示选项&#xff0c;用户的选择也会基于这种层级结构&#xff0c;el-…...

Redis进阶(四):哨兵

为了解决主节点故障&#xff0c;需要人工操作切换主从的情况&#xff1b;因此需要一种方法可以自动化的切换&#xff1a;哨兵的引入大大改变这种情况。 哨兵的基本概念 自动切换主从节点 哨兵架构 1、当一个哨兵节点发现主节点挂了的时候&#xff0c;还需要其他节点也去检测一…...

蓝屏事件:网络安全的启示

“微软蓝屏”事件暴露了网络安全哪些问题&#xff1f; 近日&#xff0c;一次由微软视窗系统软件更新引发的全球性“微软蓝屏”事件&#xff0c;不仅成为科技领域的热点新闻&#xff0c;更是一次对全球IT基础设施韧性与安全性的深刻检验。这次事件&#xff0c;源于美国电脑安全技…...

技术方案评审原则

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录 系列文章目录前言技术方案评审原则1.理论突破阶段2.技术突破阶段3.工程化阶段自动驾驶行业的技术方案分析前言 认知有限,望大家多多包涵,有什么问题也希望能够与大…...

117页PPT埃森哲-物流行业信息化整体规划方案

一、埃森哲-物流行业信息化整体规划方案 资料下载方式&#xff0c;请看每张图片右下角信息 埃森哲在物流行业信息化整体规划项目中的核心内容&#xff0c;旨在帮助物流企业通过信息技术的应用实现业务流程的优化、运营效率的提升以及市场竞争力的增强。以下是埃森哲在此类项目…...

百度网盘不下载怎么直接打印文件?

在数字化时代&#xff0c;百度网盘作为我们存储和分享文件的重要工具&#xff0c;承载了大量的文档、图片和资料。然而&#xff0c;当需要打印这些文件时&#xff0c;很多用户会面临一个共同的问题&#xff1a;不想下载到本地再打印&#xff0c;既占用空间又浪费时间。那么&…...

设置了 robots.txt 禁止爬虫抓取,为什么还是能被百度搜索出来

虽然设置了 robots.txt 禁止爬虫抓取&#xff0c;但网页仍可能被百度搜索出来&#xff0c;主要有以下几个原因&#xff1a; robots.txt 只是一种建议性协议&#xff0c;并非强制性[2]。虽然大多数搜索引擎会遵守 robots.txt 的规则&#xff0c;但并不是所有爬虫都会严格遵守。 …...

DedeCMS-V5.7.82-UTF8织梦管理系统漏洞

将靶场环境放到www目录下——访问/dedecms/uploads 安装程序 - 织梦内容管理系统 V5.7 UTF8SP2 同意协议——继续 继续 配置后——点击继续 进入后台 登录后台——填写用户名密码。 方法一&#xff1a;上传shell文件 后台——核心——附件管理——上传新文件。 访问/dedecms…...

【Python】字符串练习题及代码示例

1、使用while循环实现对字符串中每个字符进行输出。 代码示例&#xff1a; 2、请将代码实现如下进制的转换。 &#xff08;1&#xff09;v1675,请将v1转换为二进制。 代码&#xff1a; 注意&#xff1a;将十进制数转换为二进制数的方法是&#xff1a;bin(a),a是整型&#x…...

fluent动网格profile udf 注意事项

案例一&#xff1a; ((profile_name transient 2 0) ....第一行 (time 0 15.0) ....第二行 (v_x 1.2 1.2)) …...

【doghead】mac构建 2: player 端 clion构建

准备工作 【doghead】mac构建 1 【doghead】mac: clion2024.1启动崩溃 mbp的 uv 构建ok zhangbin@zhangbin-mbp-2  ~/tet/Fargo/zhb-bifrost/Bifrost-202403/worker/third_party/libuv   main clion使用lldb cmake构建 更...

论网络流(最大流篇)--新手入门超详解--包教包会

论网络流--新手入门超详解--包教包会 1 前言2 什么是最大流3最大流问题的求解&#xff08;1&#xff09;问题转化--增广路的引入&#xff08;2&#xff09;走回头路--EK算法&#xff08;3&#xff09;EK的弊端&#xff08;4&#xff09;化图为树--DINIC算法 4后记 1 前言 网络…...

环境搭建:全面详尽的 MongoDB Shell MongoDB Server介绍、安装、验证与配置指南(以 Windows 系统为主)

环境搭建&#xff1a;全面详尽的 MongoDB Shell & MongoDB Server介绍、安装、验证与配置指南&#xff08;以 Windows 系统为主&#xff09; MongoDB 是一个基于文档的 NoSQL 数据库&#xff0c;以其高性能、灵活性和可扩展性而受到广泛欢迎。本文将带您完成 MongoDB 的安装…...

营销网站建设模板/品牌策划推广方案

1.自己写返回主键keyProperty"userId"中userId对应的值是领域模型TbSysUser中对应的userId;不是数据库中对应的字段名<!-- 测试插入返回主键 --><insert id"addUser" parameterType"com.czht.wdp.core.sys.pojo.TbSysUser" useGenerat…...

小型购物网站开发/苏州百度推广公司

ESC 退出 0 进度条开关 1 屏幕原始大小 2 屏幕1/2大小 3 屏幕1/3大小 4 屏幕1/4大小 S 下一帧 [ -2秒 ] 2秒 ; -1秒1秒 < -0.05秒 > 下一个帧 -> -5秒ffmpeg-20160811-bin.7z转载于:https://www.cnblo…...

企业网站建立平台/seo是干嘛的

一、初识HMM隐马尔科夫模型&#xff08;Hidden Markov Model&#xff0c;简称HMM&#xff09;是用来描述隐含未知参数的统计模型&#xff0c;HMM已经被成功于语音识别、文本分类、生物信息科学、故障诊断和寿命预测等领域。HMM可以由三个要素组成&#xff1a; &#xff08;A,B,…...

苏州网站开发公司电话/网店代运营公司靠谱吗

今天遇到一个问题&#xff1a;pc客户端和android的App通信&#xff0c;心跳通道&#xff08;心跳包27个字节&#xff0c;是一个业务空包&#xff09;在部分pc上总是会超时&#xff08;5秒超时&#xff09;&#xff0c;nagle算法也给禁用了&#xff0c;pc端时按按量发送心跳的&a…...

微信网站建设报价/seo静态页源码

设 计 总 说 明 现在社会随着计算机技术迅速发展与技术的逐渐成熟&#xff0c;信息技术已经使人们的生活发生深刻的变化。生活中的各种服务系统也使人们在生活中的联系日常销售活动方式发生了很大的变化&#xff0c;让效率较低的手工操作成为过去&#xff0c;而换成信息化自动化…...

大学 建网站/毕节地seo

关于二叉排序树的定义以及如何查找指定关键字的结点不再赘述&#xff0c;本篇文章主要讨论二叉排序树如何删除一个指定的结点。 当我们利用查找算法在树中找到了对应的结点的时候&#xff0c;可能会遇到三类情况。 第一类情况&#xff1a; 即将删除的结点是叶结点&#xff0…...