参考《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方法,传入下面这些参数,即可实现数据库查询:

  1. Boolean类型的参数,用于指示是否包含重复数据。
  2. 要查询的数据库表名。
  3. Projection,指定要返回的列。
  4. Where,可以用?做占位符。
  5. Where参数。
  6. Group by。
  7. Having。
  8. 字符串,指定返回行的顺序。
  9. 字符串,用于指定返回的最大行数
// 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的更多相关文章

  1. Content Providers的步骤,来自官网文档

    Content Providers In this document Content provider basics Querying a content provider Modifying dat ...

  2. Content Providers

    Content providers manage access to a structured set of data. They encapsulate the data, and provide ...

  3. 我的Android 4 学习系列之数据库和Content Provider

    目录 创建数据库和使用SQLite 使用Content Provider.Cusor和Content Value来存储.共享和使用应用程序数据 使用Cursor Loader异步查询Content P ...

  4. Android Animation学习(二) ApiDemos解析:基本Animators使用

    Android Animation学习(二) ApiDemos解析:基本Animatiors使用 Animator类提供了创建动画的基本结构,但是一般使用的是它的子类: ValueAnimator.O ...

  5. Android Animation学习(二) ApiDemos解析:基本Animatiors使用

    Animator类提供了创建动画的基本结构,但是一般使用的是它的子类: ValueAnimator.ObjectAnimator.AnimatorSet ApiDemos中Animation部分是单独 ...

  6. Android:日常学习笔记(9)———探究持久化技术

    Android:日常学习笔记(9)———探究持久化技术 引入持久化技术 什么是持久化技术 持久化技术就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑关机的情况下,这些数据仍然不会丢失 ...

  7. 【转】Android开发学习笔记(一)——初识Android

    对于一名程序员来说,“自顶向下”虽然是一种最普通不过的分析问题和解决问题的方式,但其却是简单且较为有效的一种.所以,将其应用到Android的学习中来,不至于将自己的冲动演变为一种盲目和不知所措. 根 ...

  8. Android:日常学习笔记(10)———使用LitePal操作数据库

    Android:日常学习笔记(10)———使用LitePal操作数据库 引入LitePal 什么是LitePal LitePal是一款开源的Android数据库框架,采用了对象关系映射(ORM)的模式 ...

  9. Android自动化学习笔记之MonkeyRunner:官方介绍和简单实例

    ---------------------------------------------------------------------------------------------------- ...

随机推荐

  1. shell编程实例2

    1.vim  read_PERSON.sh 2. #!/bin/bash echo "What is your name?" read PERSON echo "Hell ...

  2. Elasticsearch Painless语言(实现搜索打分基础)

    With the release of Elasticsearch 5.x came Painless, Elasticsearch's answer to safe, secure, and per ...

  3. Django框架(三)

    0627内容: 上节回顾: 1. FBV.CBV 2. 数据库操作 class UserGroup(models.Model): """ 部门 3 "" ...

  4. New Concept English three (26)

    34w/m 54words No one can avoid being influenced by advertisements. Much as we may pride ourselves on ...

  5. jsp中的session

    浏览器和服务器的异常通话 常用方法 setAttribute(String key,Object value);//设置值 getAttribute(String key); //取值 Invalid ...

  6. Go开发Struct转换成map两种方式比较

    最近做Go开发的时候接触到了一个新的orm第三方框架gorose,在使用的过程中,发现没有类似beego进行直接对struct结构进行操作的方法,有部分API是通过map进行数据库相关操作,那么就需要 ...

  7. php之opcodes

    opcode是一种php脚本编译之后的语言. 例如: <?php echo "Hello World"; $a = 1 + 1; echo $a; ?> php执行这段 ...

  8. I.MX6 Android 设备节点权限

    /********************************************************************************** * I.MX6 Android ...

  9. 神奇的TextField(1)

    先看一大段测试代码,每个小方法的注释行是输出结果. private var text_content:TextField; private function textFieldDemo():void{ ...

  10. HDU - 6166:Senior Pan(顶点集合最短路&二进制分组)

    Senior Pan fails in his discrete math exam again. So he asks Master ZKC to give him graph theory pro ...