0907Android数据存储

本文详细介绍了Android中的多种数据存储方式,包括内部存储、外部存储、SharedPreferences、文件存储、SQLite数据库及ContentProvider等内容。针对每种存储方式的特点和应用场景进行了深入探讨,并提供了丰富的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考: 
  基础知识充电http://blog.youkuaiyun.com/hitlion2008/article/details/6802576
  权限大全http://www.cnblogs.com/classic/archive/2011/06/20/2085055.html

数据存储 

  1. Internal Storage内部存储空间
    • SharedPreferences共享偏好
    • SQLite Database数据库
    • 文件存储
  2. External Storage外部存储空间
  3. Internet网络(不做介绍)
  4. CountentProvider共享数据(androird四大组件之一)
      

在androidstudio中找到产生文件的位置

   打开android device monitor,选中模拟器这里写图片描述,选中右侧file Explorer,打开data文件夹下面的data文件夹
找到相应的包,选中相应xml文件,通过右上角的这里写图片描述键导出。 
这里写图片描述 
  

External Storage外部存储空间

  外部存储空间是指手机出厂的时候不存在,用户在使用时候可以自由添加的外部存储介质比如TS卡,SD卡等闪存储介质。
  因为是外部存储,访问时需要加权限。
  在mainfest的package下面增加外部存储读写允许的权限。

 <!-- 写入外部存储读写权限-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
    <!-- 拔插式扩展卡读写权限-->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission>

  外部存储卡的路径是“/mnt/sdcard”,所以你直接这样写去访问也能访问的到,鉴于可读性,通过 File path=Environment.getExternalStorageDirectory()来获取路径

存储数据

private void write_local() {
        File file=new File(Environment.getExternalStorageDirectory(),"helloWorld.txt");
        if (!file.exists()){
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            FileOutputStream outputStream=new FileOutputStream(file);
            BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(outputStream));
            writer.write(mEditText.getText().toString());
            writer.flush();
            writer.close();
            outputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

读取数据

private void read_local() {
        File file=new File(Environment.getExternalStorageDirectory(),"helloWorld.txt");
        try {
            FileInputStream inputStream=new FileInputStream(file);
            BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
            String line=reader.readLine();
//        只有一行,所以没有加循环
            mTextView.setText(line);
            reader.close();
            inputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

这里写图片描述

CountentProvider共享数据

  虽然是四大组件之一,可是老师说这家伙平常的使用率和它响当当的名头完全不成正比,平常搞开发基本用不到╮(╯▽╰)╭。本鸡肋主要用于在不同的应用程序之间实现数据共享的功能。
  访问内容提供器中的数据,借助ContentResolve,通过getContentResolve()方法获取到该类的实例。其中对数据进行CRUD操作的方法和数据库类似,参数上面稍微有点差别。
  进行CRUD操作是用Uri代替表名。URI由两部分组成,权限和路径。例:content://com.example.app.provider/table1。content://是协议声明;com.example.app.provider是权限(通常利用包名+.provider命名);/table1为路径。
  这家伙在使用之前要加权限

<uses-permission android:name="android.permission.READ_CONTACTS"/>

查询手机中的联系人以及电话demo

package com.example.laowang.mycontentprovider;

import android.app.Activity;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {
    private Button mBtn;
    private ContentResolver mContentResolver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBtn = (Button) findViewById(R.id.button);

        mContentResolver = getContentResolver();

        mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
                Cursor cursor = mContentResolver.query(uri, new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME}, null, null, null);
                cursor.moveToFirst();
                while (!cursor.isAfterLast()) {
                    String[] names = cursor.getColumnNames();
                    StringBuffer buffer = new StringBuffer();
                    for (String name : names) {
                        String value =cursor.getString(cursor.getColumnIndex(name));
                        buffer.append("字段名:  " + name + "字段值:  " + value);
                    }
                    Log.d("data", "联系人"+buffer);
                    cursor.moveToNext();
                }

            }
        });
    }
}

这里写图片描述

这里写图片描述

