因为工作关系。须要工作其中,须要读取DBF文件。找了一些DBF读取开源软件,要么是太过庞大,动不动就上万行。要么是功能有问题,编码,长度,总之是没有找到一个很爽的。

在万般无奈之下,我老人家怒从心头起,恶向胆边生。决定自己写一下。

结果仅仅用了不到300行代码就搞定了,当然搞定不是唯一目标,还要优雅简洁的搞定,亲们尾随我的脚步一起感受一下简洁的设计与实现吧。

在開始编码之前。先介绍一下DBF。这个DBF可是个老东西。在DOS时代就已经出现,而且风骚了相当一段时间。后来随着大型数据库的应用,它逐步没落。可是因为其简洁易用的特点。还是应用在大量的数据交换其中。

可是其发展过程中,也形成了很多种版本号,不同版本号的结构不一样,也就决定 了其解析程序也是不一样的。
今天我仅仅实现了Foxbase/DBaseIII的解析。可是也为扩展各种其他版本号做好了准备。

接口设计

083756_dzpL_1245989.jpg (37.32 KB, 下载次数: 0)

下载附件

2015-5-27 22:33 上传

上面一共就两个类。一个接口。Field和Header就是两个简单的POJO类,分别定义了文件头及字段相关的信息。

Reader接口是DBF文件读取的接口,主要定义了获取文件类型,编码,字段以及记录移动相关的方法。
代码实现 首先实现Reader的抽象类

public abstract class DbfReader implements Reader {
protected String encode = "GBK";
private FileChannel fileChannel;
protected Header header;
protected List<Field> fields;
private boolean recordRemoved;
int position = 0;
static Map<Integer, Class> readerMap = new HashMap<Integer, Class>(); static {
addReader(3, FoxproDBase3Reader.class);
} public static void addReader(int type, Class clazz) {
readerMap.put(type, clazz);
} public static void addReader(int type, String className) throws ClassNotFoundException {
readerMap.put(type, Class.forName(className));
} public byte getType() {
return 3;
} public String getEncode() {
return encode;
} public Header getHeader() {
return header;
} public List<Field> getFields() {
return fields;
} public boolean isRecordRemoved() {
return recordRemoved;
} public static Reader parse(String dbfFile, String encode) throws IOException, IllegalAccessException, InstantiationException {
return parse(new File(dbfFile), encode);
} public static Reader parse(String dbfFile) throws IOException, IllegalAccessException, InstantiationException {
return parse(new File(dbfFile), "GBK");
} public static Reader parse(File dbfFile) throws IOException, IllegalAccessException, InstantiationException {
return parse(dbfFile, "GBK");
} public static Reader parse(File dbfFile, String encode) throws IOException, IllegalAccessException, InstantiationException {
RandomAccessFile aFile = new RandomAccessFile(dbfFile, "r");
FileChannel fileChannel = aFile.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1);
fileChannel.read(byteBuffer);
byte type = byteBuffer.array()[0];
Class<Reader> readerClass = readerMap.get((int) type);
if (readerClass == null) {
fileChannel.close();
throw new IOException("不支持的文件类型[" + type + "]。");
}
DbfReader reader = (DbfReader) readerClass.newInstance();
reader.setFileChannel(fileChannel);
reader.readHeader();
reader.readFields();
return reader;
} public void setFileChannel(FileChannel fileChannel) {
this.fileChannel = fileChannel;
} protected abstract void readFields() throws IOException; public void moveBeforeFirst() throws IOException {
position = 0;
fileChannel.position(header.getHeaderLength());
} /**
* @param position 从1開始
* @throws java.io.IOException
*/
public void absolute(int position) throws IOException {
checkPosition(position);
this.position = position;
fileChannel.position(header.getHeaderLength() + (position - 1) * header.getRecordLength());
} private void checkPosition(int position) throws IOException {
if (position >= header.getRecordCount()) {
throw new IOException("期望记录行数为" + (this.position + 1) + ",超过实际记录行数:" + header.getRecordCount() + "。");
}
} protected abstract Field readField() throws IOException; protected abstract void readHeader() throws IOException; private void skipHeaderTerminator() throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1);
readByteBuffer(byteBuffer);
} public void close() throws IOException {
fileChannel.close();
} public void next() throws IOException {
checkPosition(position);
ByteBuffer byteBuffer = ByteBuffer.allocate(1);
readByteBuffer(byteBuffer);
this.recordRemoved = (byteBuffer.array()[0] == '*');
for (Field field : fields) {
read(field);
}
position++;
} public boolean hasNext() {
return position < header.getRecordCount();
} private void read(Field field) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(field.getLength());
readByteBuffer(buffer);
field.setStringValue(new String(buffer.array(), encode).trim());
field.setBuffer(buffer);
} protected void readByteBuffer(ByteBuffer byteBuffer) throws IOException {
fileChannel.read(byteBuffer);
}
}

