C#之读取DBF文件到DataSet中

公用工具类

复制代码
    public class DBFPhaser
    {
        /*  字段信息结构   */
        public struct Field
        {
            public byte[] name;                 /*  字段名变量           */
            public byte type;                   /*  字段类型变量         */
            public int addr;                    /*  字段数据的相对地址    */
            public byte width, deC;             /*  字段长度及小数位变量  */
        }

        public class DBFDataRow
        {
            private byte[] _data;
            private Dictionary<string, Field> _dic;
            public DBFDataRow(byte[] RowData, Dictionary<string, Field> dic_fields)
            {
                _data = RowData;
                _dic = dic_fields;
            }

            /// <summary>
            /// 获取字段值
            /// </summary>
            /// <param name="columnName">字段名称(不区分大小写)</param>
            /// <returns>字段值</returns>
            public string this[string columnName]
            {
                get
                {
                    Field fd;
                    if (_dic.TryGetValue(columnName.ToUpper(), out fd))
                    {
                        return Encoding.GetEncoding(936).GetString(_data, fd.addr, fd.width).Trim("\0".ToCharArray()).Trim();
                    }
                    else
                        return "";
                }
            }

            /// <summary>
            /// 当前行是否打了删除标记
            /// </summary>
            public bool HasDeleted
            {
                get { return _data[0] == 42; }
            }
        }

        //文件头长度
        public int HeadLen;
        //文件
        private FileStream file;
        //文件偏移位,用于去除头字节
        private int offset;

        //字段列表               
        public Dictionary<string, Field> fields = new Dictionary<string, Field>();
        //行记录数
        public int RowsCount;
        //字段数
        public int FieldCount;
        //行长度
        public int RowLen;

        //构造函数
        public DBFPhaser(string filefullname, int offset = 0)
        {
            this.offset = offset;
            //只读模式打开文件
            try
            {
                file = new FileStream(filefullname, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            }
            catch (Exception ex)
            {
                throw new Exception("打开DBF文件[" + filefullname + "出错:" + ex.Message);
            }

            opendbf();
        }

        public DBFPhaser(FileStream fs, int offset = 0)
        {
            this.offset = offset;
            file = fs;
            opendbf();
        }

        //打开文件
        public void opendbf()
        {
            Field filed;
            string s;
            byte[] buf = new byte[18];
            int n;

            //文件指针复位
            file.Seek(offset, SeekOrigin.Begin);
            n = file.Read(buf, 0, 4); //文件标志,年,月,日
            if (n != 4)
                throw new Exception("DBF文件头格式不合法");

            //行记录数
            n = file.Read(buf, 0, 4);
            if (n != 4)
                throw new Exception("DBF文件头格式不合法,没有行记录数");
            RowsCount = buf[0] + buf[1] * 256 + buf[2] * 65536 + buf[3] * 16777216;

            n = file.Read(buf, 0, 4);
            if (n != 4)
                throw new Exception("DBF文件头格式不合法,没有表头信息");
            //表头长度
            HeadLen = buf[0] + buf[1] * 256;
            //行长度
            RowLen = buf[2] + buf[3] * 256;
            //字段个数,文件头长度=32+32*字段数目+1
            //现场发现上海的prop文件导出来不会在文件头的最后加上结束标志0D,所以此处做个4舍5入处理
            FieldCount = (int)Math.Round((HeadLen - 1 - 32) / 32.0);

            //获取字段信息
            int y = 1;
            for (int i = 0; i < FieldCount; i++)
            {
                file.Seek(32 + 32 * i + offset, SeekOrigin.Begin);//文件指针指向记录处
                file.Read(buf, 0, 18);

                filed = new Field();
                filed.name = new byte[12];//分配12个空间,存储字段名
                s = string.Empty;
                Array.Copy(buf, filed.name, 11);
                int index = Array.IndexOf(filed.name, (byte)0x0); //把'\0'后面的字符都至为'\0'
                if (index != -1)
                    for (int j = index; j < 12; j++)
                        filed.name[j] = 0;
                filed.type = buf[11];
                filed.addr = y;
                filed.width = buf[16];
                y += filed.width;
                filed.deC = buf[17];
                if (Encoding.GetEncoding(936).GetString(filed.name).Trim("\0".ToCharArray()) == "") break;
                fields.Add(Encoding.GetEncoding(936).GetString(filed.name).Trim("\0".ToCharArray()).ToUpper(), filed);
            }
        }

        //关闭文件
        public void close()
        {
            file.Close();
        }

        /// <summary>
        /// 读取DBF的头定义字节内容
        /// </summary>
        /// <returns></returns>
        public byte[] GetHeads()
        {
            byte[] data = new byte[HeadLen];
            file.Seek(offset, SeekOrigin.Begin);
            file.Read(data, 0, HeadLen);
            return data;
        }

        /// <summary>
        /// 获取要读取的行对象列表
        /// </summary>
        /// <param name="BeginRow">开始行,行号从0开始</param>
        /// <returns></returns>
        public List<DBFDataRow> Rows(int BeginRow = 0)
        {
            List<DBFDataRow> rs = new List<DBFDataRow>();
            file.Seek(HeadLen + offset + RowLen * BeginRow, SeekOrigin.Begin);
            int p = BeginRow, r;
            while (p < RowsCount)
            {
                byte[] bytes = new byte[RowLen];
                r = file.Read(bytes, 0, RowLen);
                if (r < RowLen)
                    p = RowsCount;
                else
                {
                    rs.Add(new DBFDataRow(bytes, fields));
                    p++;
                }
            }
            return rs;
        }

        /// <summary>
        /// 读取指定行指定字段值
        /// </summary>
        /// <param name="row">行号</param>
        /// <param name="column">列字段名</param>
        /// <returns></returns>
        public string FiledsAsString(int row, string column)
        {
            byte[] data = new byte[fields[column].width];

            //文件指针移向字段的数据地址处
            file.Seek(HeadLen + RowLen * row + fields[column.ToUpper()].addr + offset, SeekOrigin.Begin);
            file.Read(data, 0, data.Length);
            return Encoding.GetEncoding(936).GetString(data).Trim("\0".ToCharArray()).Trim();
        }

        //一次性获取DBF内容
        public byte[] GetAllRows()
        {
            byte[] bytes = new byte[RowsCount * RowLen];
            file.Seek(HeadLen + offset, SeekOrigin.Begin);
            file.Read(bytes, 0, bytes.Length);
            return bytes;
        }

        /// <summary>
        /// 获取dbf行数据
        /// </summary>
        /// <param name="bStart"></param>
        /// <param name="rows"></param>
        /// <returns></returns>
        public byte[] GetRows(bool bStart, int rows)
        {
            byte[] bytes = new byte[rows * RowLen];
            if (bStart)
                file.Seek(HeadLen + offset, SeekOrigin.Begin);
            file.Read(bytes, 0, bytes.Length);
            return bytes;
        }

        /// <summary>
        /// 一行一行获取dbf行数据
        /// </summary>
        /// <param name="bStart"></param>
        /// <param name="rows"></param>
        /// <returns></returns>
        public IEnumerable<byte[]> GetEnumerableRows()
        {
            file.Seek(HeadLen + offset, SeekOrigin.Begin);
            for (int i = 0; i < RowsCount; i++)
            {
                byte[] bytes = new byte[RowLen];
                file.Read(bytes, 0, bytes.Length);
                yield return bytes;
            }
        }

        /// <summary>
        /// 指定行是否已经打了删除标记
        /// </summary>
        /// <param name="row">行号</param>
        /// <returns>true:已经打了删除标记,  false:未打删除标记</returns>
        public bool RowHasDeleted(int row)
        {
            file.Seek(offset + HeadLen + RowLen * row, SeekOrigin.Begin);
            if (file.ReadByte() == 42)
                return true;
            else
                return false;
        }

        /// <summary>
        /// 导出DataTable
        /// </summary>
        /// <returns></returns>
        public DataTable DBFToDataTable()
        {
            DataTable dt = new DataTable();
            foreach (var kvp in fields)
            {
                if ((char)kvp.Value.type == 'D')
                    dt.Columns.Add(new DataColumn(kvp.Key, typeof(DateTime)));
                else if ((char)kvp.Value.type == 'N')
                    dt.Columns.Add(new DataColumn(kvp.Key, typeof(double)));
                else
                    dt.Columns.Add(new DataColumn(kvp.Key, typeof(string)));
            }

            file.Seek(HeadLen + offset, SeekOrigin.Begin);
            for (int i = 0; i < RowsCount; i++)
            {
                try
                {
                    var dr = dt.NewRow();
                    //读取行开头字节
                    var tmp = new byte[1];
                    file.Read(tmp, 0, 1);
                    foreach (DataColumn col in dt.Columns)
                    {
                        byte[] data = new byte[fields[col.ColumnName].width];
                        file.Read(data, 0, data.Length);
                        if (col.DataType == typeof(DateTime))
                            dr[col.ColumnName] = StringToDate(Encoding.GetEncoding(936).GetString(data).Trim("\0".ToCharArray()).Trim());
                        else
                            dr[col.ColumnName] = Encoding.GetEncoding(936).GetString(data).Trim("\0".ToCharArray()).Trim();
                    }
                    dt.Rows.Add(dr);
                }
                catch
                {
                    
                }
            }
            return dt;
        }

        public static DateTime StringToDate(string s)
        {
            return DateTime.ParseExact(s, "yyyyMMdd", System.Globalization.CultureInfo.CurrentCulture);
        }
    }

    public enum NativeDbType : byte
    {
        //Autoincrement = (byte)0x2B, //+ in ASCII
        //Timestamp = (byte)0x40, //@ in ASCII
        //Binary = (byte)0x42, //B in ASCII
        //Long = (byte)0x49, //I in ASCII
        //Logical = (byte)0x4C, //L in ASCII
        //Memo = (byte)0x4D, //M in ASCII
        //Ole = (byte)0x47, //G in ASCII
        //Float = (byte)0x46, //F in ASCII  
        //Double = (byte)0x4F, //O in ASCII 
        Char = (byte)0x43, //C in ASCII    
        Date = (byte)0x44, //D in ASCII    8字节
        Numeric = (byte)0x4E, //N in ASCII 
    }
    public class DBFField
    {
        public string Name { get; private set; }
        public int FieldLength { get; private set; }
        public NativeDbType dataType { get; private set; }
        public int DecimalCount { get; private set; }
        public string TabFieldName { get; private set; }
        /// <summary>
        /// 构建字段
        /// </summary>
        /// <param name="aFieldName"></param>
        /// <param name="aType"></param>
        /// <param name="aFieldLength"></param>
        /// <param name="aDecimalCount"></param>
        public DBFField(string aFieldName,
                        NativeDbType aType,
                        Int32 aFieldLength = 0,
                        Int32 aDecimalCount = 0)
        {
            Name = aFieldName;
            TabFieldName = aFieldName;
            dataType = aType;
            FieldLength = aFieldLength;
            DecimalCount = aDecimalCount;
            if (dataType == NativeDbType.Char && FieldLength <= 0)
                throw new Exception("字符串字段长度必须大于0");
            if (dataType == NativeDbType.Numeric && FieldLength <= 0)
                throw new Exception("数值字段长度必须大于0");
            if (dataType == NativeDbType.Numeric && DecimalCount < 0)
                throw new Exception("数值字段精度必须大于或等于0");

            if (dataType == NativeDbType.Date)
                FieldLength = 8;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="aFieldName">dbf字段名</param>
        /// <param name="aType">字段类型</param>
        /// <param name="aSourFieldName">datatable中的字段名</param>
        /// <param name="aFieldLength">字段长度</param>
        /// <param name="aDecimalCount">小数位数</param>
        public DBFField(string aFieldName,
                        NativeDbType aType,
                        string aSourFieldName,
                        Int32 aFieldLength = 0,
                        Int32 aDecimalCount = 0)
        {
            Name = aFieldName;
            TabFieldName = aSourFieldName;
            dataType = aType;
            FieldLength = aFieldLength;
            DecimalCount = aDecimalCount;
            if (dataType == NativeDbType.Char && FieldLength <= 0)
                throw new Exception("字符串字段长度必须大于0");
            if (dataType == NativeDbType.Numeric && FieldLength <= 0)
                throw new Exception("数值字段长度必须大于0");
            if (dataType == NativeDbType.Numeric && DecimalCount < 0)
                throw new Exception("数值字段精度必须大于或等于0");

            if (dataType == NativeDbType.Date)
                FieldLength = 8;
        }
    }

    public class DBFExport
    {
        /// <summary>
        /// 导出dbf文件
        /// </summary>
        /// <param name="path"></param>
        /// <param name="dtSource"></param>
        public static void DataTableToDBF(string path, DataTable dtSource, DBFField[] Fields)
        {
            if (dtSource == null)
                throw new Exception("DBF导出数据源不可为空");
            if (Fields == null)
                throw new Exception("DBF导出字段不可为空");
            using (var fm = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
            {
                using (var bw = new BinaryWriter(fm))
                {
                    //文件头
                    bw.Write((byte)3);
                    bw.Write((byte)(DateTime.Now.Year % 100));
                    bw.Write((byte)DateTime.Now.Month);
                    bw.Write((byte)DateTime.Now.Day);
                    bw.Write(dtSource.Rows.Count);
                    bw.Write((Int16)(Fields.Length * 32 + 33));
                    //计算一条总字节数记录
                    int recordBytiesCnt = 0;
                    foreach (var field in Fields)
                        recordBytiesCnt += field.FieldLength;
                    bw.Write((Int16)(recordBytiesCnt + 1));
                    bw.Write(new byte[20]);
                    //字段属性
                    int fieldBeginPos = 1;
                    foreach (var field in Fields)
                    {
                        var fieldNameBytes = Encoding.GetEncoding(936).GetBytes(field.Name);
                        if (fieldNameBytes.Length > 11)
                        {
                            var dest = new byte[11];
                            Array.Copy(fieldNameBytes, dest, 11);
                            fieldNameBytes = dest;
                        }
                        if (fieldNameBytes.Length < 11)
                        {
                            var dest = new byte[11];
                            Array.Copy(fieldNameBytes, dest, fieldNameBytes.Length);
                            fieldNameBytes = dest;
                        }
                        bw.Write(fieldNameBytes);
                        bw.Write((byte)field.dataType);
                        bw.Write((byte)fieldBeginPos);
                        bw.Write(new byte[3]);
                        bw.Write((byte)field.FieldLength);
                        bw.Write((byte)field.DecimalCount);
                        bw.Write(new byte[14]);

                        fieldBeginPos += field.FieldLength;
                    }
                    bw.Write((byte)0x0D);

                    //数据体
                    foreach (DataRow dr in dtSource.Rows)
                    {
                        bw.Write((byte)0x20);
                        foreach (var field in Fields)
                            bw.Write(GetBytesFromField(dr[field.TabFieldName], field));
                    }
                }
            }
        }

        private static byte[] GetBytesFromField(object value, DBFField field)
        {
            byte[] res = { 0x20 };
            if (value != null && value != DBNull.Value && !string.IsNullOrEmpty(value.ToString()))
            {
                if (field.dataType == NativeDbType.Date)
                    res = Encoding.GetEncoding(936).GetBytes(Convert.ToDateTime(value).ToString("yyyyMMdd"));
                else if (field.dataType == NativeDbType.Numeric)
                {
                    string svalue = Convert.ToDecimal(value).ToString();
                    int ipos = svalue.IndexOf('.');
                    if (ipos > 0 && field.DecimalCount == 0)
                        svalue = svalue.Substring(0, ipos);
                    else if (field.DecimalCount > 0)
                    {
                        if (ipos < 0)
                            svalue += ".".PadRight(field.DecimalCount + 1, '0');
                        else if (svalue.Length - ipos - 1 < field.DecimalCount)  //实际小数位数小于要求位数
                            svalue += "0".PadRight(field.DecimalCount - (svalue.Length - ipos - 1));
                        else if (svalue.Length - ipos - 1 > field.DecimalCount) //实际小数位数大于要求位数
                            svalue = svalue.Remove(ipos + field.DecimalCount + 1);
                    }
                    res = Encoding.GetEncoding(936).GetBytes(svalue);
                }
                else
                    res = Encoding.GetEncoding(936).GetBytes(value.ToString());
            }

            var dest = new byte[field.FieldLength];
            if (res.Length == field.FieldLength)
                return res;
            else if (res.Length > field.FieldLength)
                Array.Copy(res, dest, field.FieldLength);
            else
            {
                if (field.dataType == NativeDbType.Numeric)
                {
                    //前填空格
                    for (int i = 0; i < field.FieldLength - res.Length; i++)
                        dest[i] = 0x20;
                    Array.Copy(res, 0, dest, field.FieldLength - res.Length, res.Length);
                }
                else
                {
                    Array.Copy(res, dest, res.Length);
                    //后填空格
                    for (int i = res.Length; i < field.FieldLength; i++)
                        dest[i] = 0x20;
                }
            }
            return dest;
        }
    }
复制代码

使用

复制代码
        /// <summary>
        /// 读DBF文件到DataTable
        /// </summary>
        /// <param name="dbname">DBF实际绝对路径带文件名</param>
        /// <returns></returns>
        public static DataTable DbfToDataTable(string dbname)
        {
            try
            {
                DBFPhaser DbfHelp = new DBFPhaser(dbname);
                DataTable DbfDt = DbfHelp.DBFToDataTable();
                return DbfDt;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
复制代码

 

posted @   无产铁锤  阅读(140)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示