Internal Storage内部存储空间

  内部存储空间即手机内置的存储空间,出厂后就无法修改,储存空间有限,Shared Preferences和文件存储以及SQLite数据库都是存储在内部存储空间上

SharedPreferences共享偏好

  采用键值对的方式存储数据。只能用于存储基本数据类型。事实上,在Android当中SharedPreferences使用最多的地方也是用来保存配置(Settings)信息,系统中的Settings中这样,各个应用中的Settings也是这样。

获得对象

  使用SharedPreferences来存储数据,首先获得它的对象。俩种方法获得对象。
  第一个方法是使用Context类中的getSharedPreferences()方法。其中第一个参数用于指定的getSharedPreferences的文件的名称,如果不存在会创建一个。第二个参数用于指定操作模式MODE_PRIVATE和MODE_MULTI_PROCESS,MODE_PRIVATE仍然是默认的操作模式,表示只有当前的程序才可以对这个getSharedPreferences文件进行读写。MODE_MULTI_PROCESS一般用于多个进程中对用一个getSharedPreferences进行读写的情况。

 SharedPreferences preferences_read=getSharedPreferences("sharedPreferences_test", MODE_PRIVATE);

  
第二个方法是使用Activity类中的getPreference()方法

haredPreferences preferences_write=getPreferences(MODE_PRIVATE);

  它的源码

 public SharedPreferences getPreferences(int mode) {
        return getSharedPreferences(getLocalClassName(), mode);
    }

  自动返回当前应用程序的包名作为前缀来命名文件。

存储数据
  • 调用SharedPreference().edit创建一个SharedPreferences.Editor对象
  • 调用SharedPreferences.Editor对象添加数据,API中有很多put方法。
  • 调用commit()提交数据,完成数据库存储操作。API中要求: All changes you make in an editor are batched, and not copied back to the original SharedPreferences until you call commit() or apply()
//        SharedPreferences preferences_write=getSharedPreferences("sharedPreferences_test", MODE_PRIVATE);
        SharedPreferences preferences_write=getPreferences(MODE_PRIVATE);

//                穿件editor对象
        SharedPreferences.Editor editor=preferences_write.edit();
//                写入数据,数据名(key)+数据
        editor.putString("edittext_put",mEditText.getText().toString());

        editor.commit();
读取数据

  SharedPreferences中提供了一些get方法用于对存储数据的读取。这些get方法都有两个参数get(key,默认值)

        SharedPreferences preferences_read=getPreferences(MODE_PRIVATE);
//       第一个参数传入key值 第二个参数是默认返回值  String defValue
        String content=preferences_read.getString("edittext_put","我是默认返回值");
         mTextView.setText(content);

这里写图片描述

文件存储  

  文件存储,不对存储内容进行处理,原封不动的存到文件中。适合存储简单的文本数据或二进制数据。文件默认存储的位置在data/data/file中。
  

