首页 > 代码库 > 移动端5大数据库比较
移动端5大数据库比较
5个用于移动开发的最流行数据库对比
五个数据库分别从数据库存储类型、优点、缺点、特点、API接口、操作示例六个方面进行阐述。
BerkeleyDB
数据库存储类型
relational,objects, key-value pairs, documents
2. 优点
a) 处理速度快。
b) BDB并发高于RDBMS。
c) 基于HASH支持select数据比RDBMS快。
d) 高度可移植。不论是32bit,64bit,它可以运行在高端服务器、桌面系统、掌上电脑等。
e) 函数库本身很紧凑,可以运行在高度受限的嵌入式系统上。
3. 缺点
a) 不支持结构化的查询语言(SQL)
b) 只提供了简单的逻辑操作,对于要求不同服务的应用不合适。
c) 没有提供对交互查询的支持,也没有提供图形化的报表工具。
4. 特点
a) 采用嵌入式模型。直接连接到应用程序中,和应用程序在同一内存空间运行。这一点是比关系型数据库优越的主要原因,因为和应用运行在同一地址空间,省掉了数据库操作时的进程间通信,不管是在单机还是分布式系统上,进程间通信所花的时间多于函数调用所要的时间。
b) BDB对所有的操作提供了简洁的函数调用接口,无需对查询语言进行解析,也不需要预执行。
c) 提供了简单的数据访问服务。如:在表中插入一条记录、从表中删除一条记录、通过查询键(key)从表中查找一条记录、更新表中已有的一条记录。
5. API接口
a) 打开database环境。
Environment myDbEnvironment=new Environment(File file, envConfig);
关闭database环境
myDbEnvironment.close();
清理日志。
myDbEnvironment.cleanLog();
打开database。
myDbEnvironment.openDatabase( Stringdataname,DatabaseConfig dbConfig);
关闭database。
myDbEnvironment.Close();
添加记录
Database.put();
不重复添加记录
Database.putNoOverwrite();
读取记录
Database.get();
删除记录
Database.delete();
6. 操作示例
详见附录1.
Couchbase-lite
数据库存储类型
documents
2. 优点
a) 轻量级、嵌入式,可同步的NoSql数据引擎。提供了一种简单的方法来跨设备同步应用程序的数据。
b) 支持不同结构的数据存储。
c) 支持对等复制。
d) 支持低延时、离线下获取数据。
3. 缺点
a) 不支持sql语句。
4. 特点
a) 主要模块包含:Manager(类似数据库)、Database(类似table)、Document(类似)、Revision、Attachment、View、Query、Replication、Model。
b) 由Manager管理一系列的Database。
5. API接口
a) 创建一个Manager实例。
Managermanager=new Manager(AndroidContext aContext,Option o);
创建一个Database实例。
Database db=manager.getDatabase(Stringname);//如果manager中没有该database,就会自动生成一个空的database。
添加数据改动说明。
db.addChangeListener(new ChangeListener(){})
删除database。
myDatabase.delete();
创建document。
Document document =database.createDocument();
读取document。
Document doc =database.getDocument(myDocId);
更新document。
Document doc = database.getDocument(myDocId);
doc.update(new Document.DocumentUpdater() {}
h) 删除document。
doc.delete();
6. 操作
详见附录2.
SqlLite
数据库存储类型
relational
2. 优点
a) 轻量
b) 单一文件。所有的信息(比如表、视图、触发器等)都包含在同一个文件中,这个文件可以拷贝到其他目录或者其他机器,不需要其他改变。
c) 跨平台、可移植。不仅支持主流操作系统,还支持其他的android、windows phone、symbin等。
d) 关系型数据库、支持sql语句。
3. 缺点
a) SQL标准不全,官网上列举了所有不支持的SQL92标准,最明显是不支持外键约束。
b) 需要其他机器读取SQLite数据库文件,当使用NFS并发读取的时候出现数据损坏问题。
c) 省略了一些比较有用的特性,例如高并发性、严格的存取控制,存储过程等等。
4. 特点
a) 它是大小写不敏感的。
b) 字段是无类型(Typelessness)的。
5. API接口
a) 创建数据库。
Publicstatic SQLiteDatabase openOrCreateDatabase (String path, SQLiteDatabase.CursorFactory factory);
关闭数据库
public void close ();
增加数据。
SQLiteDatabase.insert(Stringtable,String nullColumnHack, ContentValues values);
删除数据。
SQLiteDatabase.delete(Stringtable,String whereClause,String [] whereArgs);
更新数据。
public int update (String table, ContentValues values, String whereClause, String[] whereArgs);
查寻数据。
public Cursor query (booleandistinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);
6. 操作
详见附录3.
LevelDB
数据库存储类型
key-valuepairs
2. 优点
a) 操作简单,写入速度快。
3. 缺点
b) 接口较少,很多复杂的数据库操作不支持。
4. 特点
a) LevelDb是一个持久化存储的KV系统,将大部分数据存储到磁盘上。
b) 存储数据时,会根据记录的key有序存储,也就是说相邻的key值在存储文件中是依次顺序存储的,而用户可以自己定义key的比较函数。
c) LevelDb的操作接口很简单,基本操作包括写记录,读记录以及删除记录。
d) 支持数据快照功能,使得读取操作不受写操作影响,可以再读操作过程中始终看到一致的数据。
5. API接口
a) 插入一条数据。
publicSnapshot put(byte[] key, byte[] value, WriteOptions options) throwsDBException;
删除一条数据。
publicSnapshot delete(byte[] key, WriteOptions options) throws DBException;
获取一条数据。
public byte[]get(byte[] key, ReadOptions options) throws DBException;
6. 操作
详见附录4
UnQlite
数据库存储类型
key-valuepairs, documents
2. 优点
a) 零配置,事务化。
b) 跨平台。可以在32bit,64bit之间切换。
c) 是一个自包含的C库,无外部依赖。
d) 是一个标准的key/value存储,但是与BerkeleyDB和LevelDB相比,拥有更加丰富的特性集,包括支持事务、并发读等。
3. 缺点
a) 不支持java语言。
4. 特点
a) 无服务器的数据库引擎。
b) 支持事务化。
c) 零配置。
d) 单一数据库文件,不使用临时文件。
e) 跨平台的文件格式。
f) 自包含C库,对外部无依赖。
g) 标准的key-value存储格式。
h) 基于jx9标准存储json文档存储数据库。
i) 支持游标,满足线性记录遍历。
j) 简单、清晰,容易使用的API。
5. API接口
无java接口
6. 操作
无java接口
综合意见:
sqllite是最符合传统数据库的关系型数据库,如果所要保存的数据是符合关系型数据库的规则,那sqllite是最佳选择。
Couchbase-lite最明显的特点是跨设备同步应用程序的数据。它对每一次的数据操作都会生成操作说明文件,以此来对不同设备的操作进行数据统一,所以如果类似icloud等操作可以选用Couchbase-lite数据库。
UnQlite不支持java API,如果使用c,可以考虑。
LevelDB和Berkeley DB都支持key-value存储,BerkeleyDB支持数千的并发线程同时操作数据库,支持最大256Tb的数据,LevelDB支持快照功能,也支持数据的正向和反向遍历访问,支持多线程同步,但是不支持多线程同时访问,各有利弊,可根据具体的需求进行选择。
附录1
package je.gettingStarted;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import java.io.File;
..
Environment myDbEnvironment = null;
try {
EnvironmentConfig envConfig = new EnvironmentConfig();
envConfig.setAllowCreate(true);//如果不存在则创建一个
myDbEnvironment = new Environment(new File("/export/dbEnv"), envConfig);
} catch (DatabaseException dbe) {
// 错误处理
}
2. 关闭database环境
可以通过Environment.close()这个方法来关闭database环境,当你完成数据库操作后一定要关闭数据库环境。
示例:
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
...
try {
if (myDbEnvironment != null) {
myDbEnvironment.close();
}
} catch (DatabaseException dbe) {
// Exception handling goes here
}
3. 清理日志
通常在关闭数据库连接的时候,有必要清理下日志,用以释放更多的磁盘空间。我们可以在Environment.close前执行下Environment.cleanLog()来达到此目的。
示例:
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
...
try {
if (myDbEnvironment != null) {
myDbEnvironment.cleanLog(); // 在关闭环境前清理下日志
myDbEnvironment.close();
}
} catch (DatabaseException dbe) {
// Exception handling goes here
}
4. Database环境的配置
可以通过EnvironmentConfig这个对象来配置database环境。如果想得到当前环境的配置信息则可以通过Environment.getConfig()方法得到当前环境的配置信息。
也可以使用EnvironmentMutableConfig来配置环境,其实 EnvironmentConfig是EnvironmentMutableConfig的子类,所以EnvironmentMutableConfig能够使用的设置,EnvironmentConfig也同样能够使用。
如果你要获取当前环境的使用情况,那么你可以通过使用EnvironmentStats.getNCacheMiss().来监视RAM cache命中率。EnvironmentStats可以由Environment.getStats()方法获取。
EnvironmentConfig常见方法介绍
EnvironmentConfig.setAllowCreate() ;
如果设置了true则表示当数据库环境不存在时候重新创建一个数据库环境,默认为false.
EnvironmentConfig.setReadOnly()
以只读方式打开,默认为false.
EnvironmentConfig.setTransactional()
事务支持,如果为true,则表示当前环境支持事务处理,默认为false,不支持事务处理。
EnvironmentMutableConfig的介绍
setCachePercent()
设置当前环境能够使用的RAM占整个JVM内存的百分比。
setCacheSize()
设置当前环境能够使用的最大RAM。单位BYTE
setTxnNoSync()
当提交事务的时候是否把缓存中的内容同步到磁盘中去。
true 表示不同步,也就是说不写磁盘
setTxnWriteNoSync()
当提交事务的时候,是否把缓冲的log写到磁盘上
true 表示不同步,也就是说不写磁盘
示例一:
package je.gettingStarted;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import java.io.File;
...
Environment myDatabaseEnvironment = null;
try {
EnvironmentConfig envConfig = new EnvironmentConfig();
//当环境不存在的时候自动创建环境
envConfig.setAllowCreate(true);
//设置支持事务
envConfig.setTransactional(true);
myDatabaseEnvironment =
new Environment(new File("/export/dbEnv"), envConfig);
} catch (DatabaseException dbe) {
System.err.println(dbe.toString());
System.exit(1);
}
示例二:
package je.gettingStarted;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentMutableConfig;
import java.io.File;
...
try {
Environment myEnv = new Environment(new File("/export/dbEnv"), null);
EnvironmentMutableConfig envMutableConfig =
new EnvironmentMutableConfig();
envMutableConfig.setTxnNoSync(true);
myEnv.setMutableConfig(envMutableConfig);
} catch (DatabaseException dbe) {
// Exception handling goes here
}
示例三:
import com.sleepycat.je.Environment;
...
//没有命中的CACHE
long cacheMisses = myEnv.getStats(null).getNCacheMiss();
...
5. Database操作
在BDB中,数据是以key/value方式成队出现的。
打开database
可以通过environment.openDatabase()方法打开一个database,在调用这个方法的时候必须指定database的名称。和databaseConfig() (注:数据库设置)
示例:
package je.gettingStarted;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import java.io.File;
...
Environment myDbEnvironment = null;
Database myDatabase = null;
...
try {
// 打开一个环境,如果不存在则创建一个
EnvironmentConfig envConfig = new EnvironmentConfig();
envConfig.setAllowCreate(true);
myDbEnvironment = new Environment(new File("/export/dbEnv"), envConfig);
// 打开一个数据库,如果数据库不存在则创建一个
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(true);
myDatabase = myDbEnvironment.openDatabase(null,
"sampleDatabase", dbConfig); //打开一个数据库,数据库名为
//sampleDatabase,数据库的配置为dbConfig
} catch (DatabaseException dbe) {
// 错误处理
}
关闭database
通过调用Database.close()方法来关闭数据库,但要注意,在关闭数据库前必须得先把游标先关闭。
使用示例:
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Database;
import com.sleepycat.je.Environment;
...
try {
if (myDatabase != null) {
myDatabase.close();
}
if (myDbEnvironment != null) {
myDbEnvironment.close();
}
} catch (DatabaseException dbe) {
// 错误处理
}
设置数据库属性
其实设置数据库属性跟设置环境属性差不多,JE中通过DatabaseConfig对象来设置数据库属性。你能够设置的数据库属性如下。
DatabaseConfig.setAllowCreate()
如果是true的话,则当不存在此数据库的时候创建一个。
DatabaseConfig.setBtreeComparator()
设置用于Btree比较的比较器,通常是用来排序
DatabaseConfig.setDuplicateComparator()
设置用来比较一个key有两个不同值的时候的大小比较器。
DatabaseConfig.setSortedDuplicates()
设置一个key是否允许存储多个值,true代表允许,默认false.
DatabaseConfig.setExclusiveCreate()
以独占的方式打开,也就是说同一个时间只能有一实例打开这个database。
DatabaseConfig.setReadOnly()
以只读方式打开database,默认是false.
DatabaseConfig.setTransactional()
如果设置为true,则支持事务处理,默认是false,不支持事务。
使用示例:
package je.gettingStarted;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
...
// Environment open omitted for brevity
...
Database myDatabase = null;
try {
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(true);
dbConfig.setSortedDuplicates(true);
myDatabase =
myDbEnv.openDatabase(null,
"sampleDatabase",
dbConfig);
} catch (DatabaseException dbe) {
// Exception handling goes here.
}
一些用来管理的方法
l Database.getDatabaseName()
取得数据库的名称
如:String dbName = myDatabase.getDatabaseName();
l Database.getEnvironment()
取得包含这个database的环境信息
如:Environment theEnv = myDatabase.getEnvironment();
l Database.preload()
预先加载指定bytes的数据到RAM中。
如:myDatabase.preload(1048576l); // 1024*1024
l Environment.getDatabaseNames()
返回当前环境下的数据库列表
如:
import java.util.List;
List myDbNames = myDbEnv.getDatabaseNames();
for(int i=0; i < myDbNames.size(); i++) {
System.out.println("Database Name: " + (String)myDbNames.get(i));
}
l Environment.removeDatabase()
删除当前环境中指定的数据库。
如:
String dbName = myDatabase.getDatabaseName();
myDatabase.close();
myDbEnv.removeDatabase(null, dbName);
l Environment.renameDatabase()
给当前环境下的数据库改名
如:
String oldName = myDatabase.getDatabaseName();
String newName = new String(oldName + ".new", "UTF-8");
myDatabase.close();
myDbEnv.renameDatabase(null, oldName, newName);
l Environment.truncateDatabase()
清空database内的所有数据,返回清空了多少条记录。
如:
Int numDiscarded= myEnv.truncate(null,
myDatabase.getDatabaseName(),true);
System.out.println("一共删除了 " + numDiscarded +" 条记录 从数据库 " + myDatabase.getDatabaseName());
6. Database 记录
JE的记录包含两部分,key键值和value数据值,这两个值都是通过DatabaseEntry对象封装起来,所以说如果要使用记录,则你必须创建两个DatabaseEntry对象,一个是用来做为key,另外一个是做为value.
DatabaseEntry能够支持任何的能够转换为bytes数组形式的基本数据。包括所有的JAVA基本类型和可序列化的对象.
使用记录
示例一:把字符串转换DatabaseEntry
package je.gettingStarted;
import com.sleepycat.je.DatabaseEntry;
...
String aKey = "key";
String aData = "http://www.mamicode.com/data";
try {
//设置key/value,注意DatabaseEntry内使用的是bytes数组
DatabaseEntry theKey=new DatabaseEntry(aKey.getBytes("UTF-8"));
DatabaseEntry theData=http://www.mamicode.com/new DatabaseEntry(aData.getBytes("UTF-8"));
} catch (Exception e) {
// 错误处理
}
示例二:把DatabaseEntry里的数据转换成字符串
byte[] myKey = theKey.getData();
byte[] myData = http://www.mamicode.com/theData.getData();
String key = new String(myKey, "UTF-8");
String data = http://www.mamicode.com/new String(myData,"UTF-8");
读和写database 记录
读和写database记录的时候大体是基本一样的,唯一有区别的是每个key写是否允许写多条记录,默认情况下是不支持多条记录的。
a) 你可以使用如下方法向database 里添加记录
Database.put()
向database中添加一条记录。如果你的database不支持一个key对应多个data或当前database中已经存在该key了,则使用此方法将使用新的值覆盖旧的值。
Database.putNoOverwrite()
向database中添加新值但如果原先已经有了该key,则不覆盖。不管database是否允许支持多重记录(一个key对应多个value),只要存在该key就不允许添加,并且返回perationStatus.KEYEXIST信息。
Database.putNoDupData()
想database中添加一条记录,如果database中已经存在了相同的 key和value则返回 OperationStatus.KEYEXIST.
使用示例:
package je.gettingStarted;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
...
String aKey = "myFirstKey";
String aData = "http://www.mamicode.com/myFirstData";
try {
DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8"));
DatabaseEntry theData = http://www.mamicode.com/new DatabaseEntry(aData.getBytes("UTF-8"));
myDatabase.put(null, theKey, theData);
} catch (Exception e) {
// Exception handling goes here
}
b) 你可以使用如下方法从database 里读取记录
1. Database.get()
基本的读记录的方法,通过key的方式来匹配,如果没有改记录则返回OperationStatus.NOTFOUND。
l Database.getSearchBoth()
通过key和value来同时匹配,同样如果没有记录匹配key和value则会返回OperationStatus.NOTFOUND。
使用示例:
package je.gettingStarted;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
...
String aKey = "myFirstKey";
try {
DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8"));
DatabaseEntry theData = http://www.mamicode.com/new DatabaseEntry();
if (myDatabase.get(null, theKey, theData, LockMode.DEFAULT) ==
OperationStatus.SUCCESS) {
byte[] retData = http://www.mamicode.com/theData.getData();
String foundData = http://www.mamicode.com/new String(retData,"UTF-8");
System.out.println("For key: ‘" + aKey + "‘ found data: ‘" +
foundData + "‘.");
} else {
System.out.println("No record found for key ‘" + aKey + "‘.");
}
} catch (Exception e) {
// Exception handling goes here
}
c) 删除记录
可以使用Database.delete()这个方法来删除记录。如果你的database支持多重记录,则当前key下的所有记录都会被删除,如果只想删除多重记录中的一条则可以使用游标来删除。
当然你也可以使用Environment.truncateDatabase()这个方法来清空database 中的所有记录。
使用示例:
package je.gettingStarted;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
...
try {
String aKey = "myFirstKey";
DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8"));
myDatabase.delete(null, theKey);
} catch (Exception e) {
}
d) 提交事务
当你对database进行了写操作的时候,你的修改不一定马上就能生效,有的时候他仅仅是缓存在RAM中,如果想让你的修改立即生效,则可以使用Environment.sync()方法来把数据同步到磁盘中去。
e) 不同类型的数据的处理
1. 你可以使用DatabaseEntry来绑定基本的JAVA数据类型,主要有String、Character、Boolean、Byte、Short、Integer、Long、Float、Double.
使用示例一:
package je.gettingStarted;
import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.je.DatabaseEntry;
...
try {
String aKey = "myLong";
DatabaseEntry theKey = new
DatabaseEntry(aKey.getBytes("UTF-8"));
Long myLong = new Long(123456789l);
DatabaseEntry theData = http://www.mamicode.com/new DatabaseEntry();
EntryBinding myBinding =
TupleBinding.getPrimitiveBinding(Long.class);
myBinding.objectToEntry(myLong, theData);
myDatabase.put(null, theKey, theData);
} catch (Exception e) {
// Exception handling goes here
}
使用示例二:
package je.gettingStarted;
import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
...
Database myDatabase = null;
try {
String aKey = "myLong";
DatabaseEntry theKey = new
DatabaseEntry(aKey.getBytes("UTF-8"));
DatabaseEntry theData = http://www.mamicode.com/new DatabaseEntry();
EntryBinding myBinding =
TupleBinding.getPrimitiveBinding(Long.class);
OperationStatus retVal = myDatabase.get(null, theKey, theData,
LockMode.DEFAULT);
String retKey = null;
if (retVal == OperationStatus.SUCCESS) {
Long theLong = (Long) myBinding.entryToObject(theData);
retKey = new String(theKey.getData(), "UTF-8");
System.out.println("For key: ‘" + retKey + "‘ found Long: ‘" +
theLong + "‘.");
} else {
System.out.println("No record found for key ‘" + retKey + "‘.");
}
} catch (Exception e) {
// Exception handling goes here
}
2. 可序列化的对象的绑定
1. 首先你需要创建一个可序列化对象
2. 打开或创建你的database,你需要两个,一个用来存储你的数据,另外一个用来存储类信息。
3. 实例化catalog类,这个时候你可以使用com.sleepycat.bind.serial.StoredClassCatalog,来存储你的类信息。
4. 通过com.sleepycat.bind.serial.SerialBinding来绑定数据和类。
5. 绑定并存储数据。
示例:
l 创建一个可序列化的对象
package je.gettingStarted;
import java.io.Serializable;
public class MyData implements Serializable {
private long longData;
private double doubleData;
private String description;
MyData() {
longData = http://www.mamicode.com/0;
doubleData = http://www.mamicode.com/0.0;
description = null;
}
public void setLong(long data) {
longData = http://www.mamicode.com/data;
}
public void setDouble(double data) {
doubleData = http://www.mamicode.com/data;
}
public void setDescription(String data) {
description = data;
}
public long getLong() {
return longData;
}
public double getDouble() {
return doubleData;
}
public String getDescription() {
return description;
}
}
l 存储数据
package je.gettingStarted;
import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
...
String aKey = "myData";
MyData data2Store = new MyData();
data2Store.setLong(123456789l);
data2Store.setDouble(1234.9876543);
data2Store.setDescription("A test instance of this class");
try {
DatabaseConfig myDbConfig = new DatabaseConfig();
myDbConfig.setAllowCreate(true);
myDbConfig.setSortedDuplicates(true);
Database myDatabase = myDbEnv.openDatabase(null, "myDb", myDbConfig);
myDbConfig.setSortedDuplicates(false);
//打开用来存储类信息的库
Database myClassDb = myDbEnv.openDatabase(null, "classDb", myDbConfig);
// 3)创建catalog
StoredClassCatalog classCatalog = new StoredClassCatalog(myClassDb);
// 4)绑定数据和类
EntryBinding dataBinding = new SerialBinding(classCatalog,
MyData.class);
DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8"));
// 向DatabaseEntry里写数据
DatabaseEntry theData = http://www.mamicode.com/new DatabaseEntry();
dataBinding.objectToEntry(data2Store, theData);
myDatabase.put(null, theKey, theData);
} catch (Exception e) {
// 错误处理
}
l 读数据
package je.gettingStarted;
import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
...
// The key data.
String aKey = "myData";
try {
DatabaseConfig myDbConfig = new DatabaseConfig();
myDbConfig.setAllowCreate(false);
Database myDatabase = myDbEnv.openDatabase(null, "myDb", myDbConfig);
//用来存储类信息的库
Database myClassDb = myDbEnv.openDatabase(null, "classDb", myDbConfig);
// 实例化catalog
StoredClassCatalog classCatalog = new StoredClassCatalog(myClassDb);
// 创建绑定对象
EntryBinding dataBinding = new SerialBinding(classCatalog,
MyData.class);
DatabaseEntry theKey = new DatabaseEntry(aKey.getBytes("UTF-8"));
DatabaseEntry theData = http://www.mamicode.com/new DatabaseEntry();
myDatabase.get(null, theKey, theData, LockMode.DEFAULT);
// Recreate the MyData object from the retrieved DatabaseEntry using
// 根据存储的类信息还原数据
MyData retrievedData=http://www.mamicode.com/(MyData)dataBinding.entryToObject(theData);
} catch (Exception e) {
// Exception handling goes here
}
3. 自定义对象的绑定
使用tuple binding 来绑定自定义数据的步骤
①. 实例化你要存储的对象
②. 通过com.sleepycat.bind.tuple.TupleBinding class来创建一个tuple binding。
③. 创建一个database,跟序列化的对象不同,你只需要创建一个。
④. 通过继承第二步的类来创建一个entry binding 对象。
⑤. 存储和使用数据
使用示例:
l 创建要存储的对象
package je.gettingStarted;
public class MyData2 {
private long longData;
private Double doubleData;
private String description;
public MyData2() {
longData = http://www.mamicode.com/0;
doubleData = http://www.mamicode.com/new Double(0.0);
description = "";
}
public void setLong(long data) {
longData = http://www.mamicode.com/data;
}
public void setDouble(Double data) {
doubleData = http://www.mamicode.com/data;
}
public void setString(String data) {
description = data;
}
public long getLong() {
return longData;
}
public Double getDouble() {
return doubleData;
}
public String getString() {
return description;
}
}
l 创建一个TupleBinding对象
package je.gettingStarted;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.bind.tuple.TupleInput;
import com.sleepycat.bind.tuple.TupleOutput;
public class MyTupleBinding extends TupleBinding {
// 把对象转换成TupleOutput
public void objectToEntry(Object object, TupleOutput to) {
MyData2 myData = http://www.mamicode.com/(MyData2)object;
to.writeDouble(myData.getDouble().doubleValue());
to.writeLong(myData.getLong());
to.writeString(myData.getString());
}
//把TupleInput转换为对象
public Object entryToObject(TupleInput ti) {
Double theDouble = new Double(ti.readDouble());
long theLong = ti.readLong();
String theString = ti.readString();
MyData2 myData = http://www.mamicode.com/new MyData2();
myData.setDouble(theDouble);
myData.setLong(theLong);
myData.setString(theString);
return myData;
}
}
l 读和写数据
package je.gettingStarted;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.je.DatabaseEntry;
...
TupleBinding keyBinding = new MyTupleBinding();
MyData2 theKeyData = http://www.mamicode.com/new MyData2();
theKeyData.setLong(123456789l);
theKeyData.setDouble(new Double(12345.6789));
theKeyData.setString("My key data");
DatabaseEntry myDate = new DatabaseEntry();
try {
// 把theKeyData 存储到DatabaseEntry里
keyBinding.objectToEntry(theKeyData, myDate);
...
// Database 进行了一些读和写操作
...
// Retrieve the key data
theKeyData = http://www.mamicode.com/(MyData2) keyBinding.entryToObject(myDate);
} catch (Exception e) {
// 错误处理
}
f) 使用比较器
JE是使用BTrees来组织结构的,这意味着当对database的读和写需要涉及BTrees间的节点比较。这些比较在key间是经常的发生的。如果你的database支持多重记录,那么也会存在data间的比较。
默认的情况JE的比较器是按照字节的方式来进行比较的,这通常情况下能处理大多数的情况。但有的时候确实需要自定义比较器用于特殊的通途,比如说按照key来排序。
l 创建自己的比较器
其实很简单,只要你重写Comparator class中的比较方法(compare)就可以了,通过Comparator.compare()会传递给你两个byte 数组形式的值,如果你知道结构,则可以根据你自己定义的方法来进行比较
示例:
package je.gettingStarted;
import java.util.Comparator;
public class MyDataComparator implements Comparator {
public MyDataComparator() {}
public int compare(Object d1, Object d2) {
byte[] b1 = (byte[])d1;
byte[] b2 = (byte[])d2;
String s1 = new String(b1, "UTF-8");
String s2 = new String(b2, "UTF-8");
return s1.compareTo(s2);
}
}
l 让database使用你自定义的比较器
如果你想改变database中基本的排序方式,你只能重新创建database并重新导入数据。
①. DatabaseConfig.setBtreeComparator()
用于在database里两个key的比较
②. DatabaseConfig.setOverrideBtreeComparator()
如果为true则代表让database使用 DatabaseConfig.setBtreeComparator()设置的比较器来代替默认的比较器。
③. DatabaseConfig.setDuplicateComparator()
用于database可以使用多重记录的时候的data的 比较。
④. DatabaseConfig.setOverrideDuplicateComparator()
如果为true则代表让database使用 DatabaseConfig. setDuplicateComparator()设置 的比 较器来代替默认的比较器。
使用示例:
package je.gettingStarted;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import java.util.Comparator;
...
try {
DatabaseConfig myDbConfig = new DatabaseConfig();
myDbConfig.setAllowCreate(true);
// 设置要使用的比较器
myDbConfig.setDuplicateComparator(MyDataComparator.class);
// 使用自己定义的比较器
myDbConfig.setSortedDuplicates(true);
Database myDatabase = myDbEnv.openDatabase(null, "myDb", myDbConfig);
} catch (DatabaseException dbe) {
// Exception handling goes here
}
六、 游标的使用
游标提供了遍历你database中记录的一种机制,使用游标你可以获取,添加,和删除你的记录。如果你的database支持多重记录,则可以通过游标访问同一个key下的每一个记录。
l 打开和关闭游标
要想使用游标则你必须通过Database.openCursor()方法来打开一个游标,你可以通过CursorConfig来配置你的游标。
可以通过Cursor.close()方法来关闭游标。请注意在关闭database和环境前一定要关闭游标,否则会带来错误。
打开游标示例:
package je.gettingStarted;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import java.io.File;
...
Environment myDbEnvironment = null;
Database myDatabase = null;
Cursor myCursor = null;
try {
myDbEnvironment = new Environment(new File("/export/dbEnv"), null);
myDatabase = myDbEnvironment.openDatabase(null, "myDB", null);
myCursor = myDatabase.openCursor(null, null);
} catch (DatabaseException dbe) {
// Exception handling goes here ...
}
关闭游标示例:
package je.gettingStarted;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.Environment;
...
try {
...
} catch ... {
} finally {
try {
if (myCursor != null) {
myCursor.close();
}
if (myDatabase != null) {
myDatabase.close();
}
if (myDbEnvironment != null) {
myDbEnvironment.close();
}
} catch(DatabaseException dbe) {
System.err.println("Error in close: " + dbe.toString());
}
}
l 通过游标来获取记录
可以通过游标的Cursor.getNext()方法来遍历记录,Cursor.getNext()表示游标指针向下移动一条记录。同样的Cursor.getPrev()表示游标指针向上移动一条记录。
使用示例一:
package je.gettingStarted;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
...
Cursor cursor = null;
try {
cursor = myDatabase.openCursor(null, null);
DatabaseEntry foundKey = new DatabaseEntry();
DatabaseEntry foundData = http://www.mamicode.com/new DatabaseEntry();
// 通过cursor.getNex方法来遍历记录
while (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) ==
OperationStatus.SUCCESS) {
String keyString = new String(foundKey.getData(), "UTF-8");
String dataString = new String(foundData.getData(), "UTF-8");
System.out.println("Key | Data : " + keyString + " | " +
dataString + "");
}
} catch (DatabaseException de) {
System.err.println("Error accessing database." + de);
} finally {
// 使用后必须关闭游标
cursor.close();
}
使用示例二:
package je.gettingStarted;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
...
Cursor cursor = null;
try {
...
// Open the cursor.
cursor = myDatabase.openCursor(null, null);
DatabaseEntry foundKey = new DatabaseEntry();
DatabaseEntry foundData = http://www.mamicode.com/new DatabaseEntry();
// 使用cursor.getPrev方法来遍历游标获取数据
while (cursor.getPrev(foundKey, foundData, LockMode.DEFAULT)
== OperationStatus.SUCCESS) {
String theKey = new String(foundKey.getData(), "UTF-8");
String theData = http://www.mamicode.com/new String(foundData.getData(),"UTF-8");
System.out.println("Key | Data : " + theKey + " | " + theData + "");
}
} catch (DatabaseException de) {
System.err.println("Error accessing database." + de);
} finally {
// 使用后必须关闭游标
cursor.close();
}
l 搜索数据
你可以通过游标方式搜索你的database记录,你也可以通过一个key来搜索你的记录,同样的你也可以通过key和value组合在一起来搜索记录。如果查询失败,则游标会返回OperationStatus.NOTFOUND。
游标支持都检索方法如下:
1) Cursor.getSearchKey()
通过key的方式检索,使用后游标指针将移动到跟当前key匹配的第一项。
2) Cursor.getSearchKeyRange()
把游标移动到大于或等于查询的key的第一个匹配key,大小比较是通过你设置的比较器来完成的,如果没有设置则使用默认的比较器。
3) Cursor.getSearchBoth()
通过key和value方式检索,然后把游标指针移动到与查询匹配的第一项。
4) Cursor.getSearchBothRange()
把游标移动到所有的匹配key和大于或等于指定的data的第一项。
比如说database存在如下的key/value记录,,大小比较是通过你设置的比较器来完成的,如果没有设置则使用默认的比较器。
假设你的database存在如下的记录。
Alabama/Athens Alabama/Florence Alaska/Anchorage Alaska/Fairbanks Arizona/Avondale Arizona/Florence然后查询
查询的key
查询的data
游标指向
Alaska
Fa
Alaska/Fairbanks
Arizona
Fl
Arizona/Florence
Alaska
An
Alaska/Anchorage
使用示例:
package je.gettingStarted;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
...
String searchKey = "Alaska";
String searchData = "http://www.mamicode.com/Fa";
Cursor cursor = null;
try {
...
cursor = myDatabase.openCursor(null, null);
DatabaseEntry theKey =
new DatabaseEntry(searchKey.getBytes("UTF-8"));
DatabaseEntry theData = http://www.mamicode.com/
new DatabaseEntry(searchData.getBytes("UTF-8"));
cursor = myDatabase.openCursor(null, null);
OperationStatus retVal = cursor.getSearchBothRange(theKey,
theData, LockMode.DEFAULT);
if (retVal == OperationStatus.NOTFOUND) {
System.out.println(searchKey + "/" + searchData +
" not matched in database " +
myDatabase.getDatabaseName());
} else {
String foundKey = new String(theKey.getData(), "UTF-8");
String foundData = http://www.mamicode.com/new String(theData.getData(),"UTF-8");
System.out.println("Found record " + foundKey + "/" + foundData +
"for search key/data: " + searchKey +
"/" + searchData);
}
} catch (Exception e) {
// Exception handling goes here
} finally {
cursor.close();
}
l 使用游标来定位多重记录
如果你的库支持多重记录,你可以使用游标来遍历一个key下的多个data.
1) Cursor.getNext(), Cursor.getPrev()
获取上一条记录或下一条记录
2) Cursor.getSearchBothRange()
用语定位到满足指定data的第一条记录。
3) Cursor.getNextNoDup(), Cursor.getPrevNoDup()
跳到上一个key的最后一个data或下一个key的第一个data,忽略 当前key多重记录的存在。
4) Cursor.getNextDup(), Cursor.getPrevDup()
在当前key中把指针移动到前一个data或后一个data.
5) Cursor.count()
获取当前key下的data总数。
使用示例:
package je.gettingStarted;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
...
Cursor cursor = null;
try {
...
// Create DatabaseEntry objects
// searchKey is some String.
DatabaseEntry theKey = new DatabaseEntry(searchKey.getBytes("UTF-8"));
DatabaseEntry theData = http://www.mamicode.com/new DatabaseEntry();
cursor = myDatabase.openCursor(null, null);
OperationStatus retVal = cursor.getSearchKey(theKey,
theData, LockMode.DEFAULT);
// 如果count超过一个,则遍历
if (cursor.count() > 1) {
while (retVal == OperationStatus.SUCCESS) {
String keyString = new String(theKey.getData(), "UTF-8");
String dataString = new String(theData.getData(), "UTF-8");
System.out.println("Key | Data : " + keyString + " | " +
dataString + "");
retVal = cursor.getNextDup(theKey, theData, LockMode.DEFAULT);
}
}
} catch (Exception e) {
// Exception handling goes here
} finally {
// Make sure to close the cursor
cursor.close();
}
l 通过游标来添加数据
你可以通过游标来向database里添加数据
你可以使用如下方法来向database里添加数据
1) Cursor.put()
如果database不存在key,则添加,如果database存在key但允许多重记录,则可以通过比较器在适当的位置插入数据,如果key已存在且不支持多重记录,则替换原有的数据。
2) Cursor.putNoDupData()
如果存在相同的key和data则返回OperationStatus.KEYEXIST.
如果不存在key则添加数据。
3) Cursor.putNoOverwrite()
如果存在相同的key在database里则返OperationStatus.KEYEXIS,
如果不存在key则添加数据。
package je.gettingStarted;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.OperationStatus;
...
String key1str = "My first string";
String data1str = "My first data";
String key2str = "My second string";
String data2str = "My second data";
String data3str = "My third data";
Cursor cursor = null;
try {
...
DatabaseEntry key1 = new DatabaseEntry(key1str.getBytes("UTF-8"));
DatabaseEntry data1 = new DatabaseEntry(data1str.getBytes("UTF-8"));
DatabaseEntry key2 = new DatabaseEntry(key2str.getBytes("UTF-8"));
DatabaseEntry data2 = new DatabaseEntry(data2str.getBytes("UTF-8"));
DatabaseEntry data3 = new DatabaseEntry(data3str.getBytes("UTF-8"));
cursor = myDatabase.openCursor(null, null);
OperationStatus retVal = cursor.put(key1, data1); // 添加成功
retVal = cursor.put(key2, data2); // 添加成功
retVal = cursor.put(key2, data3); // 如果允许多重记录则添加成功 //否则添加失败
} catch (Exception e) {
// Exception handling goes here
} finally {
// Make sure to close the cursor
cursor.close();
}
l 使用游标来删除记录
你可以通过调用Cursor.delete().方法来删除当前游标所指向的记录。删除后如果没有移动过指针这个时候调用Cursor.getCurrent()还是可以得到当前值的,但移动以后就不可以了。如果没有重设指针,对同一个位置多次调用删除方法,会返回OperationStatus.KEYEMPTY状态。
使用示例:
package je.gettingStarted;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
...
Cursor cursor = null;
try {
...
DatabaseEntry theKey = new DatabaseEntry(searchKey.getBytes("UTF-8"));
DatabaseEntry theData = http://www.mamicode.com/new DatabaseEntry();
cursor = myDatabase.openCursor(null, null);
OperationStatus retVal = cursor.getSearchKey(theKey, theData, LockMode.DEFAULT);
//如果date不是多重记录.
if (cursor.count() == 1) {
System.out.println("Deleting " +
new String(theKey.getData(), "UTF-8") +
"|" +
new String(theData.getData(), "UTF-8"));
cursor.delete();//删除当前记录
}
} catch (Exception e) {
// Exception handling goes here
} finally {
// Make sure to close the cursor
cursor.close();
}
l 修改当前游标所在位置的值
可以通过Cursor.putCurrent()方法来修改,这个方法只有一个参数就是将要修改的值。这个方法不能用在多重记录。
使用示例:
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
...
Cursor cursor = null;
try {
...
DatabaseEntry theKey = new DatabaseEntry(searchKey.getBytes("UTF-8"));
DatabaseEntry theData = http://www.mamicode.com/new DatabaseEntry();
cursor = myDatabase.openCursor(null, null);
OperationStatus retVal = cursor.getSearchKey(theKey, theData,
LockMode.DEFAULT);
//将要被替换的值
String replaceStr = "My replacement string";
DatabaseEntry replacementData = http://www.mamicode.com/
new DatabaseEntry(replaceStr.getBytes("UTF-8"));
cursor.putCurrent(replacementData);//把当前位置用新值替换
} catch (Exception e) {
// Exception handling goes here
} finally {
// Make sure to close the cursor
cursor.close();
}
附录2
1.创建Manager。
public class Application extends android.app.Application {
private Manager manager;
private static Context mContext;
...
@Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
try {
/*
* In Java the Manager instance and all the objects descending
* from it may be used on any thread.
*/
manager = new Manager(new AndroidContext(mContext), Manager.DEFAULT_OPTIONS);
} catch (IOException e) {
Log.e(TAG, "Cannot create Manager instance", e);
return;
}
}
}
2.open a database.
try {
Manager manager = new Manager(new AndroidContext(mContext), Manager.DEFAULT_OPTIONS);
this.db = manager.getDatabase("my-database");
} catch (IOException e) {
Log.e(TAG, "Cannot create database", e);
return;
}
3.如果数据有做过改动,可以添加一个说明(database notification)
try {
Manager manager = new Manager(new AndroidContext(mContext), Manager.DEFAULT_OPTIONS);
Database db = manager.getExistingDatabase("my-database");
if(db != null) {
db.addChangeListener(new ChangeListener() {
public void changed(ChangeEvent event) {
//
// Process the notification here
//
}
});
}
} catch (IOException e) {
Log.e(TAG, "Cannot delete database", e);
return;
}
4.删除database。
try {
myDatabase.delete();
} catch (IOException e) {
Log.e(TAG, "Cannot delete database", e);
return;
}
5.创建document。
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("type", "list");
properties.put("title", title);
properties.put("created_at", currentTimeString);
properties.put("owner", "profile:" + userId);
properties.put("members", new
ArrayList<String>());
Document document = database.createDocument();
document.putProperties(properties);
6.读取document。
Document doc = database.getDocument(myDocId);
// We can directly access properties from the document object:
doc.getProperty("title");
// Or go through its properties dictionary:
Map<String, Object> properties = doc.getProperties();
String owner = (String) properties.get("owner");
7.更新document。
Document doc = database.getDocument(myDocId);
doc.update(new Document.DocumentUpdater() {
@Override
public boolean update(UnsavedRevision newRevision) {
Map<String, Object> properties = newRevision.getUserProperties();
properties.put("title", title);
properties.put("notes", notes);
newRevision.setUserProperties(properties);
return true;
}
})
8.删除document。
Document task = (Document) database.getDocument("task1");
task.delete();
附录3
public class DBHelper extends SQLiteOpenHelper{
private static final String DB_NAME="coll.db";
private static final String TBL_NAME="CollTbl";
private staic final String CREATE_TBL="create table"+"CollTbl(_id integer primary key autoincrement,name text,url text,desc text)";
private SQLiteDatabase db;
DBHelper(Context c){
super(c,DB_NAME,null,2);
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL(CREATE_TBL);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
}
public insert(ContentValues values){
SQLiteDatabase db=getWritableDatabase();
db.insert(TBL_NAME,null,values);
db.close();
}
public Cursor query(){
SQLiteDatabase db=getWritableDatabase();
Cursor c=db.query(TBL_NAME,null,null,null,null,null,null);
}
public void del(int id){
if(db==null){
db=getWritableDatabase();
db.delete(TBL_NAME,"_id=?",new String[]{String.valueOf(id)});
}
}
public void close(){
if(db!=null){
sb.close();
}
}
}
附录4
1.打开关闭database
Options options = new Options();
options.createIfMissing(true);
DB db = factory.open(new File("example"), options);
try {
// Use the db in here....
} finally {
// Make sure you close the db to shutdown the
// database and avoid resource leaks.
db.close();
}
2.添加、获取、删除key/values.
db.put(bytes("Tampa"), bytes("rocks"));
String value = http://www.mamicode.com/asString(db.get(bytes("Tampa")));
db.delete(bytes("Tampa"));
3.执行批量更新
WriteBatch batch = db.createWriteBatch();
try {
batch.delete(bytes("Denver"));
batch.put(bytes("Tampa"), bytes("green"));
batch.put(bytes("London"), bytes("red"));
db.write(batch);
} finally {
// Make sure you close the batch to avoid resource leaks.
batch.close();
}
4.遍历key/value
DBIterator iterator = db.iterator();
try {
for(iterator.seekToFirst(); iterator.hasNext(); iterator.next()) {
String key = asString(iterator.peekNext().getKey());
String value = http://www.mamicode.com/asString(iterator.peekNext().getValue());
System.out.println(key+" = "+value);
}
} finally {
// Make sure you close the iterator to avoid resource leaks.
iterator.close();
}
5.自定义key值大小比较
DBComparator comparator = new DBComparator(){
public int compare(byte[] key1, byte[] key2) {
return new String(key1).compareTo(new String(key2));
}
public String name() {
return "simple";
}
public byte[] findShortestSeparator(byte[] start, byte[] limit) {
return start;
}
public byte[] findShortSuccessor(byte[] key) {
return key;
}
};
Options options = new Options();
options.comparator(comparator);
DB db = factory.open(new File("example"), options);
移动端5大数据库比较