Android数据存储技术

一、文件存储

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

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

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

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

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

二、SharedPreferences存储

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

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

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

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

三、 SQLite数据库存储

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

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

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

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

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

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

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

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

package com.jpc.filepersistencetest

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

相关文章

树莓派安装Windows搭建网盘和下载机

0 需求分析 在同一个局域网内&#xff0c;同时有多种设备&#xff08;Windows&#xff0c;Linux&#xff0c;Android&#xff09;需要进行大量的数据共享。另外&#xff0c;还时常需要从百度网盘/夸克网盘等网盘下载文件。不难看出&#xff0c;我的需求很简单&#xff0c;就是…

异常的处理

异常处理概述 在编写程序时&#xff0c;经常要在可能出现错误的地方加上检测的代码&#xff0c;如进行x/y运算时&#xff0c;要检测分母为0&#xff0c;数据为空&#xff0c;输入的不是数据而是字符等。过多的if-else分支会导致程序的代码加长、臃肿&#xff0c;可读性差&…

论文笔记:Large Language Models as Analogical Reasoners

iclr 2024 reviewer打分5558 1 intro 基于CoT prompt的大模型能够更好地解决复杂推理问题 然而传统CoT需要提供相关的例子作为指导&#xff0c;这就增加了人工标注的成本——>Zero-shot CoT避免了人工标注来引导推理 但是对于一些复杂的任务难以完成推理&#xff0c;例如c…

Ubuntu22.04中基于Qt开发Android App

文章目录 前言在Ubuntu22.04中配置开发环境案例测试参考 前言 使用Qt开发手机应用程序是一种高效且灵活的选择。Qt作为一个跨平台的开发框架&#xff0c;为开发者提供了统一的开发体验和丰富的功能库。首先&#xff0c;Qt的跨平台性让开发者可以使用相同的代码库在不同的操作系…

SSM项目实战——哈哈音乐(四)前台模块开发

1、项目准备 ①导入依赖和前端资源 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.x…

路由策略与路由控制之双点双向重发布(OSPF-ISIS)实验

双点双向重发布在路由协议中&#xff0c;特别是在OSPF&#xff08;开放式最短路径优先&#xff09;与IS-IS&#xff08;中间系统到中间系统&#xff09;等协议之间&#xff0c;指的是在两个协议间或者两个进程间进行路由信息共享的机制。这种机制涉及到在两个不同的协议区域使用…

微软推出GPT-4 Turbo优先使用权:Copilot for Microsoft 365商业用户享受无限制对话及增强图像生成能力

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

电脑上音频太多,播放速度又不一致,如何批量调节音频播放速度?

批量调节音频速度是现代音频处理中的一个重要环节&#xff0c;尤其在音乐制作、电影剪辑、有声书制作等领域&#xff0c;它能够帮助制作者快速高效地调整音频的播放速度&#xff0c;从而满足特定的制作需求。本文将详细介绍批量调节音频速度的方法、技巧和注意事项&#xff0c;…

Docker 安装 Linux 系统可视化监控 Netdata

docker 安装 netdata 前提准备Docker 两种方式部署 Netdata1、使用 docker run 命令运行 netdata 服务2、使用 docker compose 运行 netdata 服务 Netdata 服务可视化界面Netdata 汉化处理 前提准备 说明&#xff1a;此处使用 windows11 安装的 docker desktop & wsl2/apli…

【Rust】环境搭建

Rust 支持很多的集成开发环境&#xff08;IDE&#xff09;或开发专用的文本编辑器。 官方网站公布支持的工具如下&#xff08;工具 - Rust 程序设计语言&#xff09; 本课程将使用 Visual Studio Code 作为我们的开发环境&#xff08;Eclipse 有专用于 Rust 开发的版本&#…

政安晨:【Keras机器学习实践要点】(十七)—— 利用 EfficientNet 通过微调进行图像分类

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实战 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 本文目标&#xff1a; 使用 EfficientNet 和在图…

比nestjs更优雅的ts控制反转策略-依赖查找

一、Cabloy5.0内测预告 Cabloy5.0采用TS对整个全栈框架进行了脱胎换骨般的大重构&#xff0c;并且提供了更加优雅的ts控制反转策略&#xff0c;让我们的业务开发更加快捷顺畅 1. 新旧技术栈对比&#xff1a; 后端前端旧版js、egg2.0、mysqljs、vue2、framework7新版ts、egg3…

Git 如何去使用

目录 1. Git暂存区的使用 1.1. 暂存区的作用 1.2. 暂存区覆盖工作区&#xff08;注意&#xff1a;完全确认覆盖时使用&#xff09; 1.3. 暂存区移除文件 1.4. 练习 2. Git回退版本 2.1. 概念 2.2. 查看提交历史 2.3. 回退命令 2.4. 注意 3. Git删除文件 3.1. 需求 …

Centos7使用docker安装Jenkins

一、下载Jenkins docker pull jenkins/jenkins:lts 二、启动Jenkins docker run \-u root \--rm \-d \-p 8081:8080 \-p 50000:50000 \-v /root/docker/jenkins/var/jenkins_home:/var/jenkins_home \-v /var/run/docker.sock:/var/run/docker.sock \-v /usr/bin/docker:/usr…

苹果CMSV10整合dplayer播放器增加记忆+P2P播放+自动下一集功能插件详细保姆级教程

1.本站已打包&#xff0c;可以直接下载然后上传到你服务器的苹果CMS网站static/player目录下就可以了&#xff0c;下面是教程 wlm3u8.js代码&#xff1a; MacPlayer.Html <iframe border"0" src"mac<span class"wp_keywordlink"><a hre…

Pandas:sort_index、sort_values方法的使用

sort_index和sort_values既是Series类型数据自带的方法&#xff0c;也是DataFrame数据自带的方法。本篇博客以DataFrame为例进行讲述。 1 概览 sort_index和sort_values可以将DataFrame中的数据按照索引及值的大小进行排序。这两个方法所包含的参数及其作用都基本一致。如下表…

注解,自定义注解和元注解

1.注解 1.1.注解概述、作用 注解&#xff08;Annotation&#xff09;&#xff0c;也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性&#xff0c;与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面&#xff0…

Unity类银河恶魔城学习记录12-3 p125 Limit Inventory Slots源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Inventory.cs using Newtonsoft.Json.Linq; using System.Collections; us…

Django--admin 后台管理站点

Django最大的优点之一&#xff0c;就是体贴的提供了一个基于项目model创建的一个后台管理站点admin。这个界面只给站点管理员使用&#xff0c;并不对大众开放。虽然admin的界面可能不是那么美观&#xff0c;功能不是那么强大&#xff0c;内容不一定符合你的要求&#xff0c;但是…

正则表达式(2)

文章目录 专栏导读1、贪婪与非贪婪2、转义匹配 专栏导读 ✍ 作者简介&#xff1a;i阿极&#xff0c;CSDN 数据分析领域优质创作者&#xff0c;专注于分享python数据分析领域知识。 ✍ 本文录入于《python网络爬虫实战教学》&#xff0c;本专栏针对大学生、初级数据分析工程师精…