Android 4 学习(16):Database and Content Providers
参考《Professional Android 4 Development》
Database and Content Providers
Android Database简介
Android使用SQLite数据库和ContentProvider来实现结构化数据的读写。在Android中,SQLite是以lib的形式存在的,每个应用程序含有自己的SQLite lib,减少了数据库层的耦合,并且提升了安全性。SQLite的数据文件默认存储在这个目录:
/data/data/<package_name>/databases
Content Provider使用URI的方式提供了一个统一的数据访问接口,Schema是content://,与SQLite不同,Content Provider可以跨应用程序使用,可以通过发布自己的Content Provider供他人使用。
Content Values和Cursors
Content Values用于插入数据,Cursor则是返回的查询结果。Content Values对应数据中的一行数据。Cursor类提供了移动游标的方法,常用的有这些:
- moveToFirst
- moveToNext
- moveToPrevious
- getCount
- getColumnIndexOrThrow
- getColumnName
- getColumnNames
- moveToPosition
- getPosition
SQLiteOpenHelper
SQLiteOpenHelper是一个抽象类,使用它可以更方便地进行操作数据库。SQLiteOpenHelper会缓存数据库实例,提升访问效率,也正是因为这种缓存策略,用户只有在不再访问数据库的时候才需要关闭数据库连接。下面是一个SQLiteOpenHelper的实现类例子:
private static class HoardDBOpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = “myDatabase.db";
private static final String DATABASE_TABLE = “GoldHoards";
private static final int DATABASE_VERSION = 1;
// SQL Statement to create a new database.
private static final String DATABASE_CREATE = “create table “ + DATABASE_TABLE + “ (“ + KEY_ID +
“ integer primary key autoincrement, “ +
KEY_GOLD_HOARD_NAME_COLUMN + “ text not null, “ +
KEY_GOLD_HOARDED_COLUMN + “ float, “ +
KEY_GOLD_HOARD_ACCESSIBLE_COLUMN + “ integer);";
public HoardDBOpenHelper(Context context, String name, CursorFactory factory, int version) {
super(context, name, factory, version);
}
// Called when no database exists in disk and the helper class needs to create a new one.
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
// Called when there is a database version mismatch meaning that the version of the database on disk needs to be upgraded to
// the current version.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Log the version upgrade.
Log.w(“TaskDBAdapter", “Upgrading from version “ + oldVersion + “ to “ + newVersion + “, which will destroy all old data");
// Upgrade the existing database to conform to the new version. Multiple previous versions can be handled by comparing oldVersion and newVersion values.
// The simplest case is to drop the old table and create a new one.
db.execSQL(“DROP TABLE IF IT EXISTS “ + DATABASE_TABLE);
// Create a new one.
onCreate(db);
}
}
调用SQLiteOpenHelper的getWritableDatabase或者getReadableDatabase方法可以获得一个数据库实例,若不存在数据库实例,SQLiteOpenHelper的内部实现是使用onCreate()方法来创建一个数据库实例并返回。
使用其他方式创建或打开SQLite数据库
SQLiteDatabase db = context.openOrCreateDatabase(DATABASE_NAME, Context.MODE_PRIVATE, null);
使用上面的代码可以获得数据库,onCreate和onUpdate方法中的逻辑需要手动来完成。
Android数据库设计需要注意的事情
1. 文件不要直接存在数据库中。更好的方法是将文件路径放到数据库中。
2. 表中加入auto-increment的id字段。
数据库查询
使用query方法,传入下面这些参数,即可实现数据库查询:
- Boolean类型的参数,用于指示是否包含重复数据。
- 要查询的数据库表名。
- Projection,指定要返回的列。
- Where,可以用?做占位符。
- Where参数。
- Group by。
- Having。
- 字符串,指定返回行的顺序。
- 字符串,用于指定返回的最大行数
// Specify the result column projection. Return the minimum set of columns required to satisfy your requirements.
String[] result_columns = new String[] {KEY_ID, KEY_GOLD_HOARD_ACCESSIBLE_COLUMN, KEY_GOLD_HOARDED_COLUMN };
// Specify the where clause that will limit our results.
String where = KEY_GOLD_HOARD_ACCESSIBLE_COLUMN + “=" + 1;
// Replace these with valid SQL statements as necessary.
String whereArgs[] = null;
String groupBy = null;
String having = null;
String order = null;
SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();
Cursor cursor = db.query(HoardDBOpenHelper.DATABASE_TABLE, result_columns, where, whereArgs, groupBy, having, order);
从Cursor中提取数据
从Cursor中提取数据需要两步。首先使用前面介绍的moveTo<loction>等方法首先移动到某行,然后使用get<type>方法获取某列的值。使用getColumnIndexOrThrow或getColumnIndex方法可以通过列名获得该列在Cursor中的index。若找不到该列,getColumnIndex方法将返回-1,而getColumnIndexOrThrow将抛出异常。
int columnIndex = cursor.getColumnIndex(KEY_COLUMN_1_NAME);
if (columnIndex > -1) {
String columnValue = cursor.getString(columnIndex);
// Do something with the column value.
}
else {
// Do something else if the column doesn’t exist.
}
下面是一个从Cursor中读取数据的完整示例:
float totalHoard = 0f;
float averageHoard = 0f;
// Find the index to the column(s) being used.
int GOLD_HOARDED_COLUMN_INDEX = cursor.getColumnIndexOrThrow(KEY_GOLD_HOARDED_COLUMN);
// Iterate over the cursors rows.
// The Cursor is initialized at before first, so we can check only if there is a “next" row available. If the
// result Cursor is empty this will return false.
while (cursor.moveToNext()) {
float hoard = cursor.getFloat(GOLD_HOARDED_COLUMN_INDEX);
totalHoard += hoard;
}
// Calculate an average -- checking for divide by zero errors.
float cursorCount = cursor.getCount();
averageHoard = cursorCount > 0 ? (totalHoard / cursorCount) : Float.NaN;
// Close the Cursor when you’ve finished with it.
cursor.close();
增、改、删
插入数据:
// Create a new row of values to insert.
ContentValues newValues = new ContentValues();
// Assign values for each row.
newValues.put(KEY_GOLD_HOARD_NAME_COLUMN, hoardName);
newValues.put(KEY_GOLD_HOARDED_COLUMN, hoardValue);
newValues.put(KEY_GOLD_HOARD_ACCESSIBLE_COLUMN, hoardAccessible);
// [ ... Repeat for each column / value pair ... ]
// Insert the row into your table
SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();
db.insert(HoardDBOpenHelper.DATABASE_TABLE, null, newValues);
更新数据:
// Create the updated row Content Values.
ContentValues updatedValues = new ContentValues();
// Assign values for each row.
updatedValues.put(KEY_GOLD_HOARDED_COLUMN, newHoardValue);
// [ ... Repeat for each column to update ... ]
// Specify a where clause the defines which rows should be updated. Specify where arguments as necessary.
String where = KEY_ID + “=" + hoardId;
String whereArgs[] = null;
// Update the row with the specified index with the new values.
SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();
db.update(HoardDBOpenHelper.DATABASE_TABLE, updatedValues, where, whereArgs);
删除数据:
// Specify a where clause that determines which row(s) to delete.
// Specify where arguments as necessary.
String where = KEY_GOLD_HOARDED_COLUMN + “=" + 0;
String whereArgs[] = null;
// Delete the rows that match the where clause.
SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();
db.delete(HoardDBOpenHelper.DATABASE_TABLE, where, whereArgs);
创建Content Providers
Content Provider是一个接口,Content Resolver可以通过这个接口读取数据。通过继承ContentProvider类,即可创建一个Content Provider:
public class MyContentProvider extends ContentProvider
注册Content Provider
将Content Provider加到Manifest后,Content Resolver才能找到并使用。每个Content Provider标签中都有authorities属性,这个属性标识Content Provider。
<provider android:name=".MyContentProvider" android:authorities="com.paad.skeletondatabaseprovider" />
发布Content Provider
每个ContentProvider都应该还有一个静态字符串变量CONTENT_URI,这个变量由该ContentProvider的authorities和data path组成,如:
public static final Uri CONTENT_URI = Uri.parse(“content://com.paad.skeletondatabaseprovider/elements”);
在CONTENT_URI后面加上行号,即可获得某一行数据的URI,如:
content://com.paad.skeletondatabaseprovider/elements/5
在实际使用中,最好将这两种URI都发布出来,并配合UriMatcher使用:
// Create the constants used to differentiate between the different URI requests.
private static final int ALLROWS = 1;
private static final int SINGLE_ROW = 2;
private static final UriMatcher uriMatcher;
// Populate the UriMatcher object, where a URI ending in ‘elements’ will correspond to a request for all items,
// and ‘elements/[rowID]’ represents a single row.
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(“com.paad.skeletondatabaseprovider”, “elements”, ALLROWS);
uriMatcher.addURI(“com.paad.skeletondatabaseprovider”, “elements/#”, SINGLE_ROW);
}
UriMatcher经常和SQLiteQueryBuilder配合使用:
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// If this is a row query, limit the result set to the passed in row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
queryBuilder.appendWhere(KEY_ID + “=” + rowID);
default: break;
}
创建Content Provider的数据库
使用SQLiteOpenHelper实现ContentProvider的onCreate方法:
private MySQLiteOpenHelper myOpenHelper;
@Override
public boolean onCreate() {
// Construct the underlying database.
// Defer opening the database until you need to perform a query or transaction.
myOpenHelper = new MySQLiteOpenHelper(getContext(), MySQLiteOpenHelper.DATABASE_NAME, null, MySQLiteOpenHelper.DATABASE_VERSION);
return true;
}
实现Content Provider查询
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// Open the database.
SQLiteDatabase db;
try {
db = myOpenHelper.getWritableDatabase();
} catch (SQLiteException ex) {
db = myOpenHelper.getReadableDatabase();
}
// Replace these with valid SQL statements if necessary.
String groupBy = null;
String having = null;
// Use an SQLite Query Builder to simplify constructing the database query.
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// If this is a row query, limit the result set to the passed in row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
queryBuilder.appendWhere(KEY_ID + “=” + rowID);
default:
break;
}
// Specify the table on which to perform the query. This can be a specific table or a join as required.
queryBuilder.setTables(MySQLiteOpenHelper.DATABASE_TABLE);
// Execute the query.
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder);
// Return the result Cursor.
return cursor;
}
实现query方法之后,还需要提供返回数据的MIME信息。因此要重写getType方法并返回一个字符串来描述你的数据类型。数据类型有两种,一种是单条数据,另一种是集合数据:
- 单条数据:vnd.android.cursor.item/vnd.<companyname>.<contenttype>
- 所有数据:vnd.android.cursor.dir/vnd.<companyname>.<contenttype>
@Override
public String getType(Uri uri) {
// Return a string that identifies the MIME type for a Content Provider URI
switch (uriMatcher.match(uri)) {
case ALLROWS:
return “vnd.android.cursor.dir/vnd.paad.elemental”;
case SINGLE_ROW:
return “vnd.android.cursor.item/vnd.paad.elemental”;
default:
throw new IllegalArgumentException(“Unsupported URI: “ + uri);
}
}
Content Provider Transactions
在修改完数据后,需要调用Content Resolver的notifyChange方法,这样Content Observer即可获得数据修改通知。下面是一段比较全面的示例代码:
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// If this is a row URI, limit the deletion to the specified row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + “=” + rowID + (!TextUtils.isEmpty(selection) ?“ AND (“ + selection + ‘)’ : “”);
default: break;
}
// To return the number of deleted items you must specify a where clause. To delete all rows and return a value pass in “1”.
if (selection == null)
selection = “1”;
// Perform the deletion.
int deleteCount = db.delete(MySQLiteOpenHelper.DATABASE_TABLE, selection, selectionArgs);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(uri, null);
// Return the number of deleted items.
return deleteCount;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// To add empty rows to your database by passing in an empty
// Content Values object you must use the null column hack parameter to specify the name of the column that can be set to null.
String nullColumnHack = null;
// Insert the values into the table
long id = db.insert(MySQLiteOpenHelper.DATABASE_TABLE, nullColumnHack, values);
// Construct and return the URI of the newly inserted row.
if (id > -1) {
// Construct and return the URI of the newly inserted row.
Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(insertedId, null);
return insertedId;
}else
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// If this is a row URI, limit the deletion to the specified row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + “=” + rowID + (!TextUtils.isEmpty(selection) ?“ AND (“ + selection + ‘)’ : “”);
default: break;
}
// Perform the update.
int updateCount = db.update(MySQLiteOpenHelper.DATABASE_TABLE, values, selection, selectionArgs);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(uri, null);
return updateCount;
}
在Content Provider中存储文件
文件存储的最佳设计模式是将文件路径存储在数据库中,使用Content Provider的接口方法读写。文件路径应该存放在数据库表中的_data列中,并重写Content Provider的openFile方法,返回ParcelFileDescriptor:
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
// Find the row ID and use it as a filename.
String rowID = uri.getPathSegments().get(1);
// Create a file object in the application’s external files directory.
String picsDir = Environment.DIRECTORY_PICTURES;
File file = new File(getContext().getExternalFilesDir(picsDir), rowID);
// If the file doesn’t exist, create it now.
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
Log.d(TAG, “File creation failed: “ + e.getMessage());
}
}
// Translate the mode parameter to the corresponding Parcel File Descriptor open mode.
int fileMode = 0;
if (mode.contains(“w”))
fileMode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
if (mode.contains(“r”))
fileMode |= ParcelFileDescriptor.MODE_READ_ONLY;
if (mode.contains(“+”))
fileMode |= ParcelFileDescriptor.MODE_APPEND;
// Return a Parcel File Descriptor that represents the file.
return ParcelFileDescriptor.open(file, fileMode);
}
Content Provider的Skeleton示例
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
public class MyContentProvider extends ContentProvider {
public static final Uri CONTENT_URI = Uri.parse(“content://com.paad.skeletondatabaseprovider/elements”);
// Create the constants used to differentiate between the different URI requests.
private static final int ALLROWS = 1;
private static final int SINGLE_ROW = 2;
private static final UriMatcher uriMatcher;
// Populate the UriMatcher object, where a URI ending in ‘elements’ will correspond to a request for all
// items, and ‘elements/[rowID]’ represents a single row.
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(“com.paad.skeletondatabaseprovider”, “elements”, ALLROWS);
uriMatcher.addURI(“com.paad.skeletondatabaseprovider”, “elements/#”, SINGLE_ROW);
}
// The index (key) column name for use in where clauses.
public static final String KEY_ID = “_id”;
// The name and column index of each column in your database.
// These should be descriptive.
public static final String KEY_COLUMN_1_NAME = “KEY_COLUMN_1_NAME”;
// TODO: Create public field for each column in your table.
// SQLite Open Helper variable
private MySQLiteOpenHelper myOpenHelper;
@Override
public boolean onCreate() {
// Construct the underlying database.
// Defer opening the database until you need to perform a query or transaction.
myOpenHelper = new MySQLiteOpenHelper(getContext(), MySQLiteOpenHelper.DATABASE_NAME, null, MySQLiteOpenHelper.DATABASE_VERSION);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// Open the database.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Replace these with valid SQL statements if necessary.
String groupBy = null;
String having = null;
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(MySQLiteOpenHelper.DATABASE_TABLE);
// If this is a row query, limit the result set to the passed in row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
queryBuilder.appendWhere(KEY_ID + “=” + rowID);
default: break;
}
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder);
return cursor;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs){
// Open a read / write database to support the transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// If this is a row URI, limit the deletion to the specified row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + “=” + rowID + (!TextUtils.isEmpty(selection) ?“ AND (“ + selection + ‘)’ : “”);
default: break;
}
// To return the number of deleted items, you must specify a where clause. To delete all rows and return a value, pass in “1”.
if (selection == null) selection = “1”;
// Execute the deletion.
int deleteCount = db.delete(MySQLiteOpenHelper.DATABASE_TABLE, selection, selectionArgs);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(uri, null);
return deleteCount;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// To add empty rows to your database by passing in an empty
// Content Values object, you must use the null column hack parameter to specify the name of the column that can be set to null.
String nullColumnHack = null;
// Insert the values into the table
long id = db.insert(MySQLiteOpenHelper.DATABASE_TABLE, nullColumnHack, values);
if (id > -1) {
// Construct and return the URI of the newly inserted row.
Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(insertedId, null);
return insertedId;
}else
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// If this is a row URI, limit the deletion to the specified row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + “=” + rowID + (!TextUtils.isEmpty(selection) ?“ AND (“ + selection + ‘)’ : “”);
default: break;
}
// Perform the update.
int updateCount = db.update(MySQLiteOpenHelper.DATABASE_TABLE, values, selection, selectionArgs);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(uri, null);
return updateCount;
}
@Override
public String getType(Uri uri) {
// Return a string that identifies the MIME type for a Content Provider URI
switch (uriMatcher.match(uri)) {
case ALLROWS:
return “vnd.android.cursor.dir/vnd.paad.elemental”;
case SINGLE_ROW:
return “vnd.android.cursor.item/vnd.paad.elemental”;
default:
throw new IllegalArgumentException(“Unsupported URI: “ + uri);
}
}
private static class MySQLiteOpenHelper extends SQLiteOpenHelper {
// [ ... SQLite Open Helper Implementation ... ]
}
}
Android 4 学习(16):Database and Content Providers的更多相关文章
- Content Providers的步骤,来自官网文档
Content Providers In this document Content provider basics Querying a content provider Modifying dat ...
- Content Providers
Content providers manage access to a structured set of data. They encapsulate the data, and provide ...
- 我的Android 4 学习系列之数据库和Content Provider
目录 创建数据库和使用SQLite 使用Content Provider.Cusor和Content Value来存储.共享和使用应用程序数据 使用Cursor Loader异步查询Content P ...
- Android Animation学习(二) ApiDemos解析:基本Animators使用
Android Animation学习(二) ApiDemos解析:基本Animatiors使用 Animator类提供了创建动画的基本结构,但是一般使用的是它的子类: ValueAnimator.O ...
- Android Animation学习(二) ApiDemos解析:基本Animatiors使用
Animator类提供了创建动画的基本结构,但是一般使用的是它的子类: ValueAnimator.ObjectAnimator.AnimatorSet ApiDemos中Animation部分是单独 ...
- Android:日常学习笔记(9)———探究持久化技术
Android:日常学习笔记(9)———探究持久化技术 引入持久化技术 什么是持久化技术 持久化技术就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑关机的情况下,这些数据仍然不会丢失 ...
- 【转】Android开发学习笔记(一)——初识Android
对于一名程序员来说,“自顶向下”虽然是一种最普通不过的分析问题和解决问题的方式,但其却是简单且较为有效的一种.所以,将其应用到Android的学习中来,不至于将自己的冲动演变为一种盲目和不知所措. 根 ...
- Android:日常学习笔记(10)———使用LitePal操作数据库
Android:日常学习笔记(10)———使用LitePal操作数据库 引入LitePal 什么是LitePal LitePal是一款开源的Android数据库框架,采用了对象关系映射(ORM)的模式 ...
- Android自动化学习笔记之MonkeyRunner:官方介绍和简单实例
---------------------------------------------------------------------------------------------------- ...
随机推荐
- shell编程实例2
1.vim read_PERSON.sh 2. #!/bin/bash echo "What is your name?" read PERSON echo "Hell ...
- Elasticsearch Painless语言(实现搜索打分基础)
With the release of Elasticsearch 5.x came Painless, Elasticsearch's answer to safe, secure, and per ...
- Django框架(三)
0627内容: 上节回顾: 1. FBV.CBV 2. 数据库操作 class UserGroup(models.Model): """ 部门 3 "" ...
- New Concept English three (26)
34w/m 54words No one can avoid being influenced by advertisements. Much as we may pride ourselves on ...
- jsp中的session
浏览器和服务器的异常通话 常用方法 setAttribute(String key,Object value);//设置值 getAttribute(String key); //取值 Invalid ...
- Go开发Struct转换成map两种方式比较
最近做Go开发的时候接触到了一个新的orm第三方框架gorose,在使用的过程中,发现没有类似beego进行直接对struct结构进行操作的方法,有部分API是通过map进行数据库相关操作,那么就需要 ...
- php之opcodes
opcode是一种php脚本编译之后的语言. 例如: <?php echo "Hello World"; $a = 1 + 1; echo $a; ?> php执行这段 ...
- I.MX6 Android 设备节点权限
/********************************************************************************** * I.MX6 Android ...
- 神奇的TextField(1)
先看一大段测试代码,每个小方法的注释行是输出结果. private var text_content:TextField; private function textFieldDemo():void{ ...
- HDU - 6166:Senior Pan(顶点集合最短路&二进制分组)
Senior Pan fails in his discrete math exam again. So he asks Master ZKC to give him graph theory pro ...