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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

序言

由于工作关系,需要工作当中,需要读取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

相关文章
|
13天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
57 6
|
13天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
29 1
|
13天前
|
Kubernetes Cloud Native 云计算
云原生技术深度解析:重塑企业IT架构的未来####
本文深入探讨了云原生技术的核心理念、关键技术组件及其对企业IT架构转型的深远影响。通过剖析Kubernetes、微服务、容器化等核心技术,本文揭示了云原生如何提升应用的灵活性、可扩展性和可维护性,助力企业在数字化转型中保持领先地位。 ####
|
14天前
|
运维 Kubernetes Cloud Native
Kubernetes云原生架构深度解析与实践指南####
本文深入探讨了Kubernetes作为领先的云原生应用编排平台,其设计理念、核心组件及高级特性。通过剖析Kubernetes的工作原理,结合具体案例分析,为读者呈现如何在实际项目中高效部署、管理和扩展容器化应用的策略与技巧。文章还涵盖了服务发现、负载均衡、配置管理、自动化伸缩等关键议题,旨在帮助开发者和运维人员掌握利用Kubernetes构建健壮、可伸缩的云原生生态系统的能力。 ####
|
22天前
|
边缘计算 自动驾驶 5G
|
18天前
|
机器学习/深度学习 人工智能 自然语言处理
医疗行业的语音识别技术解析:AI多模态能力平台的应用与架构
AI多模态能力平台通过语音识别技术,实现实时转录医患对话,自动生成结构化数据,提高医疗效率。平台具备强大的环境降噪、语音分离及自然语言处理能力,支持与医院系统无缝集成,广泛应用于门诊记录、多学科会诊和急诊场景,显著提升工作效率和数据准确性。
|
22天前
|
消息中间件 编解码 开发者
深入解析 Flutter兼容鸿蒙next全体生态的横竖屏适配与多屏协作兼容架构
本文深入探讨了 Flutter 在屏幕适配、横竖屏切换及多屏协作方面的兼容架构。介绍了 Flutter 的响应式布局、逻辑像素、方向感知、LayoutBuilder 等工具,以及如何通过 StreamBuilder 和 Provider 实现多屏数据同步。结合实际应用场景,如移动办公和教育应用,展示了 Flutter 的强大功能和灵活性。
88 6
|
22天前
|
存储 SQL 缓存
AnalyticDB 实时数仓架构解析
AnalyticDB 是阿里云自研的 OLAP 数据库,广泛应用于行为分析、数据报表、金融风控等应用场景,可支持 100 trillion 行记录、10PB 量级的数据规模,亚秒级完成交互式分析查询。本文是对 《 AnalyticDB: Real-time OLAP Database System at Alibaba Cloud 》的学习总结。
39 1
|
7天前
|
API 持续交付 网络架构
深入解析微服务架构:原理、优势与实践
深入解析微服务架构:原理、优势与实践
11 0
|
24天前
|
数据管理 Nacos 开发者
"Nacos架构深度解析:一篇文章带你掌握业务层四大核心功能,服务注册、配置管理、元数据与健康检查一网打尽!"
【10月更文挑战第23天】Nacos 是一个用于服务注册发现和配置管理的平台,支持动态服务发现、配置管理、元数据管理和健康检查。其业务层包括服务注册与发现、配置管理、元数据管理和健康检查四大核心功能。通过示例代码展示了如何在业务层中使用Nacos,帮助开发者构建高可用、动态扩展的微服务生态系统。
69 0

推荐镜像

更多
下一篇
无影云桌面