Berkeley DB是历史悠久的嵌入式数据库系统,主要应用在UNIX/LINUX操作系统上。Berkeley DB的存储的是key/value键值对,可以理解为硬盘上的超级hash表。其可以管理256TB数据,而且能支撑几千个并发访问。目前Berkeley DB有C++版和Java版。所以,我们需要一个访问的中间转换,已经有人发布了C#的API。可以从 Berkeley DB for .NET 上面找到,现在最新版是0.95版本,可以支持4.3和4.5版。本篇将以4.5版做实例。BerkeleyDB的版本可以在http://www.oracle.com/technology/products/berkeley-db/index.html下载,当前最新版本为4.7版。4.5 C++版的Berkeley DB可以在http://www.oracle.com/technology/software/products/berkeley-db/db/index.html这里下载。
By Birdshover@ 博客园 http://www.cnblogs.com/birdshover/
下载到Berkeley DB for .Net的API——libdb-dotnet_0_95.zip后,就可以开始使用了。首先在libdb-dotnet_0_95.zip解压缩的bin目录找到libdb_dotNET45.dll,这个就是4.5版本使用的dll。新建项目,引用这个dll。注意,自己编译源码可能会编译不过,主要是因为里面一些委托和委托的参数可见性不一致造成的。把那些参数用到的class 或者struct都调成public即可。
BerkeleyDB的数据库操作需要借助DbBTree类。因此需要先得到DbBTree的实例,但是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);
}
}
调用方法:
首先要有一个写入的实体类,必须可以序列化,并且实现IPut接口:
[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();