这个类是最大的一个类,值得注意的是几个静态方法:  addReader和parse。 addReader用于添加新的类型的Reader,parse用于解析文件。

parse的运行过程是首先读取第一个字节。推断是否有相应的解析实现类,假设有。就有相应的解析实现类去解析。假设没有,则抛出错误声明不支持。
以下写实现类就简单了,以下是FoxproDBase3的解析器:

public class FoxproDBase3Reader extends DbfReader {
protected void readFields() throws IOException {
fields = new ArrayList<Field>();
for (int i = 0; i < (header.getHeaderLength() - 32 - 1) / 32; i++) {
fields.add(readField());
}
} public byte getType() {
return 3;
} protected Field readField() throws IOException {
Field field = new Field();
ByteBuffer byteBuffer = ByteBuffer.allocate(32);
readByteBuffer(byteBuffer);
byte[] bytes = byteBuffer.array();
field.setName(new String(bytes, 0, 11, encode).trim().split("\0")[0]);
field.setType((char) bytes[11]);
field.setDisplacement(Util.getUnsignedInt(bytes, 12, 4));
field.setLength(Util.getUnsignedInt(bytes, 16, 1));
field.setDecimal(Util.getUnsignedInt(bytes, 17, 1));
field.setFlag(bytes[18]);
return field;
} protected void readHeader() throws IOException {
header = new Header();
ByteBuffer byteBuffer = ByteBuffer.allocate(31);
readByteBuffer(byteBuffer);
byte[] bytes = byteBuffer.array();
header.setLastUpdate((Util.getUnsignedInt(bytes, 0, 1) + 1900) * 10000 + Util.getUnsignedInt(bytes, 1, 1) * 100 + Util.getUnsignedInt(bytes, 2, 1));
header.setRecordCount(Util.getUnsignedInt(bytes, 3, 4));
header.setHeaderLength(Util.getUnsignedInt(bytes, 7, 2));
header.setRecordLength(Util.getUnsignedInt(bytes, 9, 2));
}
}

測试用例 

public class DbfReaderTest {
static String[] files = {"BESTIMATE20140401", "BHDQUOTE20140401"}; public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException {
for (String file : files) {
printFile(file);
}
} public static void printFile(String fileName) throws IOException, InstantiationException, IllegalAccessException {
Reader dbfReader = DbfReader.parse("E:\\20140401\\" + fileName + ".DBF");
for (Field field : dbfReader.getFields()) {
System.out.printf("name:%s %s(%d,%d)\n", field.getName(), field.getType(), field.getLength(), field.getDecimal());
}
System.out.println();
for (int i = 0; i < dbfReader.getHeader().getRecordCount(); i++) {
dbfReader.next();
for (Field field : dbfReader.getFields()) {
System.out.printf("%" + field.getLength() + "s", field.getStringValue());
}
System.out.println();
}
dbfReader.close(); }
}

能够看到最后的使用也是很简洁的。
代码统计

 

085959_EzQV_1245989.jpg (15.06 KB, 下载次数: 0)

下载附件

2015-5-27 22:33 上传

总共的代码行数是282行,去掉import和接口声明之类的。真正干活的代码大概就200行了:
总结 上面不仅展示了怎样实现DBF文件的解析。同一时候还展示了怎样在如今面临的需求与未来的扩展进行合理均衡的设计方式。
比方:要实现另外一个标准的DBF文件支持,仅仅要相似上面FoxproDBase3Reader类一样,简单实现之后,再调用DbfParser.addReader(xxxReader);
好的设计须要即避免过度设计,搞得太复杂。同一时候也要对未来的变化与扩展做适当考虑,避免新的需求来的时候须要这里动动,那里改改导致结构上的调整与变化,同一时候要注意遵守DRY原则。能够这样说假设程序中有必要的大量的反复,就说明一定存在结构设计上的问题。


欢迎关注:http://bbs.tinygroup.org

本例涉及的代码和框架资料,将会在这里分享。也欢迎进添加QQ群:228977971,让我们一起成长。

