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

简介:

序言

由于工作关系,需要工作当中,需要读取DBF文件,找了一些DBF读取开源软件,要么是太过庞大,动不动就上万行,要么是功能有问题,编码,长度,总之是没有找到一个非常爽的。在万般无奈之下,我老人家怒从心头起,恶向胆边生,决定自己写一下。结果只用了不到300行代码就搞定了,当然搞定不是唯一目标,还要优雅简洁的搞定,亲们跟随我的脚步一起感受一下简洁的设计与实现吧。

在开始编码之前,先介绍一下DBF,这个DBF可是个老东西,在DOS时代就已经出现,并且风骚了相当一段时间,后来随着大型数据库的应用,它逐步没落,但是由于其简洁易用的特点,还是应用在大量的数据交换当中。但是其发展过程中,也形成了许多种版本,不同版本的结构不一样,也就决定 了其解析程序也是不一样的。

今天我只实现了Foxbase/DBaseIII的解析,但是也为扩展各种其它版本做好了准备。

接口设计

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

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

代码实现

首先实现Reader的抽象类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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的解析器:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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 ));
     }
}

测试用例

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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();
 
     }
}

可以看到最后的使用也是非常简洁的。

代码统计

总共的代码行数是282行,去掉import和接口声明之类的,真正干活的代码大概就200行了:

总结

上面不仅展示了如何实现DBF文件的解析,同时还展示了如何在现在面临的需求与未来的扩展进行合理均衡的设计方式。

比如:要实现另外一个标准的DBF文件支持,只要类似上面FoxproDBase3Reader类一样,简单实现之后,再调用DbfParser.addReader(xxxReader);

好的设计需要即避免过度设计,搞得太复杂,同时也要对未来的变化与扩展做适当考虑,避免新的需求来的时候需要这里动动,那里改改导致结构上的调整与变化,同时要注意遵守DRY原则,可以这样说如果程序中有必要的大量的重复,就说明一定存在结构设计上的问题。

所有的代码都可以在下面的连接看到:

https://git.oschina.net/tinyframework/tiny/tree/master/framework/org.tinygroup.dbf/src/main/java/org/tinygroup/dbf

相关文章
|
6月前
|
运维 负载均衡 微服务
|
6月前
|
数据采集 机器学习/深度学习 人工智能
YOLOv11浅浅解析:架构创新
YOLOv11是YOLO系列最新升级版,通过C3k2模块、SPPF优化和解耦检测头等创新,显著提升检测精度与速度,mAP提高2-5%,推理更快,支持多平台部署,适用于工业、安防、自动驾驶等场景。
|
7月前
|
人工智能 JavaScript 前端开发
LangGraph架构解析
本文深入解析了传统Agent开发的三大痛点:状态管理碎片化、流程控制复杂及扩展性差,提出使用LangGraph通过有向图模型重构工作流,将LLM调用与工具执行抽象为节点,实现动态流程跳转。文中详述LangGraph四大核心组件——状态机引擎、节点设计、条件边与工具层集成,并结合生产环境最佳实践,如可视化调试、状态持久化与人工干预机制,最终对比LangGraph与传统方案的性能差异,给出选型建议。
1376 0
|
6月前
|
机器学习/深度学习 人工智能 搜索推荐
从零构建短视频推荐系统:双塔算法架构解析与代码实现
短视频推荐看似“读心”,实则依赖双塔推荐系统:用户塔与物品塔分别将行为与内容编码为向量,通过相似度匹配实现精准推送。本文解析其架构原理、技术实现与工程挑战,揭秘抖音等平台如何用AI抓住你的注意力。
1528 7
从零构建短视频推荐系统:双塔算法架构解析与代码实现
|
5月前
|
机器学习/深度学习 人工智能 自然语言处理
34_GPT系列:从1到5的架构升级_深度解析
大型语言模型(LLM)的发展历程中,OpenAI的GPT系列无疑扮演着至关重要的角色。自2018年GPT-1问世以来,每一代GPT模型都在架构设计、预训练策略和性能表现上实现了质的飞跃。本专题将深入剖析GPT系列从1.17亿参数到能够处理百万级token上下文的技术演进,特别关注2025年8月8日发布的GPT-5如何引领大模型技术迈向通用人工智能(AGI)的重要一步。
|
5月前
|
存储 监控 安全
132_API部署:FastAPI与现代安全架构深度解析与LLM服务化最佳实践
在大语言模型(LLM)部署的最后一公里,API接口的设计与安全性直接决定了模型服务的可用性、稳定性与用户信任度。随着2025年LLM应用的爆炸式增长,如何构建高性能、高安全性的REST API成为开发者面临的核心挑战。FastAPI作为Python生态中最受青睐的Web框架之一,凭借其卓越的性能、强大的类型安全支持和完善的文档生成能力,已成为LLM服务化部署的首选方案。
|
6月前
|
存储 监控 NoSQL
Redis高可用架构全解析:从主从复制到集群方案
Redis高可用确保服务持续稳定,避免单点故障导致数据丢失或业务中断。通过主从复制实现数据冗余,哨兵模式支持自动故障转移,Cluster集群则提供分布式数据分片与水平扩展,三者层层递进,保障读写分离、容灾切换与大规模数据存储,构建高性能、高可靠的Redis架构体系。

推荐镜像

更多
  • DNS