存储数据

  Context类中的openFileOutput()方法可以用于将数据存储到指定的文件中。openFileOutput(文件名,操作模式),文件名中不可以包含路径。操作模式,MODE_PRIVATE和MODE_APPEND。默认MODE_PRIVATE,表示同文件名时,会覆盖原文件的内容,MODE_APPEND则是追加内容。
  默认路径下

 private void preference_write_cache() {
        try {
            FileOutputStream outputStream=openFileOutput("helloCache",MODE_PRIVATE);
            BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(outputStream));
            writer.write(mEditText.getText().toString());
            writer.flush();
            writer.close();
            outputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

  在指定路径在data/data/cache文件下创建文件

    private void write_dir_cache() {
//        新建文件(路径,名字)
        File file=new File(getCacheDir(),"helloDir");
//        如果文件没有产生,则产生文件
        if (!file.exists()){
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            FileOutputStream outputStream=new FileOutputStream(file);
            BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(outputStream));
            writer.write(mEditText.getText().toString());
            writer.flush();
            writer.close();
            outputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
读取数据

  同样的Context类中的一个方法openFileInput(),只有一个参数:要读取的文件名。系统会自动到默认目录下加载文件。

  private void preference_read_cache() {
        try {
            FileInputStream inputStream=openFileInput("helloCache");
            BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
            String line=reader.readLine();
            mTextView.setText(line);
            reader.close();
            inputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

  加载指定文件夹下的文件

private void read_dir_cache() {
        try {
            File file=new File(getCacheDir(),"helloDir");
            FileInputStream inputStream=new FileInputStream(file);
            BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
            String line=reader.readLine();
//        只有一行,所以没有加循环
            mTextView.setText(line);
            reader.close();
            inputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

这里写图片描述
这里写图片描述

SQLiteDatabase数据库

SQLiteDatabase适用于存储大量复杂的关系型数据。

  Android提供了SQLiteOpenHelper帮助类,对数据库进行创建和升级。
  SQLiteOpenHelper是抽象类,所以需要创建类来继承他并重写他的方法onCreate()和onUpdate()。
  俩个实例化方法:getReadableDatabase()和getWritableDatabase(),这两个方法都可以创建或者打开一个数据库,并返回一个可以对数据库进行读写操作的对象。区别在于打开已满的数据库时,getReadableDatabase()返回的对象只能进行读取操作,儿另一个家伙直接报错。
  SQLiteOpenHelper有俩个构造方法供重写,一般使用参数比较少的那个。第一个参数是content,上下文。第二个是数据库名,创建数据库时使用的就是这个指定的名称。第三个参数可返回一自定义的Cursor,一般是传入null。第四个参数表示当前的数据库的版本号。实例化SQLiteOpenHelper之后, 在调用getReadableDatabase()或者getWritableDatabase()就可以创建数据库了,数据库会存放在data/data///database中。
  
  数据库的基本操作,创建数据库,CRUD(create,retrieve(查询),update,delete)。

创建数据库

  在SQLiteOpenHelper复写的onCreate中创建表。

 public void onCreate(SQLiteDatabase db) {
//        creatTable
        db.execSQL("create table if not exists student(id integer primary key autoincrement,name varchar(20),password varchar(20))");
    }
活动中创建数据库
//        创建SQLiteOpenHelper对象
        TestSqliteOpenHelper helper=new TestSqliteOpenHelper(getApplicationContext(),"MY_FIRST_DB.db");
//        打开或者创建一个数据库,实例化
        SQLiteDatabase db=helper.getWritableDatabase();
添加数据

  SQLiteDatebase 中提供了insert(),接受三个参数。
  insert(String table, String nullColumnHack, ContentValues values)
  第一个参数是要传入数据的表名,第二个参数用于给指定在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般用不着,直接传入null,第三个参数是一个ContentValues 对象,提供了一系列的put方法重载,用于向ContentValues 中添加数据,只需要将表中的列名以及相应的数据填入即可。

 private void insert() {
        ContentValues valuesAdd=new ContentValues();
        valuesAdd.put("name",mEditTextUserName.getText().toString());
        valuesAdd.put("password", mEditTextPassword.getText().toString());
        db.insert("student", null, valuesAdd);
////        插入第一条数据
//        valuesAdd.put("name","lihua");
//        valuesAdd.put("password", "456");
//        db.insert("student", null, valuesAdd);
//        valuesAdd.clear();//每次重新装数据都需要重载一下
////        插入第二条数据
//        valuesAdd.put("name","xiaoming");
//        valuesAdd.put("password", "123");
//        db.insert("student", null, valuesAdd);
//        valuesAdd.clear();
////        插入第三条数据
//        valuesAdd.put("name","lisi");
//        valuesAdd.put("password", "789");
//        db.insert("student", null, valuesAdd);
        Toast.makeText(getApplicationContext(), "添加数据成功", Toast.LENGTH_SHORT).show();
    }
删除数据

  SQLiteDatebase 中提供了delete()
  delete(String table, String whereClause, String[] whereArgs)
  第一个参数仍然是表名,第二三个参数用于约束删除某一行或者某几行的数据,默认null的话就是删除所有行。

 private void delete() {
        //                删除name为李华的数据
//        db.delete("student","name=?",new String[]{lihua});
        db.delete("student",null,null);//全部删除
        Toast.makeText(getApplicationContext(), "删除数据成功", Toast.LENGTH_SHORT).show();
    }
更新数据

  update(String table, ContentValues values, String whereClause, String[] whereArgs)
  第一个参数还是表名,第二个参数是ContentValues对象,把要更新的数据组装进去。三四个参数用来约束更新某一行或者某几行。不指定的话就是更新所有行。

 private void update() {
        ContentValues valuesUpdate=new ContentValues();
        valuesUpdate.put("name","zhangsan");
//                将name为lihua的数据中name改为zhangsan
        db.update("student", valuesUpdate, "name=?", new String[]{"lihua"});
        Toast.makeText(getApplicationContext(), "更新数据成功", Toast.LENGTH_SHORT).show();
    }
查询数据

  提供了很多方法。。。取俩常用的
  rawQuery(String sql, String[] selectionArgs)
  第一个参数传入sql命令,第二个是约束条件
  query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
  。。。参数多的要命,依次是:指定查询的表名,指定查询的列名,指定where的约束条件,为where中的占位符提供具体的值,指定需要group by的列,对group by后的结果进一步约束,指定查询结果的排序方式,限制查询数据的数目以及偏移量。
  调用query()方法后会返回一个Cursor对象,查询到的数据从这个对象中取出。

 private void query() {
//          找到所有数据
//            Cursor cursor=db.rawQuery("select * from student",null);
//        以id为标准倒序排列,每次显示三条,offset表示顺序开始的前几条数据,这里设置为2.DESC为降序,ASC为升序
        Cursor cursor=db.query("student",null,null,null,null,null,"name DESC","2,3");//limit语句 偏移量,查询的数量
        if(cursor.moveToFirst()){
            while(!cursor.isAfterLast()){
//              通过得到索引  得到属性,也可以直接传入列数getString(1),效果一样;
               String name=cursor.getString(cursor.getColumnIndex("name"));
               String password=cursor.getString(cursor.getColumnIndex("password"));
                Log.d("查询到的结果", "student name is" + name);
                Log.d("查询到的结果","student password is"+password);
                Toast.makeText(getApplicationContext(), "查询数据成功:姓名 "+name+"密码"+password, Toast.LENGTH_SHORT).show();
//                移到下一个数据
                cursor.moveToNext();
            }

        }
        cursor.close();
    }
DEMO

活动

package com.example.laowang.sqlitedemo;

import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.example.laowang.sqlitedemo.db.TestSqliteOpenHelper;

import java.io.BufferedReader;

public class MainActivity extends Activity implements View.OnClickListener{
    private SQLiteDatabase db;
    private TestSqliteOpenHelper helper;
    private Button mBtnCreateDb;
    private Button mBtnInsertDb;
    private Button mBtnDeleteDb;
    private Button mBtnUpdateDb;
    private Button mBtnQueryDb;
    private EditText mEditTextUserName;
    private EditText mEditTextPassword;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBtnCreateDb= (Button) findViewById(R.id.button_creat_db);
        mBtnInsertDb= (Button) findViewById(R.id.button_insert_db);
        mBtnDeleteDb= (Button) findViewById(R.id.button_delete_db);
        mBtnUpdateDb= (Button) findViewById(R.id.button_update_db);
        mBtnQueryDb= (Button) findViewById(R.id.button_query_db);
        mEditTextUserName= (EditText) findViewById(R.id.edittext_username);
        mEditTextPassword= (EditText) findViewById(R.id.edittext_password);


        mBtnInsertDb.setOnClickListener(this);
        mBtnCreateDb.setOnClickListener(this);
        mBtnQueryDb.setOnClickListener(this);
        mBtnUpdateDb.setOnClickListener(this);
        mBtnDeleteDb.setOnClickListener(this);
//        创建SQLiteOpenHelper对象
        helper=new TestSqliteOpenHelper(getApplicationContext(),"MY_FIRST_DB.db");
//        打开或者创建一个数据库,实例化
        db=helper.getWritableDatabase();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button_creat_db:

                Toast.makeText(getApplicationContext(),"创建了数据库",Toast.LENGTH_SHORT).show();
                break;

            case R.id.button_insert_db:
                insert();
                break;

            case R.id.button_delete_db:
                delete();

                break;

            case R.id.button_update_db:
                update();

                break;
            case R.id.button_query_db:
                query();
                break;
            default:
                break;
        }
    }

    private void query() {
//          找到所有数据
//            Cursor cursor=db.rawQuery("select * from student",null);
//        以id为标准倒序排列,每次显示三条,offset表示顺序开始的前几条数据,这里设置为2.DESC为降序,ASC为升序
        Cursor cursor=db.query("student",null,null,null,null,null,"name DESC","2,3");//limit语句 偏移量,查询的数量
        if(cursor.moveToFirst()){
            while(!cursor.isAfterLast()){
//              通过得到索引  得到属性,也可以直接传入列数getString(1),效果一样;
               String name=cursor.getString(cursor.getColumnIndex("name"));
               String password=cursor.getString(cursor.getColumnIndex("password"));
                Log.d("查询到的结果", "student name is" + name);
                Log.d("查询到的结果","student password is"+password);
                Toast.makeText(getApplicationContext(), "查询数据成功:姓名 "+name+"密码"+password, Toast.LENGTH_SHORT).show();
//                移到下一个数据
                cursor.moveToNext();
            }

        }
        cursor.close();
    }

    private void update() {
        ContentValues valuesUpdate=new ContentValues();
        valuesUpdate.put("name","zhangsan");
//                将name为lihua的数据中name改为zhangsan
        db.update("student", valuesUpdate, "name=?", new String[]{"lihua"});
        Toast.makeText(getApplicationContext(), "更新数据成功", Toast.LENGTH_SHORT).show();
    }

    private void delete() {
        //                删除name为李华的数据
//        db.delete("student","name=?",new String[]{lihua});
        db.delete("student",null,null);//全部删除
        Toast.makeText(getApplicationContext(), "删除数据成功", Toast.LENGTH_SHORT).show();
    }

    private void insert() {
        ContentValues valuesAdd=new ContentValues();
        valuesAdd.put("name",mEditTextUserName.getText().toString());
        valuesAdd.put("password", mEditTextPassword.getText().toString());
        db.insert("student", null, valuesAdd);
////        插入第一条数据
//        valuesAdd.put("name","lihua");
//        valuesAdd.put("password", "456");
//        db.insert("student", null, valuesAdd);
//        valuesAdd.clear();//每次重新装数据都需要重载一下
////        插入第二条数据
//        valuesAdd.put("name","xiaoming");
//        valuesAdd.put("password", "123");
//        db.insert("student", null, valuesAdd);
//        valuesAdd.clear();
////        插入第三条数据
//        valuesAdd.put("name","lisi");
//        valuesAdd.put("password", "789");
//        db.insert("student", null, valuesAdd);
        Toast.makeText(getApplicationContext(), "添加数据成功", Toast.LENGTH_SHORT).show();
    }
}

SQLiteOpenHelper实例

package com.example.laowang.sqlitedemo.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
 * Created by Administrator on 2015/9/7.
 */
public class TestSqliteOpenHelper extends SQLiteOpenHelper {

//    creatdatebase
    public TestSqliteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }
//    固定构造器后两个参数的写法
    public TestSqliteOpenHelper(Context context, String name){
        this(context, name, null, 1);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
//        creatTable
        db.execSQL("create table if not exists student(id integer primary key autoincrement,name varchar(20),password varchar(20))");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">
    <EditText
        android:id="@+id/edittext_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:background="@drawable/edittext_background"
        android:hint="请输入用户名"/>
    <EditText
        android:id="@+id/edittext_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:background="@drawable/edittext_background"
        android:hint="请输入密码"/>
    <Button
        android:id="@+id/button_creat_db"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="创建数据库"/>
    <Button
        android:id="@+id/button_insert_db"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="插入数据"/>
    <Button
        android:id="@+id/button_update_db"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="更新数据"/>
    <Button
        android:id="@+id/button_delete_db"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="删除数据"/>
    <Button
        android:id="@+id/button_query_db"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="查询数据"/>

</LinearLayout>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值