在企业应用中,经常遇到对文本数据的读入和写入问题。如一个移动话单文件,或者使用SQLServer导出的文本数据,可能会包含这样的内容:
… 13411112222,XXXX,20060225121800,1000 13512345678,YYYY,20060224101011,800 … |
在解析这些文件时,要将每行数据拆分成数据单元,然后保存在预先定义好的数据对象中,一个数据文件,经过解析,就会形成一个数据对象列表。
对于这类数据文件,区别只在于数据的列数、每列数据的含义,以及数据的分隔符,文件读入或写入都是和文件格式无关的。基于这样的特性,可以考虑实现一个通用的数据文件处理模块,适用于所有这种模式存储的数据文件。
要实现上述功能,我们需要把那些不确定的因素(数据的列数、每列数据的含义,以及数据的分隔符)尽量推迟到用户实用阶段,也就是把单独一行的数据交给用户定义的对象来解析,同时也允许用户将生成好的一行数据交给我们的模块来存储。另外为了使序列化后的数据访问更快,这个记录表在内存中应该使用哈希表来存储,支持主键索引。
先定义两个接口类,限定用户定义的对象的特性:支持一行数据的解析和生成、支持主键索引。
/// <summary> /// 接口,表示可以从对象中得到主键 /// guanzhong 2005 /// http://blog.youkuaiyun.com/guanzhongs /// </summary> public interface IHashable { /// <summary> /// 获得对象中的主键字段 /// </summary> /// <returns></returns> Object GetKey(); }
/// <summary> /// 接口,表示可以将对象以文本方式导入导出到数据文件中 /// guanzhong 2005 /// http://blog.youkuaiyun.com/guanzhongs /// </summary> public interface ITextSerialisable : IHashable { /// <summary> /// 可以将输入字符串解释为对象的属性 /// </summary> /// <param name="str"></param> /// <returns>返回载入的字段数</returns> Int32 SerialIn(string str);
/// <summary> /// 与 SerialIn 接口逆向,将对象属性生成为字符串 /// </summary> /// <returns></returns> String SerialOut(); }
|
当用户希望他的数据对象支持文本记录的序列化时,只需要从ITextSerialisable派生即可。
下面代码实现了具体的文件数据读取:
/// <summary> /// 散列表数据的文件存储类 /// 存储时只能接受从 ITextSerialisable 派生的对象 /// 在 new 一个新对象时,需要指定所存储数据类型 /// guanzhong 2005 /// http://blog.youkuaiyun.com/guanzhongs /// 效率测试 /// 插入记录 1000毫秒/10万条 /// 写入文件 250毫秒/10万条 /// 载入文件 1800毫秒/10万条 /// </summary> public class FileData { private HashedList listData = null; System.Type typeData;
/// <summary> /// 构造函数 /// </summary> /// <param name="type">指定所存储的数据类型,改类型必须从 ITextSerialisable 派生</param> public FileData(System.Type type) { listData = new HashedList(); typeData = type; }
/// <summary> /// 添加记录 /// </summary> /// <param name="itsa"></param> /// <returns></returns> public bool Add(ITextSerialisable itsa) { if(itsa.GetType() != typeData) { Exception ex = new Exception(string.Format("Invalid type of input parameter, only type /"{0}/" is accceptable",typeData.ToString())); throw(ex); }
return listData.Add(itsa); }
/// <summary> /// 根据主键获取对象 /// </summary> /// <param name="key"></param> /// <returns></returns> public Object Get(Object key) { return listData.Get(key); }
/// <summary> /// 删除指定对象 /// </summary> /// <param name="itsa"></param> /// <returns></returns> public bool Delete(ITextSerialisable itsa) { if(itsa.GetType() != typeData) { Exception ex = new Exception(string.Format("Invalid type of input parameter, only type /"{0}/" is accceptable",typeData.ToString())); throw(ex); }
return listData.Delete(itsa); }
/// <summary> /// 根据主键删除记录 /// </summary> /// <param name="key"></param> /// <returns></returns> public bool Delete(Object key) { return listData.Delete(key); }
/// <summary> /// 清除保存的所有对象 /// </summary> public void Clear() { listData.Clear(); }
/// <summary> /// 从文件加载记录 /// </summary> /// <param name="strFileName"></param> /// <returns>读入记录的行数</returns> public int Load(string strFileName) { if(strFileName == null || strFileName == "") { return -1; }
FileStream stream = File.OpenRead(strFileName);
if(stream == null) { return -1; }
byte[] buf = new byte[stream.Length];
int ptr = 0; int nlen = buf.Length;
// 读取文件数据 while(nlen > 0) { int nReaded = stream.Read(buf,ptr,nlen); if(nReaded == 0) { break; }
ptr += nReaded; nlen -= nReaded; }
stream.Close();
string strContent = Encoding.Default.GetString(buf); string[] lines = strContent.Split('/n');
// 处理每一行 int nRead = 0; foreach(string strLine in lines) { string strTmpLine = strLine.TrimEnd('/r'); // 如果文件是以"/r/n"换行,则使用'/n'分割后每行末尾会遗留一个'/r'符,所以将其去掉
// 读取一行的记录 ITextSerialisable itsa = (ITextSerialisable)System.Activator.CreateInstance(typeData); if(itsa.SerialIn(strTmpLine) > 0) { if(listData.Add(itsa)) { nRead++; } }
}
return nRead; }
/// <summary> /// 将记录保存到文件 /// </summary> /// <param name="strFileName"></param> /// <returns>写入的记录数</returns> public int Store(string strFileName) { if(strFileName == null || strFileName == "") { return -1; }
StreamWriter writer = File.CreateText(strFileName);
if(writer == null) { return -1; }
int nWrite = 0;
for(int i=0;i<listData.Count;i++) { ITextSerialisable itsa = (ITextSerialisable)listData.GetAt(i); writer.WriteLine(itsa.SerialOut());
nWrite++; }
writer.Close();
return nWrite; }
public HashedList Datas { get { return listData; } }
public int Count { get { return listData.Count; } }
/// <summary> /// 散列记录集 /// guanzhong 2005 /// http://blog.youkuaiyun.com/guanzhongs /// </summary> public class HashedList : ICollection { private SortedList list = null; private ArrayList array = null; // 用于有序的存放列表元素,使得可以在GetEnumerator可以返回IEnumerator对象
public HashedList() { list = new SortedList(); array = new ArrayList(); }
/// <summary> /// 以对象指定的主键将对象插入到列表中 /// </summary> /// <param name="iek"></param> /// <returns></returns> public bool Add(IHashable iha) { if(iha == null) { return false; }
if(iha.GetKey() == null) { Exception ex = new Exception(string.Format("Null hash key of object /"{0}/"",typeof(IHashable).ToString())); throw(ex); }
list.Remove(iha.GetKey()); // 删除旧的记录(如果有的话) list.Add(iha.GetKey(),iha); // 加入新的记录 return true; }
/// <summary> /// 根据主键获得对象 /// </summary> /// <param name="key"></param> /// <returns></returns> public Object Get(Object key) { if(key == null) { return null; }
return list[key]; }
/// <summary> /// 根据索引获得对象 /// </summary> /// <param name="index"></param> /// <returns></returns> public Object GetAt(int index) { return list.GetByIndex(index); }
/// <summary> /// 删除指定对象,必须保证对象中的主键有效 /// </summary> /// <param name="iek"></param> /// <returns></returns> public bool Delete(IHashable iha) { if(iha == null) { return false; }
if(iha.GetKey() == null) { Exception ex = new Exception(string.Format("Null hash key of object /"{0}/"",typeof(IHashable).ToString())); throw(ex); }
list.Remove(iha.GetKey());
return true; }
/// <summary> /// 根据主键删除记录 /// </summary> /// <param name="key"></param> /// <returns></returns> public bool Delete(Object key) { list.Remove(key); return true; }
/// <summary> /// 清除列表 /// </summary> public void Clear() { list.Clear(); }
/// <summary> /// 将对象复制到一维列表中,以便可以只用foreach进行遍历 /// </summary> /// <param name="array"></param> private void CopyToArrayList(ref ArrayList array) { array.Clear(); for(int i=0;i<Count;i++) { Object obj = list.GetByIndex(i); array.Add(obj); } }
#region ICollection Members
public bool IsSynchronized { get { // TODO: Add PaydataCollection.IsSynchronized getter implementation return false; } }
public int Count { get { // TODO: Add PaydataCollection.Count getter implementation return list.Count; } }
public void CopyTo(Array array, int index) { // TODO: Add PaydataCollection.CopyTo implementation list.CopyTo(array,index); }
public object SyncRoot { get { // TODO: Add PaydataCollection.SyncRoot getter implementation return null; } }
#endregion
#region IEnumerable Members
public IEnumerator GetEnumerator() { // TODO: Add PaydataCollection.GetEnumerator implementation CopyToArrayList(ref array); //return list.GetEnumerator(); return array.GetEnumerator(); }
#endregion
} // HashedList }
|
在Load方法中,这几行
// 读取一行的记录 ITextSerialisable itsa = (ITextSerialisable)System.Activator.CreateInstance(typeData); if(itsa.SerialIn(strTmpLine) > 0) { if(listData.Add(itsa)) { nRead++; } }
|
是关键,它根据用户在初始化FileData对象时,指定的存储类型,将字符串数据还原成相应的类型对象(动态创建),保存在对象列表中。