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

由于工作关系,需要工作当中,需要读取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://web.j2ee.top。本例涉及的代码和框架资料,将会在这里分享。也欢迎进加入QQ群:228977971,让我们一起成长!

时间: 2024-09-29 23:07:11

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

TinyDBF-用200行的DBF解析器来展示良好架构设计

由于工作关系,需要工作当中,需要读取DBF文件,找了一些DBF读取开源软件,要么是太过庞大,动不动就上万行,要么是功能有问题,编码,长度,总之是没有找到一个非常爽的.在万般无奈之下,我老人家怒从心头起,恶向胆边生,决定自己写一下.结果只用了不到300行代码就搞定了,当然搞定不是唯一目标,还要优雅简洁的搞定,亲们跟随我的脚步一起感受一下简洁的设计与实现吧. 在开始编码之前,先介绍一下DBF,这个DBF可是个老东西,在DOS时代就已经出现,并且风骚了相当一段时间,后来随着大型数据库的应用,它逐步没落

《自己动手写框架9》:理想的开源框架与设计原则

理想的开源框架?她应该是小的.简单的,满足Simple Is Beautiful?她应该是成长性好的,随着不断的扩展,她可以越来越丰满?她应该是有良好工具支持的,为什么要花时间做工具可以完成的事情呢??她应该是自组装的,也就是尽可能的脱离配置,而是用一种依赖即可用,取消依赖即消失的全自动处理模式?她应该是模块化的,所有的内容都可以被打入jar包而作为一个整体进行发布,并且能支持热部署的,可以开着车儿换轮胎的?她应该是支持水平部署的,想加服务器就加,想减服务器就减?她应该是有良好知识积累体系的,使

《自己动手写框架1》:缘起

前言 自己动手写框架?第一次接触这本书的读者可能会有一些惊讶.是的,这就是写本书的缘由. 1.1  缘由 从毕业以来,我一直从事软件开发及软件管理,做过大量的项目与产品,但是还是喜欢平台软件及软件管理方面的工作.多年的业务开发及平台构建的过程中,踩了许许多多的坑,也积累了许许多多的经验. 和很多的朋友一样,在没有开发框架之前,可能我们只有一些落在纸面上的编码规范,或者说是开发约定.但不管怎么说,这些规范和约定对于我们软件开发过程还是有相当的指导与规范作用的.再后来,随着时间的推移,我们的软件代码

《自己动手写框架8》:高屋建瓴,理念先行

<史记·高祖本纪>:"地势便利,其以下兵于诸侯,譬犹居高屋之上建瓴水也."这里用到了高屋建瓴这个词.意思是把瓶子里的水从高层顶上倾倒.比喻居高临下,不可阻遏的形势.现指对事物把握全面,了解透彻.此典故于汉高祖刘邦欲杀功臣韩信,大夫田肯进言到"陛下牢牢地控制着三秦(关中),陛下利用这雄险的地势,来控制.驾御诸侯,就如从高高的屋脊上把水从瓶子里倒下去."以此来表彰韩信的功劳,于是,刘邦赦免了韩信,只是将他降为淮阴侯. 同样,设计企业框架,也要对事物把握全面,

《自己动手写框架4》:分布式锁的简单实现

分布式锁在分布式应用当中是要经常用到的,主要是解决分布式资源访问冲突的问题.  一开始考虑采用ReentrantLock来实现,但是实际上去实现的时候,是有问题的,ReentrantLock的lock和unlock要求必须是在同一线程进行,而分布式应用中,lock和unlock是两次不相关的请求,因此肯定不是同一线程,因此导致无法使用ReentrantLock. 接下来就考虑采用自己做个状态来进行锁状态的记录,结果发现总是死锁,仔细一看代码,能不锁死么. public synchronized

《自己动手写框架7》:关于框架体系与战术的思考

什么是框架? 这个问题实际上许多"做框架"的人也不明白. 框架和库的本质不同在于: 框架考虑的是机制的复用,而库主要考虑的是代码的复用 框架考虑的是在机制不变的情况下进行扩展,而库则基本不考虑扩展方面的问题 框架本身是不完整的,在大多数的情况下它自己是干不了啥事情的,而库自身是完整的,可以解决某个领域的问题. 框架是活的,通过不断的扩展与衍生,它就更加强大,而库而是死的,发布时是怎样,就是怎样. 当然,关于这两货之间的比较,还有许多个角度,但我个人觉得本质是我上面举的这些. 设计的时候

自己动手写框架(一)

PHP框架接触有一段时间,主要是Kohana和Thinkphp.这两个框架各有各的特色,总体来说Kohana还是相对来说比较简单,但是国内资料较少. 简单的,我把我的这个框架命名为Demo. 框架搭建第一步,总体目录规划: 目录  APP(应用)->TEM (主题) -> SYS (系统) ||-------Application 应用 |---classes Controller Models |---views || ---Template 主题 |---classes Controlle

《自己动手写框架5》:生态圈的建立

曾经有人提出过一个看似天方夜谭的设想,在我们生活的地球上再造一个"迷你地球",探求人类在这个现代"南泥湾"之中自给自足,以及未来在月球或火星上建立生存空间的可能性.美国得克萨斯州的石油大王爱德华·巴斯为此憧憬不已. 既然是自己动手写框架,我这里想借鉴一下生物圈(Biosphere)这个概念来描述一下.生物圈是指地球上所有生态系统的统合整体,是地球的一个外层圈,其范围大约为海平面上下垂直约10公里.它包括地球上有生命存在和由生命过程变化和转变的空气.陆地.岩石圈和水.

《自己动手写框架3》:业务流程引擎

一般的时候,我们都采用编程式开发,编程式开发的好处非常明显:直接.高效.自由,当然其缺点也是有的,与其优点刚好相对,因为直接,所以有些变化都要进行代码上的修改:因为高效,所以一旦出问题,导致的结果也比较严重,因为自由,所以带来的修改风险也比较大.  这也就是许多大的公司都在进行流程化开发的重要原因之一,比如:上海普元,Livebos, Justep,还有许许多多知名不知名的公司都有类似的流程化开发引擎存在,通过流程化开发,增强代码的复用性,降低软件开发成本及测试成本,提升软件的可维护性及降低维护