关注开源代码的实际应用
Berkeley DB是历史悠久的嵌入式数据库系统,早期主要应用在UNIX/LINUX操作系统上,现在也有大量的Windows应用程序使用Berkeley DB存储数据。Berkeley DB的存储的是key/value键值对,可以理解为硬盘上的超级hash表,可以管理256TB数据,而且能支撑几千个并发访问。BerkeleyDB相关资料可以从这里下载:http://www.oracle.com/technology/products/berkeley-db/index.html。
由于SQLite只支持单写多读,大多数情况下无法满足大访问量Web站点应用的并发性要求;而DB4O的Embed模式完全不支持并发;可以考虑使用Berkeley DB作为Asp.Net的Embed数据库。
目前Berkeley DB官方有C++版和Java版。要想在.NET中使用,可以使用Berkeley DB for .NET的Binding(Wrapper)。Berkeley DB for .NET 最新版本为0.95,支持BDB 4.3和4.5。下载到libdb-dotnet_0_95.zip后,解压,在解压缩后的bin目录找到libdb_dotNET45.dll,这就是BDB 4.5的.NET Binding,在.NET项目中引用此DLL就可以开始使用了。
一、初始化DbBTree实例
①、Berkeley DB通过DbBTree类进行数据库操作,因此需要先得到DbBTree的实例。
- /// <summary>
- /// 数据库目录
- /// </summary>
- private string directory;
- /// <summary>
- /// 数据库文件名
- /// </summary>
- private string dbName;
- private DbBTree btree;
- private Txn txn;
- private Db db;
- private Env env;
- /// <summary>
- /// 初始化
- /// </summary>
- private void Init()
- {
- env = new Env(EnvCreateFlags.None);
- Env.OpenFlags envFlags =
- Env.OpenFlags.Create |
- Env.OpenFlags.InitLock |
- Env.OpenFlags.InitLog |
- Env.OpenFlags.InitMPool |
- Env.OpenFlags.InitTxn |
- Env.OpenFlags.Recover;
- env.Open(directory, envFlags, 0);
- txn = env.TxnBegin(null, Txn.BeginFlags.None);
- db = env.CreateDatabase(DbCreateFlags.None);
- btree = (DbBTree)db.Open(txn, dbName, null, DbType.BTree, Db.OpenFlags.Create, 0);
- }
②、另外Berkeley DB数据库的操作需要借助于序列化。
- /// <summary>
- /// 二进制序列化
- /// </summary>
- private BinaryFormatter formatter;
- /// <summary>
- /// 键内存流
- /// </summary>
- private MemoryStream keyStream;
- /// <summary>
- /// 内容内存流
- /// </summary>
- private MemoryStream dataStream;
- private void StreamInit()
- {
- formatter = new BinaryFormatter();
- keyStream = new MemoryStream();
- dataStream = new MemoryStream();
- }
③、Berkeley DB是键值数据库,因此定义一个获取键接口:
- public interface IPut
- {
- string Key { get; }
- }
二、数据库的保存与更新
- public bool Set(IPut put)
- {
- Reset();
- keyStream.Position = 0;
- formatter.Serialize(keyStream, put.Key);
- DbEntry key = DbEntry.InOut(keyStream.GetBuffer(), 0, (int)keyStream.Position);
- dataStream.Position = 0;
- formatter.Serialize(dataStream, put);
- DbEntry data = DbEntry.InOut(dataStream.GetBuffer(), 0, (int)dataStream.Position);
- WriteStatus status = btree.Put(txn, ref key, ref data);
- switch (status)
- {
- case WriteStatus.Success:
- return true;
- case WriteStatus.NotFound:
- case WriteStatus.KeyExist:
- default:
- return false;
- }
- }
上述代码就可以保存键值,对键值进行序列化,然后再保存。保存完有三个状态,可以一一处理。
三、数据库的删除
- public bool Remove(IPut put)
- {
- keyStream.Position = 0;
- formatter.Serialize(keyStream, put.Key);
- DbEntry key = DbEntry.InOut(keyStream.GetBuffer(), 0, (int)keyStream.Position);
- DeleteStatus status = btree.Delete(txn, ref key);
- switch (status)
- {
- case DeleteStatus.NotFound:
- case DeleteStatus.Success:
- return true;
- case DeleteStatus.KeyEmpty:
- default:
- return false;
- }
- }
四、添加和删除并没有真正得进行添加和删除,须执行Commit操作:
- private bool iscomit = false;
- public void Commit()
- {
- txn.Commit(Txn.CommitMode.None);
- iscomit = true;
- }
五、用键查询值,和hash表一样使用:
- public bool Get(ref IPut put)
- {
- keyStream.Position = 0;
- formatter.Serialize(keyStream, put.Key);
- DbEntry key = DbEntry.InOut(keyStream.GetBuffer(), 0, (int)keyStream.Position);
- dataStream.SetLength(dataStream.Capacity);
- DbEntry data = DbEntry.Out(dataStream.GetBuffer());
- while (true)
- {
- ReadStatus status = btree.Get(txn, ref key, ref data, DbFile.ReadFlags.None);
- switch (status)
- {
- case ReadStatus.Success:
- dataStream.Position = 0;
- dataStream.SetLength(data.Size);
- put = (IPut)formatter.Deserialize(dataStream);
- return true;
- case ReadStatus.BufferSmall: //扩容
- if (key.Buffer.Length < key.Size)
- {
- keyStream.SetLength(key.Size);
- key = DbEntry.Out(keyStream.GetBuffer());
- }
- if (data.Buffer.Length < data.Size)
- {
- dataStream.SetLength(data.Size);
- data = DbEntry.Out(dataStream.GetBuffer());
- }
- continue;
- case ReadStatus.NotFound:
- case ReadStatus.KeyEmpty:
- default:
- return false;
- }
- }
- }
六、遍历
- public List<IPut> Find()
- {
- List<IPut> custList = new List<IPut>();
- using (DbBTreeCursor cursor = btree.OpenCursor(txn, DbFileCursor.CreateFlags.None))
- {
- IPut cust = null;
- while (GetNextRecord(cursor, ref cust))
- custList.Add(cust);
- }
- return custList;
- }
- private bool GetNextRecord(DbBTreeCursor cursor, ref IPut cust)
- {
- ReadStatus status;
- keyStream.SetLength(keyStream.Capacity);
- dataStream.SetLength(dataStream.Capacity);
- DbEntry key = DbEntry.Out(keyStream.GetBuffer());
- DbEntry data = DbEntry.Out(dataStream.GetBuffer());
- do
- {
- status = cursor.Get(ref key, ref data, DbFileCursor.GetMode.Next, DbFileCursor.ReadFlags.None);
- switch (status)
- {
- case ReadStatus.NotFound: return false;
- case ReadStatus.KeyEmpty: continue; // skip deleted records
- case ReadStatus.BufferSmall:
- if (key.Buffer.Length < key.Size)
- {
- keyStream.SetLength(key.Size);
- key = DbEntry.Out(keyStream.GetBuffer());
- }
- if (data.Buffer.Length < data.Size)
- {
- dataStream.SetLength(data.Size);
- data = DbEntry.Out(dataStream.GetBuffer());
- }
- continue;
- case ReadStatus.Success:
- dataStream.Position = 0;
- dataStream.SetLength(data.Size);
- cust = (IPut)formatter.Deserialize(dataStream);
- return true;
- default:
- return false;
- }
- } while (true);
- }
七、完整操作封装
public interface IPut
{
string Key { get; }
}
public class BDBManager : IDisposable
{
/// <summary>
/// 数据库目录
/// </summary>
private string directory;
/// <summary>
/// 数据库文件名
/// </summary>
private string dbName;
private DbBTree btree;
private Txn txn;
private Db db;
private Env env;
/// <summary>
/// 二进制序列化
/// </summary>
private BinaryFormatter formatter;
/// <summary>
/// 键内存流
/// </summary>
private MemoryStream keyStream;
/// <summary>
/// 内容内存流
/// </summary>
private MemoryStream dataStream;
public BDBManager(string directory, string dbName)
{
this.directory = directory;
this.dbName = dbName;
Init();
StreamInit();
}
public bool Set(IPut put)
{
Reset();
keyStream.Position = 0;
formatter.Serialize(keyStream, put.Key);
DbEntry key = DbEntry.InOut(keyStream.GetBuffer(), 0, (int)keyStream.Position);
dataStream.Position = 0;
formatter.Serialize(dataStream, put);
DbEntry data = DbEntry.InOut(dataStream.GetBuffer(), 0, (int)dataStream.Position);
WriteStatus status = btree.Put(txn, ref key, ref data);
switch (status)
{
case WriteStatus.Success:
return true;
case WriteStatus.NotFound:
case WriteStatus.KeyExist:
default:
return false;
}
}
private bool iscomit = false;
public void Commit()
{
txn.Commit(Txn.CommitMode.None);
iscomit = true;
}
public List<IPut> Find()
{
List<IPut> custList = new List<IPut>();
using (DbBTreeCursor cursor = btree.OpenCursor(txn, DbFileCursor.CreateFlags.None))
{
IPut cust = null;
while (GetNextRecord(cursor, ref cust))
custList.Add(cust);
}
return custList;
}
public bool Get(ref IPut put)
{
keyStream.Position = 0;
formatter.Serialize(keyStream, put.Key);
DbEntry key = DbEntry.InOut(keyStream.GetBuffer(), 0, (int)keyStream.Position);
dataStream.SetLength(dataStream.Capacity);
DbEntry data = DbEntry.Out(dataStream.GetBuffer());
while (true)
{
ReadStatus status = btree.Get(txn, ref key, ref data, DbFile.ReadFlags.None);
switch (status)
{
case ReadStatus.Success:
dataStream.Position = 0;
dataStream.SetLength(data.Size);
put = (IPut)formatter.Deserialize(dataStream);
return true;
case ReadStatus.BufferSmall: //扩容
if (key.Buffer.Length < key.Size)
{
keyStream.SetLength(key.Size);
key = DbEntry.Out(keyStream.GetBuffer());
}
if (data.Buffer.Length < data.Size)
{
dataStream.SetLength(data.Size);
data = DbEntry.Out(dataStream.GetBuffer());
}
continue;
case ReadStatus.NotFound:
case ReadStatus.KeyEmpty:
default:
return false;
}
}
}
public bool Remove(IPut put)
{
Reset();
keyStream.Position = 0;
formatter.Serialize(keyStream, put.Key);
DbEntry key = DbEntry.InOut(keyStream.GetBuffer(), 0, (int)keyStream.Position);
DeleteStatus status = btree.Delete(txn, ref key);
switch (status)
{
case DeleteStatus.NotFound:
case DeleteStatus.Success:
return true;
case DeleteStatus.KeyEmpty:
default:
return false;
}
}
public void Dispose()
{
if (!iscomit)
Commit();
db.Close();
db.Close();
}
private void Reset()
{
iscomit = false;
}
private void Init()
{
env = new Env(EnvCreateFlags.None);
Env.OpenFlags envFlags =
Env.OpenFlags.Create |
Env.OpenFlags.InitLock |
Env.OpenFlags.InitLog |
Env.OpenFlags.InitMPool |
Env.OpenFlags.InitTxn |
Env.OpenFlags.Recover;
env.Open(directory, envFlags, 0);
txn = env.TxnBegin(null, Txn.BeginFlags.None);
db = env.CreateDatabase(DbCreateFlags.None);
btree = (DbBTree)db.Open(txn, dbName, null, DbType.BTree, Db.OpenFlags.Create, 0);
}
private void StreamInit()
{
formatter = new BinaryFormatter();
keyStream = new MemoryStream();
dataStream = new MemoryStream();
}
private bool GetNextRecord(DbBTreeCursor cursor, ref IPut cust)
{
ReadStatus status;
keyStream.SetLength(keyStream.Capacity);
dataStream.SetLength(dataStream.Capacity);
DbEntry key = DbEntry.Out(keyStream.GetBuffer());
DbEntry data = DbEntry.Out(dataStream.GetBuffer());
do
{
status = cursor.Get(ref key, ref data, DbFileCursor.GetMode.Next, DbFileCursor.ReadFlags.None);
switch (status)
{
case ReadStatus.NotFound: return false;
case ReadStatus.KeyEmpty: continue; // skip deleted records
case ReadStatus.BufferSmall:
if (key.Buffer.Length < key.Size)
{
keyStream.SetLength(key.Size);
key = DbEntry.Out(keyStream.GetBuffer());
}
if (data.Buffer.Length < data.Size)
{
dataStream.SetLength(data.Size);
data = DbEntry.Out(dataStream.GetBuffer());
}
continue;
case ReadStatus.Success:
dataStream.Position = 0;
dataStream.SetLength(data.Size);
cust = (IPut)formatter.Deserialize(dataStream);
return true;
default:
return false;
}
} while (true);
}
}
调用方法:
[Serializable()]
class Item : IPut
{
public string Name { get; set; }
public string Text { get; set; }
public int ID { get; set; }
public override string ToString()
{
return string.Format("ID:{0} Key:{1}", ID, Name);
}
public string Key
{
get { return Name; }
}
}
using (BDBManager manager = new BDBManager("db", "db.dat"))
{
bool success = manager.Set(new Item() { ID = 1000, Name = "Test",Text = "213" });
Console.WriteLine(string.Format("set is {0}", success));
}
using (BDBManager manager = new BDBManager("db", "db.dat"))
{
IPut put = new Item() { Name = "Test" };
bool success = manager.Get(ref put);
Console.WriteLine(string.Format("read is {0},item : {1}", success, put.ToString()));
}
using (BDBManager manager = new BDBManager("db", "db.dat"))
{
IPut put = new Item() { Name = "Test" };
bool success = manager.Remove(put);
Console.WriteLine(string.Format("remove is {0},item : {1}", success, put.ToString()));
}
using (BDBManager manager = new BDBManager("db", "db.dat"))
{
List<IPut> list = manager.Find();
foreach (var item in list)
{
Console.WriteLine(item.ToString());
}
}
Console.WriteLine("end");
Console.ReadKey();