《自己动手写框架2》:用200行的DBF解析器来展示良好架构设计的更多相关文章

  1. 自己动手写框架——IoC的实现

    先看看 IoC百度百科 优化过程 namespace Test { class Program { static void Main(string[] args) { //场景 某公司客服要回访一些客 ...

  2. cJSON: 一个用c写的一个简单好用的JSON解析器

    转自:http://blog.csdn.net/chenzhongjing/article/details/9188347 下载地址: http://sourceforge.net/projects/ ...

  3. 只要200行JavaScript代码,就能把特斯拉汽车带到您身边

    Jerry的前一篇文章 如何使用JavaScript开发AR(增强现实)移动应用 (一) 介绍了用React-Native + ViroReact开发增强现实应用的一些预备知识. 本文咱们开始进入增强 ...

  4. 一起写一个JSON解析器

    [本篇博文会介绍JSON解析的原理与实现,并一步一步写出来一个简单但实用的JSON解析器,项目地址:SimpleJSON.希望通过这篇博文,能让我们以后与JSON打交道时更加得心应手.由于个人水平有限 ...

  5. 200行代码,7个对象——让你了解ASP.NET Core框架的本质

    2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘>的分享.在此次分享中,我按照ASP.NET Core自身的运行原理和设计 ...

  6. 200行代码,7个对象——让你了解ASP.NET Core框架的本质

    原文:200行代码,7个对象--让你了解ASP.NET Core框架的本质 2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘&g ...

  7. 200行代码,7个对象——让你了解ASP.NET Core框架的本质[3.x版]

    2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘>的分享.在此次分享中,我按照ASP.NET Core自身的运行原理和设计 ...

  8. 动手写一个简单的Web框架(模板渲染)

    动手写一个简单的Web框架(模板渲染) 在百度上搜索jinja2,显示的大部分内容都是jinja2的渲染语法,这个不是Web框架需要做的事,最终,居然在Werkzeug的官方文档里找到模板渲染的代码. ...

  9. HashMap设计原理与实现(下篇)200行带你写自己的HashMap!!!

    HashMap设计原理与实现(下篇)200行带你写自己的HashMap!!! 我们在上篇文章哈希表的设计原理当中已经大体说明了哈希表的实现原理,在这篇文章当中我们将自己动手实现我们自己的HashMap ...

随机推荐

  1. Swift语言精要 - 浅谈代理模式(Delegate)

    在iOS编程中,我们经常谈到代理代理,也就是delegate,那么什么是代理呢? 我们来看一下cocoa对它的描述: Delegation is Cocoa’s term for passing of ...

  2. FIS.js前端开发的使用说明文档

    文档结构 什么是FIS 部署FIS FIS基本使用 模块定义 加载方式 调用Tangram 2.0 一.什么是FIS FIS提供了一套贯穿开发流程的开发体系和集成开发环境,为产品线提供前端开发底层架构 ...

  3. 【树莓派】RPi desktop系统重启或关机挂起几个问题:plymouth-reboot.service、plymouth-poweroff.service、Deconfiguring network interfaces

    在基于intel平台安装的RPi desktop关机或者重启时,会存在挂起的问题,一直卡着不动. 挂起问题有3个: 系统关机时候,停留在:plymouth-poweroff.service 系统重启时 ...

  4. 解决工作中遇到的一个"打开,保存"文件框的bug的过程

    工作中遇到的这个问题还是很有意思的.其中嵌套了很多奇葩性的问题. (转载请指明出于breaksoftware的csdn博客) 我们来看下故事的发生过程,QA同学发现我们存在如下的bug 看到如此多的串 ...

  5. flume-ng-elasticsearch 索引时间命名问题(时区和时间格式)

    目前版本还不支持,只把索引名称的加载项传递过去了

  6. 算法笔记_229:有理数的循环节(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 1/7 = 0.142857142... 是个无限循环小数.任何有理数都可以表示为无限循环小数的形式. 本题目要求即是:给出一个数字的循环小数表示法 ...

  7. string format 格式化小数位

    String具体的格式化数据的方法 int a = 12345678;格式为sring输出Label1.Text = string.Format("asdfadsf{0}adsfasdf&q ...

  8. Git之第三方托管oschina

    一.git 简介 1.Git是一款免费.开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目. 2.Git是一个开源的分布式版本控制系统,用以有效.高速的处理从很小到非常大的项目版本管理. ...

  9. 【Linux】shell编程案例

    一.随机生成字符文件名 1.需求描述: 使用for循环在/usr/test目录下创建10个html文件,其中每个文件需要包含10个随机小写字母加固定字符串test,案例名称如下: 以下为示例: 2.代 ...

  10. CentOS7下 简单安装和配置Elasticsearch Kibana Filebeat 快速搭建集群日志收集平台

    目录 1.添加elasticsearch官网的yum源 2.Elasticsearch 安装elasticsearch 配置elasticsearch 启动elasticsearch并设为开机启动 3 ...