Android数据存储:数据库基础,在Android应用中使用数据库,附加Menu菜单选项的一些说明
为什么**持久性数据很重要:
在计分器那个应用中,是将球队得分存储在变量里,当我们旋转应用时Activity被销毁,得分记录也没有了。将设备旋转为新的方向时,Activity被重现创建,得分又变成默认值0,因此需要一个更永久的方式来存储用户的数据,这一概念就叫做数据持久性。方式有两种,存到文件中或者数据库中。
Android提供了几个不同的数据持久性选项用来将数据存储到本地存储器中
还可以将数据保存到云端,但这更加复杂,需要连接互联网,这里只讨论将数据直接保存到Android设备中。
三种Android存储方式:1.Files;2.SharedPerferences;3.SQLite Databases
1.文件存储用于存储大型的媒体文件;
2.SharedPerferences用于在应用中存储用户的偏好设置,关闭应用后系统会记住这一偏好设置。SharedPerferences使用键值对来保存少量的数据。
3.SQLite数据库(数据库是有组织的数据集合,信息经过合理组织后,我们就可以从中提取所需的信息并加以分析。每列都表示了对象的不同属性,数据库通对大量信息进行整理,使我们能够轻松地搜索和排序所有这些信息。)SQLite是一种数据库,使我们能够在我们的应用在其运行的设备上创建一个本地数据库,Lite一词是指典型数据库的轻量级版本,相反地重量级的数据就如MySQL之类,MySQL可以提供更加复杂的功能, SQL是免费开源的,并且是自带的数据库,这也就是为何我们需要学习这一特定类型的数据库。无需下载安装任何内容,就能在Android应用中使用SQLite
通常Android应用会结合使用这三种方式。
在应用开发中,一开始一般是构建一个MVP,MVP(Minimum Viable Product)表示最小可行性产品。存储所认为最终要的属性
一个数据库可以包括多个表格,在决定要使用SQLite数据库时,首先要想清楚的是你需要什么表格,表格的列应该是什么内容
SQLite命令
SQLite 命令
sqlite3 在命令提示符窗口里打开 sqlite3 程序的命令。
sqlite3 shelter.db
打开 sqlite3 程序并直接打开以下数据库文件的命令。你可以将“shelter.db”替换为任何现有的数据库文件。
.open shelter.db
sqlite 程序中用来打开数据库文件的命令。在这里,我们打开的是 shelter.db 文件。你可以将“shelter.db”替换为任何现有的其他 .db 文件。
.help
调出可用命令列表的命令
.quit
退出 sqlite 应用的命令
echo %cd%
echo 请求计算机向屏幕上输出内容。在这里,我们使用 %cd%
来要求计算机输出“当前目录”(即我们当前所处的目录)。(限于 Windows 系统)
.header <on|off>
Turn display headers on or off
.help
Display the help menu listing dot commands
.mode <mode>
Set the output mode to one of these options - ascii, csv, column, html,
insert, line, list, tabs, tcl
.open <filename>
Close the existing database and open the file name given
.quit
Exit the program
.schema <table_name>
Show the CREATE statement used to generate the table listed
.tables
List names of tables
PRAGMA_TABLE_INFO(table_name);
,该指令会显示一些有用的信息,包括所有列的名称和类型。这是个SQL指令,注意要以;
分号结尾,而上面那些有.
的是SQLite3的指令,不要将这两种类型的指令弄混了,另外SQLite指令是区分大小写的,也就是.help
和.HELP
是有区别的,SQLite无法识别.HELP
这个指令,而SQL关键字不区分大小写。
数据库的结构化查询语言SQL
数据库中的数据类型NULL
代表空值,INTEGER
为整型,REAL
为浮点型,TEXT
为字符串,BLOB
表示数据完全按照输入的样式存储起来(例如图片或者二进制数据),SQLite中并没有单独的布尔型类型,更有效的方式是将布尔型存储为整型,false为数字0,true为1
实际上整型还非常适合存储某种物品的不同类型,比如说耳机有入耳式为1,包耳式为2,骨传导为3,若是直接存储为为本容易出错而且会占用更大的空间
如何向数据库添加表格
格式:
CREATE TABLE <table_name>(
<column_name_1> <data_type_1>,
<column_name_2> <data_type_2>,
...);
example:
CREATE TABLE pets (_id INTEGER,name TEXT,breed TEXT,gender INTEGER,weight INTEGER);
Android的惯常格式是_id前面有个下划线
向数据表中读取数据(查)
读取表格中特定列数据
格式:
SELECT <columns>
FROM <table_name>;
example:
SELECT name, breed from
pets;
读取表格中的所有行和列
格式:
SELECT * FROM <table_name>;
example:
SELECT * FROM pets;
向数据表中插入数据(增)
格式:
INSERT INTO <table_name>(<column_name_1>,<column_name_2>,
…)
VALUES (<values_1>,<values_2>,
…);
example:
INSERT INTO pets (_id,name,breed,gender,weight)
VALUES (1,"Tommy","Pomeranian",1,4);
向数据表中更新数据(改)
格式:
UPDATE <table_name>
SET <column_name> = <value>
WHERE <condition>;
example:
UPDATE pets
SET weight = 18
WHERE _id = 5;
向数据表中删除数据(删)
格式:
DELETE FROM <table_name> WHERE
<condition>;
example:
DELETE FROM pets WHERE _id = 1;
将表格中的所有内容删除掉(一定要谨慎使用该命令)
DELETE FROM <table_name>
删除数据表 (一定要谨慎地使用该命令)
格式:
DROP TABLE <table_name>;
example:
DROP TABLE pets;
SQL关键字
PRIMARY KEY:这个关键字会将相关的列标为唯一的标识的行,可以确保唯一性。每个表格只能有一个主键。
example:
CREATE TABLE headphones (_id INTEGER PRIMARY KEY,name TEXT,price INTEGER,style INTEGER,in_stock INTEGER,description TEXT);
AUTOINCREMENT :PRIMARY KEY通常和AUTOINCREMENT这个关键字一起使用,这个关键字可以确保ID列是唯一的,每当增加一行时,自动生成新的唯一的ID,即使在插入新数据的时候没有注明id这一列,id也会自动加一。
example:
CREATE TABLE headphones (_id INTEGER PRIMARY KEYAUTOINCREMENT,
name TEXT,
price INTEGER,
style INTEGER,
in_stock INTEGER,
description TEXT);
NOT NULL: 表示该列必须具有相关的值
example:
CREATE TABLE headphones (_id INTEGER PRIMARY KEYAUTOINCREMENT,name TEXT NOT NULL,price INTEGER,style INTEGER,in_stock INTEGER NOT NULLDEFAULT 0,description TEXT);
DEFAULT < value>: 表示在没有给定其他值时,会添加一个默认值。
example:
CREATE TABLE headphones (_id INTEGER PRIMARY KEYAUTOINCREMENT,name TEXT NOT NULL,price INTEGER,style INTEGER,in_stock INTEGER NOT NULLDEFAULT 0,description TEXT);
WHERE语句
格式:
SELECT * FROM pets WHERE
<condition>;UPDATE <table_name>
SET <column_name> = <value>
WHERE <condition>;DELETE FROM <table_name>
WHERE <condition>;
example:
SELECT * FROM pets
WHERE _id = 1;SELECT * FROM pets
WHERE weight >= 15;SELECT name, gender FROM
pets WHERE breed != "Breed
Unknown";DELETE FROM pets WHERE _id =
<id_of_pet_to_delete>;
ORDER BY语句
example:
SELECT * FROM pets
ORDER BY name ASC;//升序SELECT weight FROM pets
ORDER BY name DESC;//降序
在Android应用中使用数据库
创建数据库的步骤:
一.第一步需要在Contract合约类里创建一个架构。
二.使用SQL辅助函数SQL Helper创建数据库,这个辅助函数会指导我们如何执行增删改查指令。
一:在Contract合约类里创建一个架构。
创建数据库架构其实就是规划数据库的结构,为此我们可以问问自己两个问题:1.首先表格的名称是什么?2.这些表格里的列的名称和数据类型是什么?
使用Conract合约类的理由:1.帮助我们定义架构,并规定去哪查找数据库常量;2.生成SQL指令时,可以帮助我们避免拼写错误;3.使我们更容易更新数据库架构。
以Pets应用为例,在Java代码中创建一个data包(可以将具有特定数据功能的类于Activity类区分开来,包里创建PetContract合约类,在里面创建架构:
(创建Contract通常步骤:外部类BlankContract -> 内部类BlankEntry(每个内部类实现BaseColumns类) -> 字符串常量)
//final是因为这个类只用来提供常量,不需要被外部类继承。
public final class PetContract {public static abstract class PetEntry implements BaseColumns {public static final String TABLE_NAME = "pets";public static final String _ID = BaseColumns._ID;public static final String COLUMN_PET_NAME = "name";public static final String COLUMN_PET_BREED = "breed";public static final String COLUMN_PET_GENDER = "gender";public static final String COLUMN_PET_WEIGHT = "weight";public static final int GENDER_UNKNOWN = 0;public static final int GENDER_MALE = 1;public static final int GENDER_FEMALE = 2;}
}
二:使用SQLiteOpenHelper类创建数据库、管理数据库
Android提供了一个类来帮助我们创建、打开和管理数据库,这个类就叫做SQLiteOpenHelper类,他可以帮我们完成以下这些任务:
1.首先,当应用第一次访问数据库时,SQLiteOpenHelper类将创建我们要使用的数据库,它会使用OnCreate()方法进行创建。
2.下次在设备上打开该应用时SQLiteOpenHelper将帮助我们访问已经存在的数据库,而不是创建新的数据库。
3.如果数据库版本发生任何更改,这个SQLiteOpenHelper类将使用onUpgrade()方法帮助我们更新数据库架构。
创建SQLiteOpenHelper类的5个步骤(以Pets应用为例):
1.创建一个类PetDbHelper,继承SQLiteOpenHelper;
2.在这个类中创建数据库名称和数据库版本的常量;
3.创建PetDbHelper的构造函数;
4.重写onCreate方法,当数据库第一次创建的时候会用到该方法
5.重写onUpgrade方法,当数据库架构数据库架构发生变化的时候会用到该方法(当我们想数据库中添加新的列时,架构就会发生变化。)
public class PetDbHelper extends SQLiteOpenHelper {/*Name of the database file*/public static final int DATABASE_VERSION = 1;//If you change the database schema, you must increment the database version.public static final String DATABASE_NAME = "shelter.db";public PetDbHelper(Context context) {super(context, DATABASE_NAME, null, DATABASE_VERSION);}@Overridepublic void onCreate(SQLiteDatabase sqLiteDatabase) {//Create a String that contains the SQL statement to create the pets table.final String SQL_CREATE_ENTRIES ="CREATE TABLE " + PetEntry.TABLE_NAME + " (" +PetEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +PetEntry.COLUMN_PET_NAME + " TEXT NOT NULL," +PetEntry.COLUMN_PET_BREED + " TEXT," +PetEntry.COLUMN_PET_WEIGHT + " INTEGER NOT NULL DEFAULT 0," +PetEntry.COLUMN_PET_GENDER + " INTEGER NOT NULL);";Log.v("SQL_CREATE_ENTRIES", "The SQL_CREATE_ENTRIES:"+ SQL_CREATE_ENTRIES);sqLiteDatabase.execSQL(SQL_CREATE_ENTRIES);}@Overridepublic void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {//sqLiteDatabase.execSQL(SQL_DELETE_ENTRIES);//onCreate(sqLiteDatabase);}
如何查看数据库是否正常运转?
在模拟器中调试,将SQLite数据库文件下载到电脑上,然后通过上面的学到的技能在终端上检查数据库是否构建正确:
在Android Studio 右下角Device Explorer中,在data->data->com.example.android.pets下有个databses,当代码运行调用getReadableDatabase()
后,实际上在设备上创建了这个文件,右键点save as就可以下载该数据库文件。之后在终端相应目录下运行以下两行代码来查看表格和插入语句。
PRAGMA TABLE_INFO(pets);.schema
如果发现有问题可以通过卸载应用来清空数据库,然后修复代码并重新安装应用。
SQLiteDatabase对象使我们能够操纵相关的数据库,使用该SQLiteDatabase对象对数据库执行任何SQL语句。如果想执行创建,更新或删除操作,则需要调用getWritableDatabase()方法,读取查询只需要使用getReadableDatabase()方法
如何插入数据?
打开Saving Data in SQL Databases文档并转到"将信息添加到数据库"部分,这个示例使用了ContentValues类,该类存储了大量的键值对,这里的键为数据库中的列名称,值为你要插入的值
Content Values 使用起来非常简单,只需创建一个ContentValue对象,然后调用put()方法,其中包含列名称和列值
要在数据库实际地创建新的一行,我们需要使用SQLiteDatabase类的insert()方法
/*** Get user input form editor and save new pet into database.*/private void insertPet(){//Gets the data repository in write mode.SQLiteDatabase db = mDbHelper.getWritableDatabase();ContentValues values = new ContentValues();values.put(PetEntry.COLUMN_PET_NAME, "Toto");values.put(PetEntry.COLUMN_PET_BREED, "Terrier");values.put(PetEntry.COLUMN_PET_GENDER, PetEntry.GENDER_MALE);values.put(PetEntry.COLUMN_PET_WEIGHT, 7);long newRowId = db.insert(PetEntry.TABLE_NAME,null, values);Log.v("CatalogActivity", "New row ID" + newRowId);}
inset()方法第一个参数是表格名称,第二个参数是一个可选参数,我们始终设为null,第三个参数是我们创建的Content Values,这个方法最后会返回新插入行的ID,如果出现错误,它会返回-1
如何通过菜单栏插入虚拟宠物(即当按下“保存”按钮时,表示已经编辑完毕,此时应该保存宠物数据了)
(简单说这些步骤就是到用户输入字段里获取宠物属性,然后根据这些属性向数据库插入新的宠物)
具体来说:
1.首先按下“保存”按钮后触发操作;
2.从EditText字段中中获取所有数据
3.将信息保存到ContentValues对象中
4.将该ConentValues对象插入pets表格(利用insert()方法将其存储到我们的数据库中)
当用户向这些字段里输入宠物数据后,我们需要从这些字段里读取数据,然后存储到变量里,然后我们把这些变量的值当作新的宠物存储到数据库里。
回顾一下如何从EditText字段中获取值,在之前JustJava点咖啡的应用中,为了发送电子邮件,我们需要从EditText字段中获取名称,我们使用了getText()和toString()方法来获取该EditText中的字符串
public void createOrderSummary(int PriceOfOrder) {EditText nameEditText = (EditText) findViewById(R.id.name_edit_text);String name = nameEditText.getText().toString();......}
在pets应用中,同样通过这种方法,注意下面代码中还使用的trim()
方法,该方法可以出去字符串中开头或者结尾的空格。
/*** Get user input form editor and save new pet into database.*/private void insertPet(){//Read from input fields//Use trim to eliminate leading or trailing white space.String petName = mNameEditText.getText().toString().trim();String petBreed = mBreedEditText.getText().toString().trim();int petWeight = Integer.parseInt(mWeightEditText.getText().toString().trim());//create database helperPetDbHelper mDbHelper = new PetDbHelper(this);//Gets the data repository in write mode.SQLiteDatabase db = mDbHelper.getWritableDatabase();//Create a ContentValues object where column names are the keys,//and pet attributes from the editor are the values.ContentValues values = new ContentValues();values.put(PetEntry.COLUMN_PET_NAME, petName);values.put(PetEntry.COLUMN_PET_BREED, petBreed);values.put(PetEntry.COLUMN_PET_GENDER, mGender);values.put(PetEntry.COLUMN_PET_WEIGHT, petWeight);//Insert a new row for pet in the database, returning the ID of that new row.long newRowId = db.insert(PetEntry.TABLE_NAME,null, values);Log.v("CatalogActivity", "New row ID" + newRowId);if (newRowId == -1) {//If the row ID is -1, then there was an error with insertion.Toast.makeText(this, "Error with saving pet", Toast.LENGTH_SHORT).show();} else {//Otherwise, the Insertion was successful and we can display a toast with the row ID.Toast.makeText(this, "Pet saved with row id: " + newRowId, Toast.LENGTH_SHORT).show();}}
如何在android设备中读取SQLite数据库
可以使用execSQL()方法执行SQL语句;另外可以使用rawQuery,它是一个类似的API会使用SQL来读取数据库,但是不建议使用该方法,最好使用SQLiteDatabase.query()方法
可以参考Android官方文档:使用SQLite保存数据
1.首先定义一个字符串数组叫projection,projection就是我们想要获取的列名称,如果不定义projection,则默认地会获取所有列;
SQLiteDatabase db = mDbHelper.getReadableDatabase();String[] projection = {PetEntry._ID,PetEntry.COLUMN_PET_NAME,PetEntry.COLUMN_PET_BREED,PetEntry.COLUMN_PET_GENDER,PetEntry.COLUMN_PET_WEIGHT};// Filter results WHERE "id" = '1'// Define 'where' part of queryString selection = PetEntry._ID + " = ?";// Specify arguments in placeholder orderString[] selectionArgs = { "1" };// How you want the results sorted in the resulting CursorString sortOrder =PetEntry.COLUMN_PET_NAME + " DESC";Cursor cursor = db.query(PetEntry.TABLE_NAME, // The table to queryprojection, // The array of columns to return (pass null to get all)selection, // The columns for the WHERE clauseselectionArgs, // The values for the WHERE clausenull, // don't group the rowsnull, // don't filter by row groupssortOrder // The sort order);
2.query()方法具有大量的输入参数,第一个是TABLE_NAME,后面的参数都是可选参数(可选是指可以对所有这些参数传入null),代表了SELECT语句的不同部分。第二个参数是我们在上面创建的projection
,如果传入了null那么所有行都会被选中,类似于使用SELECT * FROM pets
而不是SELECT name, breed FROM pets
。下面两个参数是selection和selectionArgs处理的是可选的WHERE条件,产生的作用是SELECT * FROM pets WHERE _id = 1
,其中selection中的问好是占位符,之后会填充selectionArgs参数中的值,另外使用占位符?可以防止SQL攻击。完成所有这些内容 将query()方法设置好后,会返回一个Cursor对象(可以把Cursor当作在计算机上的SQLite3中输入语句时出现的表格),本质上它是一种对象,代表数据库中多行内容的对象,可以捕获数据库中所有数据的子集。
Cursor对象中转到特定位置的方法:
cursor.getCount()
查看Cursor包含多少行;
为了能获取特定行的数据,我们需要将Cursor位置移到我们所需的精确行,当我们第一次获取Cursor时位置从-1开始,cursor.moveTofirst()
则会将Cursor位置移动到表格中的第一行(对应POSITION为0)
转到该行后,我们就可以使用不同的get方法,帮助我们从数据库中获取特定的值
它们都有个参数 级columnIndex,指的是Cursor中我们感兴趣的属性(即列名称)的列索引,列索引根据我们在projection给定的名称排序,也可以通过cursor.getColumnIndex(String columnName)
方法来根据名称获取列的索引。
见一个例子:
对于Cursor要注意的一个地方是,使用完毕后,一定要记得调用cursor.close()
,这样会完全清空Cursor使其无效,仅在完全操作完毕后调用该方法,不关闭Cursor的话,会因内存泄漏而导致降低性能。
总结
Menu菜单选项的一些说明
就像我们可以使用XML来声明布局、字符串和Style样式资源一样,我们也可以用XML来声明彩带布局,在pets应用中,在res的menu下,我们定义了menu_catalog 的XML文件
<menu 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"tools:context=".CatalogActivity"><itemandroid:id="@+id/action_insert_dummy_data"android:title="@string/action_insert_dummy_data"app:showAsAction="never" /><itemandroid:id="@+id/action_delete_all_entries"android:title="@string/action_delete_all_entries"app:showAsAction="never" />
</menu>
在这个文件里,我们使用了menu标记 声明了具有两个菜单项的菜单。这个showAsAction属性决定了这个菜单项是显示在应用栏还是溢出菜单里(溢出菜单栏是位于应用栏右侧的三个垂直点),这里我们将showAsAction设为了nerver,表示该菜单项始终显示在溢出菜单里。
现在我们定义了菜单资源文件,如何将其与CatalogActivity关联起来呢?
在CatalogActivity文件中,我们可以重写onCreateOptionsMenu()方法,通过引用菜单资源文件的资源ID R.menu.menu_catalog
来隐形膨胀菜单资源
@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu options from the res/menu/menu_catalog.xml file.// This adds menu items to the app bar.getMenuInflater().inflate(R.menu.menu_catalog, menu);return true;}
当CatalogActivity第一次被创建时,系统将自动调用这个onCreateOptionMenu()方法。
为了向每个菜单项添加行为,我们需要重写onOptionItemSelected()这个方法,当用户点击选项菜单时,系统将调用这个方法。
@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// User clicked on a menu option in the app bar overflow menuswitch (item.getItemId()) {// Respond to a click on the "Insert dummy data" menu optioncase R.id.action_insert_dummy_data:insertPet();displayDatabaseInfo();return true;// Respond to a click on the "Delete all entries" menu optioncase R.id.action_delete_all_entries:// Do nothing for nowreturn true;}return super.onOptionsItemSelected(item);}
Android数据存储:数据库基础,在Android应用中使用数据库,附加Menu菜单选项的一些说明相关推荐
- Android数据存储:Shared Preferences
Android数据存储之SharedPreferences 在Android系统中提供了多种存储技术.通过这些存储技术可以将数据存储在各种存储介质上, Android 为数据存储提供了如下几种方式: ...
- Android数据存储之GreenDao 3.0 详解
前言: 今天一大早收到GreenDao 3.0 正式发布的消息,自从2014年接触GreenDao至今,项目中一直使用GreenDao框架处理数据库操作,本人使用数据库路线 Sqlite----> ...
- Android数据存储安全实践
0x00 数据安全 Android操作系统自问世以来凭借其开放性和易用性成为当前智能手机的主流操作系统之一,作为与人们关系最密切的智能设备,越来越多的通讯录.短信.视频等隐私数据以明文的方式保存在手机 ...
- Android数据存储(三)——SQLite
如果需要一个更加健壮的数据存储机制,则需要使用一个关系型数据库,在Android上,则为SQLlite. SQLite的特点:轻量级.嵌入式的.关系型数据库.可移植性好,易使用,小,高效且可靠,与使用 ...
- < Android数据存储> 任务二 应用程序数据文件夹里的文件读写
:zh]Android中提供了两个方法用来打开应用程序的数据文件夹IO流. 1.FileInputStream openFileInput(String name):参数name表示某个文件名,该方法 ...
- 5 Android数据存储 任务二 应用程序数据文件夹里的文件读写 ,
Android中提供了两个方法用来打开应用程序的数据文件夹IO流. 1.FileInputStream openFileInput(String name):参数name表示某个文件名,该方法用于打开 ...
- android+默认存储,Android 数据存储之SP存储,内部存储,外部存储
Android 数据存储之SP存储,内部存储,外部存储 Android提供了多种数据存储的技术来永久的保存应用数据,以便于开发者能够根据自己的需求来选择合适的数据存储方案,主要有SharedPrefe ...
- 【Android数据存储】ContentProvider详细介绍(附实例源码)
1.ContentProvider是什么? ContentProvider--内容提供者.它是一个类,这个类主要是对Android系统中进行共享的数据进行包装,并提供了一组统一的访问接口供其他程序调用 ...
- Android数据存储几种方式用法总结
Android数据存储几种方式用法总结 1.概述 Android提供了5种方式来让用户保存持久化应用程序数据.根据自己的需求来做选择,比如数据是否是应用程序私有的,是否能被其他程序访问,需要多少数据存 ...
最新文章
- Python使用matplotlib进行3D可视化分析:3d柱状图、3d直方图、3d线框图、3d曲面图、3d翼面图(莫比乌斯环)
- python3.6升级及setuptools、pip安装
- iptable 命令
- RxJava 2.x 教程
- c/c++ 重载运算符 函数调用运算符
- docker容器与宿主拷贝文件
- 魅蓝2 刷 android,魅蓝2全系列-解锁BootLoader完整版+刷入第三方recovery+刷入第三方ROM教程...
- Linux Redhat7 防火墙关闭/开启查看
- 040 罗尔定理与零点定理、介值定理综合应用;柯西中值定理; 型二( f(n) (ξ) =0 )
- 汉字读音表GB2312版 (共7809个汉字)
- 2021年中国商品期货交易规模分析:交易量达71.71亿手,同比增长21.15%[图]
- 基于RFM模型的用户价值分析——PythonTableau
- 【转】ARM经典300问
- BackTrack 5 R3 Released
- android 神气插件 自动补全tabnine
- KMZ转换为KML,KML转换为shp文件格式方法
- [奥塔在线]JAVA启蒙:WIN10下的JDK环境部署
- mootools_在jQuery中实现MooTools的Elements.addEvent
- python dataframe去掉索引_python中pandas.DataFrame的简单操作方法(创建、索引、增添与删除)...
- 何飞作业-17037065